Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update 251 #1382

Merged
merged 29 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b85b2cf
Adds Carrier and Star Name to Confirmation Dialog for Hiring a Specia…
FireGamer3 Feb 1, 2025
df630b1
Makes it so stars are not unnecessarily fetched when they are known
FireGamer3 Feb 1, 2025
761177c
Use map for scanning ranges
SpacialCircumstances Feb 1, 2025
247c08b
Merge pull request #1363 from FireGamer3/add_carrier_star_name_to_spe…
SpacialCircumstances Feb 3, 2025
59afc57
Merge pull request #1365 from FireGamer3/dont_search_for_already_know…
SpacialCircumstances Feb 5, 2025
dcd18db
Fix non-existing games leaving the player stuck in a loading screen b…
SpacialCircumstances Feb 6, 2025
5247772
Remove imports
SpacialCircumstances Feb 6, 2025
4a2f46e
Begin defining typed API
SpacialCircumstances Feb 6, 2025
c3b08d7
Highlight selected carrier paths
MoustachioMario Feb 7, 2025
e87bb7f
Cover entire admin API in typed API definition
SpacialCircumstances Feb 8, 2025
b3664a1
Preliminary idea of new SanitiseAllCarrierWaypointsByScanningRange logic
FireGamer3 Feb 9, 2025
30dda1d
Account for wormhole stars
FireGamer3 Feb 9, 2025
8d3caa9
Add support for typed GET calls to API
SpacialCircumstances Feb 10, 2025
d2df060
Add support for typed path parameters
SpacialCircumstances Feb 10, 2025
8035510
Implement other http methods
SpacialCircumstances Feb 10, 2025
3a85d58
Work on typed api requests
SpacialCircumstances Feb 10, 2025
a4b1900
Implement some API methods
SpacialCircumstances Feb 11, 2025
82495b5
Add automatic date conversion for responses
SpacialCircumstances Feb 11, 2025
a3863e8
Merge pull request #1378 from solaris-games/feature/typed-api
SpacialCircumstances Feb 11, 2025
63e06d8
Removed old sanitiseAllCarrierWaypointsByScanningRange, replaced with…
FireGamer3 Feb 13, 2025
8479f3b
Adds ownedByPlayerId sanity check
FireGamer3 Feb 13, 2025
e5a1dd3
Merge pull request #1372 from FireGamer3/faster_dark_mode_carrier_check
SpacialCircumstances Feb 13, 2025
fd3367a
Add fps limit setting
SpacialCircumstances Feb 13, 2025
14e574a
Fix type error
SpacialCircumstances Feb 14, 2025
231e49f
Merge pull request #1371 from MoustachioMario/improvement/carrier-hig…
SpacialCircumstances Feb 14, 2025
92ebfa2
Change multiplied rank calculation
SpacialCircumstances Feb 14, 2025
8557bee
Merge remote-tracking branch 'origin/dev' into dev
SpacialCircumstances Feb 14, 2025
d44e636
Fix black hole graphics
SpacialCircumstances Feb 14, 2025
ba1c84b
Add carrier limit to prevent spam
SpacialCircumstances Feb 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions client/src/assets/map-objects/128x128_star_black_hole.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 20 additions & 2 deletions client/src/game/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ export class Map extends EventEmitter {
this.userSettings = userSettings
this.game = game

this.app.ticker.maxFPS = userSettings.technical.fpsLimit || 60;

this.pathManager = new PathManager( game, userSettings, this )


Expand Down Expand Up @@ -432,7 +434,9 @@ export class Map extends EventEmitter {
}
}

reloadGame (game, userSettings) {
reloadGame (game: Game, userSettings: UserGameSettings) {
this.app.ticker.maxFPS = userSettings.technical.fpsLimit;

this.game = game

this.pathManager!.setup(game, userSettings)
Expand Down Expand Up @@ -480,7 +484,7 @@ export class Map extends EventEmitter {
let existing = this.carriers.find(x => x.data!._id === carrierData._id)

if (existing) {
let player = gameHelper.getPlayerById(game, carrierData.ownedByPlayerId)
let player = gameHelper.getPlayerById(game, carrierData.ownedByPlayerId!)

existing.setup(carrierData, userSettings, this.context, this.stars, player, game.constants.distances.lightYear)
} else {
Expand Down Expand Up @@ -721,6 +725,7 @@ export class Map extends EventEmitter {

c.unselect()
}
this.clearCarrierHighlights();
}

unselectAllStarsExcept (star) {
Expand All @@ -743,6 +748,11 @@ export class Map extends EventEmitter {
c.unselect()
}
})
this.clearCarrierHighlights();
}

clearCarrierHighlights() {
this.waypoints!.clear();
}

onTick(deltaTime) {
Expand Down Expand Up @@ -915,6 +925,14 @@ export class Map extends EventEmitter {

selectedCarrier!.toggleSelected()

//highlight carrier path if selected
if (selectedCarrier?.isSelected) {
this.waypoints!.draw(selectedCarrier!.data, false);
}
else {
this.waypoints!.clear();
}

if (!dic.tryMultiSelect || !this.tryMultiSelect(e.location)) {
this.emit('onCarrierClicked', e)
} else {
Expand Down
11 changes: 6 additions & 5 deletions client/src/game/waypoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ class Waypoints extends EventEmitter {
this.container.removeChildren()
}

draw (carrier) {
draw (carrier, plotting=true) {
this.clear()

this.carrier = carrier

this.drawHyperspaceRange()
this.drawLastWaypoint()
this.drawNextWaypoints()
if (plotting) {
this.drawHyperspaceRange()
this.drawLastWaypoint()
this.drawNextWaypoints()
}
this.drawPaths()
}

Expand Down
6 changes: 6 additions & 0 deletions client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { DiplomacyClientSocketHandler } from './sockets/socketHandlers/diplomacy
import { GameClientSocketHandler } from './sockets/socketHandlers/game'
import { PlayerClientSocketHandler } from "./sockets/socketHandlers/player"
import { createSolarisStore, type State } from './store'
import { httpInjectionKey } from "./services/typedapi"
import {createHttpClient} from "./util/http";

// Note: This was done to get around an issue where the Steam client
// had bootstrap as undefined. This also affects the UI template we're using,
Expand Down Expand Up @@ -75,6 +77,10 @@ const playerClientSocketEmitter: PlayerClientSocketEmitter = new PlayerClientSoc
app.provide(playerClientSocketEmitterInjectionKey, playerClientSocketEmitter);
app.provide(eventBusInjectionKey, eventBus);

const httpClient = createHttpClient();

app.provide(httpInjectionKey, httpClient);

const clientHandler: ClientHandler = new ClientHandler(socket, store, playerClientSocketEmitter);

app.config.globalProperties.$confirm = async function(title, text, confirmText = 'Yes', cancelText = 'No', hideCancelButton = false, cover = false) {
Expand Down
17 changes: 17 additions & 0 deletions client/src/services/typedapi/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {type Announcement, createAdminRoutes, type ListPasswordReset, type ListUser} from "@solaris-common";
import type { Axios } from "axios";
import {doGet, type ResponseResult} from ".";

const routes = createAdminRoutes<string>();

export const getUsers = (axios: Axios) => async (): Promise<ResponseResult<ListUser<string>[]>> => {
return doGet(axios)(routes.listUsers, {});
}

export const getPasswordResets = (axios: Axios) => async (): Promise<ResponseResult<ListPasswordReset<string>[]>> => {
return doGet(axios)(routes.listPasswordResets, {});
}

export const getAllAnnouncements = (axios: Axios) => async (): Promise<ResponseResult<Announcement<string>[]>> => {
return doGet(axios)(routes.getAllAnnouncements, {});
}
144 changes: 144 additions & 0 deletions client/src/services/typedapi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { type Route, type GetRoute, type PostRoute, type PatchRoute, type DeleteRoute, type PutRoute } from "@solaris-common";
import { type Axios, type AxiosRequestConfig, isAxiosError } from "axios";
import type { InjectionKey } from "vue";

export type ReqOptions = AxiosRequestConfig;

export enum ResponseResultKind {
Ok = 'ok',
RequestError = 'requestError',
ResponseError = 'responseError',
};

export type ResponseResultOk<T> = {
kind: ResponseResultKind.Ok,
data: T,
};

export type ResponseResultRequestError = {
kind: ResponseResultKind.RequestError,
cause: Error,
}

export type ResponseResultResponseError = {
kind: ResponseResultKind.ResponseError,
status: number,
data: string,
cause: Error,
};

export type ResponseResult<T> = ResponseResultOk<T> | ResponseResultRequestError | ResponseResultResponseError;

export const isError = <T>(result: ResponseResult<T>) => result.kind !== ResponseResultKind.Ok;

const PATH_VARIABLE_PATTERN = /:(.\w+)/;

const pathReplacement = <PP extends Object, T1, T2>(route: Route<PP, T1, T2>, params: PP) => {
return route.path.replaceAll(PATH_VARIABLE_PATTERN, (_match, g1) => {
const param = params[g1];

if (param === undefined) {
throw new Error(`Call to ${route.path} is missing value for parameter ${g1}`);
}

return param;
});
}

const mapError = <T>(e: unknown, path: string): ResponseResult<T> => {
if (isAxiosError(e)) {
if (e.response) {
return {
kind: ResponseResultKind.ResponseError,
status: e.response.status,
data: e.response.data,
cause: e,
}
} else {
return {
kind: ResponseResultKind.RequestError,
cause: e,
}
}
} else {
throw new Error(`Error calling ${path}`, { cause: e });
}
}

export const doGet = (axios: Axios) => async <PathParams extends Object, Resp>(route: GetRoute<PathParams, Resp>, args: PathParams, options?: ReqOptions): Promise<ResponseResult<Resp>> => {
const path = pathReplacement(route, args);

try {
const response = await axios.get<Resp>(path, options);

return {
kind: ResponseResultKind.Ok,
data: response.data,
}
} catch (e) {
return mapError(e, path);
}
}


export const doPost = <PathParams extends Object, Req, Resp>(axios: Axios) => async (route: PostRoute<PathParams, Req, Resp>, args: PathParams, req: Req, options?: ReqOptions): Promise<ResponseResult<Resp>> => {
const path = pathReplacement(route, args);

try {
const response = await axios.post<Resp>(path, req, options);

return {
kind: ResponseResultKind.Ok,
data: response.data,
}
} catch (e) {
return mapError(e, path);
}
}

export const doPatch = <PathParams extends Object, Req, Resp>(axios: Axios) => async (route: PatchRoute<PathParams, Req, Resp>, args: PathParams, req: Req, options?: ReqOptions): Promise<ResponseResult<Resp>> => {
const path = pathReplacement(route, args);

try {
const response = await axios.patch<Resp>(path, req, options);

return {
kind: ResponseResultKind.Ok,
data: response.data,
}
} catch (e) {
return mapError(e, path);
}
}

export const doDelete = <PathParams extends Object, Req, Resp>(axios: Axios) => async (route: DeleteRoute<PathParams, Resp>, args: PathParams, req: Req, options?: ReqOptions): Promise<ResponseResult<Resp>> => {
const path = pathReplacement(route, args);

try {
const response = await axios.delete<Resp>(path, options);

return {
kind: ResponseResultKind.Ok,
data: response.data,
}
} catch (e) {
return mapError(e, path);
}
}

export const doPut = <PathParams extends Object, Req, Resp>(axios: Axios) => async (route: PutRoute<PathParams, Req, Resp>, args: PathParams, req: Req, options?: ReqOptions): Promise<ResponseResult<Resp>> => {
const path = pathReplacement(route, args);

try {
const response = await axios.put<Resp>(path, req, options);

return {
kind: ResponseResultKind.Ok,
data: response.data,
}
} catch (e) {
return mapError(e, path);
}
}

export const httpInjectionKey: InjectionKey<Axios> = Symbol('httpClient');
33 changes: 33 additions & 0 deletions client/src/util/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from "axios";

const isISODate = (value: unknown) => {
return typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d+)?Z$/);
};

export const convertDates = (obj: unknown) => {
if (Array.isArray(obj)) {
return obj.map(convertDates);
} else if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, convertDates(value)])
);
} else if (isISODate(obj)) {
return new Date(obj as string);
}
return obj;
};

export const createHttpClient = () => {
axios.create();

axios.interceptors.response.use((response) => {
if (response.data) {
response.data = convertDates(response.data);
}
return response;
}, (error) => {
return Promise.reject(error);
});

return axios
}
7 changes: 5 additions & 2 deletions client/src/views/game/Game.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ import GameApiService from '../../services/api/game'
import UserApiService from '../../services/api/user'
import GameHelper from '../../services/gameHelper'
import AudioService from '../../game/audio'
import moment from 'moment'
import gameHelper from '../../services/gameHelper'
import authService from '../../services/api/auth'
import ColourOverrideDialog from "./components/player/ColourOverrideDialog.vue";
import { eventBusInjectionKey } from '../../eventBus'
import { inject } from 'vue';
import { playerClientSocketEmitterInjectionKey } from '../../sockets/socketEmitters/player'
import PlayerEventBusEventNames from '../../eventBusEventNames/player'
import GameEventBusEventNames from '../../eventBusEventNames/game'
import router from '../../router'

export default {
components: {
Expand Down Expand Up @@ -172,6 +171,10 @@ export default {
}
} catch (err) {
console.error(err)

this.$toast.error('Game failed to load');

await router.push({ name: 'main-menu' })
}
},
async reloadSettings () {
Expand Down
7 changes: 7 additions & 0 deletions client/src/views/game/components/menu/OptionsForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,13 @@
</div>
</div>

<div class="row pt-1 pb-1">
<label for="fpsLimit" class="col-12 col-sm-6 col-form-label">FPS Limit</label>
<div class="col-12 col-sm-6">
<input class="form-control" min="0" max="240" type="number" id="fpsLimit" v-model="settings.technical.fpsLimit" :disabled="isSavingSettings">
</div>
</div>

<form-error-list v-bind:errors="errors"/>

<div class="row mt-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default {
this.$emit('onOpenCarrierDetailRequested', carrier._id)
},
async hireSpecialist (specialist) {
if (!await this.$confirm('Hire specialist', `Are you sure you want to hire a ${specialist.name} for ${this.getSpecialistActualCostString(specialist)}?`)) {
if (!await this.$confirm('Hire specialist', `Are you sure you want to hire a ${specialist.name} for ${this.getSpecialistActualCostString(specialist)} on Carrier ${this.carrier.name}?`)) {
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default {
this.$emit('onOpenStarDetailRequested', star._id)
},
async hireSpecialist (specialist) {
if (!await this.$confirm('Hire specialist', `Are you sure you want to hire a ${specialist.name} for ${this.getSpecialistActualCostString(specialist)}?`)) {
if (!await this.$confirm('Hire specialist', `Are you sure you want to hire a ${specialist.name} for ${this.getSpecialistActualCostString(specialist)} on Star ${this.star.name}?`)) {
return
}

Expand Down
Loading