diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index e7e59df..1a67d9e 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -11,10 +11,14 @@ io.onConnection((channel) => { const playerId = channel.id ?? raise("Channel ID is null"); // ID is assigned to player store.dispatch(PlayerActions.connect(playerId)); - if (store.getState().game.gameMode == null) { + if (store.getState().game.game == null) { store.dispatch( - GameActions.assignGameMode({ - gameMode: "FFA", + GameActions.setupGame({ + game: { + gameMode: "FFA", + map: "testMap1", + currentTimeMs: 0, // TODO: Add this later when games can actually start and end https://app.clickup.com/t/86b5v1mym + }, }), ); } @@ -26,7 +30,7 @@ io.onConnection((channel) => { team.playerIds.includes(playerId), ); // In FFA, if the player disconnects, we don't want to keep their team - if (store.getState().game.gameMode === "FFA" && playersTeam) { + if (store.getState().game.game?.gameMode === "FFA" && playersTeam) { store.dispatch(GameActions.deleteTeam({ teamId: playersTeam[0] })); } diff --git a/packages/frontend/src/game/HUD/components/gameScore/GameScore.tsx b/packages/frontend/src/game/HUD/components/gameScore/GameScore.tsx index 7fa233c..3b8e238 100644 --- a/packages/frontend/src/game/HUD/components/gameScore/GameScore.tsx +++ b/packages/frontend/src/game/HUD/components/gameScore/GameScore.tsx @@ -52,7 +52,7 @@ export const GameScore: React.FC = ({ gameStateRef }) => { } }); - const gameMode = gameStateRef.current.game.gameMode; + const gameMode = gameStateRef.current.game.game?.gameMode; if (gameMode == null) return null; const liveTeams = gameStateRef.current.game.teams; diff --git a/packages/frontend/src/game/World.tsx b/packages/frontend/src/game/World.tsx index 7cd5139..85bccef 100644 --- a/packages/frontend/src/game/World.tsx +++ b/packages/frontend/src/game/World.tsx @@ -18,7 +18,6 @@ export const World: React.FC = () => { const gameStateRef: GameStateRef = React.useRef({ game: { game: null, - gameMode: null, teams: {}, }, player: { diff --git a/packages/frontend/src/game/utils/geckos/gameState/updateClientGameState.ts b/packages/frontend/src/game/utils/geckos/gameState/updateClientGameState.ts index a0489f0..9864f94 100644 --- a/packages/frontend/src/game/utils/geckos/gameState/updateClientGameState.ts +++ b/packages/frontend/src/game/utils/geckos/gameState/updateClientGameState.ts @@ -7,7 +7,6 @@ interface UpdateClientGameStateProps { } export const updateClientGameState = ({ game, gameStateRef }: UpdateClientGameStateProps) => { - gameStateRef.current.game.gameMode = game.gameMode; gameStateRef.current.game.teams = { ...game.teams }; if (game.game) { gameStateRef.current.game.game = { ...game.game }; diff --git a/packages/lib/src/engine/slices/game/gameSlice.types.ts b/packages/lib/src/engine/slices/game/gameSlice.types.ts deleted file mode 100644 index 9b03736..0000000 --- a/packages/lib/src/engine/slices/game/gameSlice.types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GAME_MODES } from "../../../objects/gameModes"; - -export interface GameTeamState { - name: string; - playerIds: string[]; - currentScore: number; -} - -export interface GameGameState { - currentTimeMs: number; // TODO: Add this later when games can actually start and end https://app.clickup.com/t/86b5v1mym -} - -export interface GameSliceState { - game: GameGameState | null; - gameMode: keyof typeof GAME_MODES | null; - teams: Record; -} diff --git a/packages/lib/src/engine/slices/game/gameSlice.ts b/packages/lib/src/engine/slices/gameSlice.ts similarity index 76% rename from packages/lib/src/engine/slices/game/gameSlice.ts rename to packages/lib/src/engine/slices/gameSlice.ts index 249944d..16516e0 100644 --- a/packages/lib/src/engine/slices/game/gameSlice.ts +++ b/packages/lib/src/engine/slices/gameSlice.ts @@ -1,37 +1,49 @@ import "immer"; -import { assertNever } from "../../../utils/assertNever"; +import { assertNever } from "../../utils/assertNever"; import { createSlice } from "@reduxjs/toolkit"; -import { GAME_MODES } from "../../../objects/gameModes"; -import { objectEntries } from "../../../utils/objectUtils"; -import { raise } from "../../../utils/raise"; -import type { GameSliceState } from "./gameSlice.types"; +import { GAME_MODES } from "../../objects/gameModes"; +import { MAP_CONFIGS } from "../../objects/mapConfigs"; +import { objectEntries } from "../../utils/objectUtils"; +import { raise } from "../../utils/raise"; import type { PayloadAction } from "@reduxjs/toolkit"; import type { WritableDraft } from "immer"; +export interface GameTeamState { + name: string; + playerIds: string[]; + currentScore: number; +} + +export interface GameGameState { + map: keyof typeof MAP_CONFIGS; + gameMode: keyof typeof GAME_MODES; + currentTimeMs: number; // TODO: Add this later when games can actually start and end https://app.clickup.com/t/86b5v1mym +} + +export interface GameSliceState { + game: GameGameState | null; + teams: Record; +} + +type State = WritableDraft; +type Action = PayloadAction; + const initialState: GameSliceState = { game: null, - gameMode: null, teams: {}, }; -type State = WritableDraft; -type Action = PayloadAction; - export const gameSlice = createSlice({ name: "game", initialState, reducers: { - assignGameMode: (state: State, action: Action<{ gameMode: keyof typeof GAME_MODES }>) => { - const { gameMode } = action.payload; - state.gameMode = gameMode; - }, assignPlayerToTeam: (state: State, action: Action<{ playerId: string }>) => { const PRESET_TEAM_NAMES = ["Red", "Blue", "Green", "Yellow", "Purple", "Orange", "Black", "White", "Silver", "Gold"] as const; // prettier-ignore const { playerId } = action.payload; const existingTeams = objectEntries(state.teams); - if (state.gameMode == null) return raise("No gamemode has been assigned to the game yet"); + if (state.game == null) return raise("No gamemode has been assigned to the game yet"); // Find used team names const usedNames = new Set(existingTeams.map(([, team]) => team.name)); @@ -45,7 +57,7 @@ export const gameSlice = createSlice({ (a, b) => a[1].playerIds.length - b[1].playerIds.length, ); - const maxPlayersPerTeam = GAME_MODES[state.gameMode].maxNumberOfPlayersPerTeam; + const maxPlayersPerTeam = GAME_MODES[state.game.gameMode].maxNumberOfPlayersPerTeam; if (teamsByLeastPlayers.length === 0 || teamsByLeastPlayers[0][1].playerIds.length >= maxPlayersPerTeam) { const newTeamId = crypto.randomUUID(); @@ -77,6 +89,10 @@ export const gameSlice = createSlice({ // Remove the player from the team teamEntry[1].playerIds = teamEntry[1].playerIds.filter((id) => id !== playerId); }, + setupGame: (state: State, action: Action<{ game: GameGameState }>) => { + const { game } = action.payload; + state.game = game; + }, updateCurrentScore: ( state: State, action: Action<{ adjustmentType: "increase" | "decrease"; scoreAmount: number; teamId: string }>, diff --git a/packages/lib/src/engine/slices/player/playerSlice.types.ts b/packages/lib/src/engine/slices/player/playerSlice.types.ts deleted file mode 100644 index d0bf26e..0000000 --- a/packages/lib/src/engine/slices/player/playerSlice.types.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { QuaternionLike, Vector3Like } from "three"; -import type { Weapon } from "../../../objects/weapon"; - -export type PlayerStance = "standing" | "crouching" | "prone"; - -export interface PlayerState { - canRise: boolean; - currentHealth: number; - currentWeapon: Weapon | null; - direction: QuaternionLike; - isAiming: boolean; - isAlive: boolean; - isJumping: boolean; - isReloading: boolean; - isShooting: boolean; - isSprinting: boolean; - isWalking: boolean; - position: Vector3Like; - stance: PlayerStance; - timeSinceLastDamage: number; -} - -export interface PlayerSliceState { - bulletImpacts: Vector3Like[]; // TODO: Move this to another state slice e.g. "world" slice https://app.clickup.com/t/86b5k36rf - players: Record; -} diff --git a/packages/lib/src/engine/slices/player/playerSlice.ts b/packages/lib/src/engine/slices/playerSlice.ts similarity index 88% rename from packages/lib/src/engine/slices/player/playerSlice.ts rename to packages/lib/src/engine/slices/playerSlice.ts index f0de34b..e69ce56 100644 --- a/packages/lib/src/engine/slices/player/playerSlice.ts +++ b/packages/lib/src/engine/slices/playerSlice.ts @@ -1,14 +1,38 @@ import "immer"; import { createSlice } from "@reduxjs/toolkit"; -import { damagePerShot, HitLocation } from "../../../functions/damagePerShot"; +import { damagePerShot, HitLocation } from "../../functions/damagePerShot"; +import { MAP_CONFIGS } from "../../objects/mapConfigs"; import { Quaternion, Vector3 } from "three"; -import { SerializableThree } from "../../../utils/serializableThree"; -import { WeaponActions, weaponSlice } from "../weapon/weaponSlice"; -import type { PlayerSliceState, PlayerState } from "./playerSlice.types"; +import { SerializableThree } from "../../utils/serializableThree"; +import { WeaponActions, weaponSlice } from "./weaponSlice"; import type { PayloadAction } from "@reduxjs/toolkit"; -import type { Vector3Like } from "three"; +import type { QuaternionLike, Vector3Like } from "three"; import type { WritableDraft } from "immer"; -import { MAP_CONFIGS } from "../../../objects/mapConfigs"; +import type { Weapon } from "../../objects/weapon"; + +export type PlayerStance = "standing" | "crouching" | "prone"; + +export interface PlayerState { + canRise: boolean; + currentHealth: number; + currentWeapon: Weapon | null; + direction: QuaternionLike; + isAiming: boolean; + isAlive: boolean; + isJumping: boolean; + isReloading: boolean; + isShooting: boolean; + isSprinting: boolean; + isWalking: boolean; + position: Vector3Like; + stance: PlayerStance; + timeSinceLastDamage: number; +} + +export interface PlayerSliceState { + bulletImpacts: Vector3Like[]; // TODO: Move this to another state slice e.g. "world" slice https://app.clickup.com/t/86b5k36rf + players: Record; +} const initialState: PlayerSliceState = { bulletImpacts: [], diff --git a/packages/lib/src/engine/slices/weapon/weaponSlice.types.ts b/packages/lib/src/engine/slices/weapon/weaponSlice.types.ts deleted file mode 100644 index f5f2a6f..0000000 --- a/packages/lib/src/engine/slices/weapon/weaponSlice.types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { WEAPONS } from "../../../objects/weapon"; -import type { QuaternionLike, Vector3Like } from "three"; - -type WeaponWithPlayer = - | { currentPlayerId: string; isActive: boolean } - | { currentPlayerId: null; direction: QuaternionLike; position: Vector3Like }; // If not with player, gun is on ground somewhere - -export type WeaponState = { - attachments?: string[]; // TODO: Future feature https://app.clickup.com/t/86b5v1tv7 - camo?: string; // TODO: Future feature https://app.clickup.com/t/86b5v1td1 - currentAmmoInMagazine: number; - reserveAmmo: number; - weaponName: keyof typeof WEAPONS; -} & WeaponWithPlayer; - -export interface WeaponSliceState { - weapons: Record; -} diff --git a/packages/lib/src/engine/slices/weapon/weaponSlice.ts b/packages/lib/src/engine/slices/weaponSlice.ts similarity index 80% rename from packages/lib/src/engine/slices/weapon/weaponSlice.ts rename to packages/lib/src/engine/slices/weaponSlice.ts index 12f0bfb..444c4ae 100644 --- a/packages/lib/src/engine/slices/weapon/weaponSlice.ts +++ b/packages/lib/src/engine/slices/weaponSlice.ts @@ -1,12 +1,28 @@ import "immer"; import { createSlice } from "@reduxjs/toolkit"; -import { objectEntries } from "../../../utils/objectUtils"; -import { raise } from "../../../utils/raise"; -import { WEAPONS } from "../../../objects/weapon"; +import { objectEntries } from "../../utils/objectUtils"; +import { raise } from "../../utils/raise"; +import { WEAPONS } from "../../objects/weapon"; import type { PayloadAction } from "@reduxjs/toolkit"; -import type { WeaponSliceState } from "./weaponSlice.types"; +import type { QuaternionLike, Vector3Like } from "three"; import type { WritableDraft } from "immer"; +type WeaponWithPlayer = + | { currentPlayerId: string; isActive: boolean } + | { currentPlayerId: null; direction: QuaternionLike; position: Vector3Like }; // If not with player, gun is on ground somewhere + +export type WeaponState = { + attachments?: string[]; // TODO: Future feature https://app.clickup.com/t/86b5v1tv7 + camo?: string; // TODO: Future feature https://app.clickup.com/t/86b5v1td1 + currentAmmoInMagazine: number; + reserveAmmo: number; + weaponName: keyof typeof WEAPONS; +} & WeaponWithPlayer; + +export interface WeaponSliceState { + weapons: Record; +} + const initialState: WeaponSliceState = { weapons: {}, }; diff --git a/packages/lib/src/engine/store.ts b/packages/lib/src/engine/store.ts index b768f79..65bf0e3 100644 --- a/packages/lib/src/engine/store.ts +++ b/packages/lib/src/engine/store.ts @@ -1,7 +1,7 @@ import { configureStore } from "@reduxjs/toolkit"; -import { gameSlice } from "./slices/game/gameSlice"; -import { playerSlice } from "./slices/player/playerSlice"; -import { weaponSlice } from "./slices/weapon/weaponSlice"; +import { gameSlice } from "./slices/gameSlice"; +import { playerSlice } from "./slices/playerSlice"; +import { weaponSlice } from "./slices/weaponSlice"; type EngineStore = ReturnType; diff --git a/packages/lib/src/engine/thunks/shootEnemyThunk.ts b/packages/lib/src/engine/thunks/shootEnemyThunk.ts index e7f2409..7d83fd5 100644 --- a/packages/lib/src/engine/thunks/shootEnemyThunk.ts +++ b/packages/lib/src/engine/thunks/shootEnemyThunk.ts @@ -1,7 +1,8 @@ -import { GameActions } from "../slices/game/gameSlice"; +import { GameActions } from "../slices/gameSlice"; import { GAME_MODES } from "../../objects/gameModes"; import { objectEntries } from "../../utils/objectUtils"; -import { PlayerActions } from "../slices/player/playerSlice"; +import { PlayerActions } from "../slices/playerSlice"; +import { raise } from "../../utils/raise"; import type { EngineDispatch, EngineState } from "../store"; import type { HitLocation } from "../../functions/damagePerShot"; import type { Vector3Like } from "three"; @@ -30,7 +31,8 @@ export const shootEnemyThunk = const [teamId] = shooterTeam; - const gameMode = state.game.gameMode; + const gameMode = state.game.game?.gameMode; + if (state.game.game == null) raise("Game state is null"); if (gameMode && "kill" in GAME_MODES[gameMode].scoreAdjustments) { dispatch( GameActions.updateCurrentScore({ diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index b20f7c0..05d8277 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -1,22 +1,22 @@ export { assertNever } from "./utils/assertNever"; export { configureEngineStore } from "./engine/store"; -export { GameActions } from "./engine/slices/game/gameSlice"; +export { GameActions } from "./engine/slices/gameSlice"; export { GAME_MODES, MAX_PLAYERS_ALLOWED_IN_GAME } from "./objects/gameModes"; export { getActiveWeaponId, getAllPlayerWeaponIds } from "./utils/getPlayerWeapons"; export { isKey, objectKeys, objectEntries, objectFromEntries, objectValues } from "./utils/objectUtils"; export { MAP_CONFIGS } from "./objects/mapConfigs"; export { Optional } from "./utils/optional"; -export { PlayerActions } from "./engine/slices/player/playerSlice"; +export { PlayerActions } from "./engine/slices/playerSlice"; export { raise } from "./utils/raise"; export { SerializableThree } from "./utils/serializableThree"; export { shootEnemyThunk } from "./engine/thunks/shootEnemyThunk"; export { THUNKS } from "./engine/thunks/thunks"; export { WEAPONS } from "./objects/weapon"; -export { WeaponActions } from "./engine/slices/weapon/weaponSlice"; +export { WeaponActions } from "./engine/slices/weaponSlice"; export type { EngineState, EngineDispatch } from "./engine/store"; export type { FromSerializableThreeRecursive } from "./utils/serializableThree"; -export type { GameSliceState, GameTeamState } from "./engine/slices/game/gameSlice.types"; +export type { GameSliceState, GameTeamState } from "./engine/slices/gameSlice"; export type { MapConfiguration, Spawn } from "./objects/mapConfigs"; -export type { PlayerState, PlayerStance } from "./engine/slices/player/playerSlice.types"; +export type { PlayerState, PlayerStance } from "./engine/slices/playerSlice"; export type { WeaponType, WeaponClass, FiringMode, DamageType, Recoil, WeaponStats, Weapon } from "./objects/weapon"; -export type { WeaponState } from "./engine/slices/weapon/weaponSlice.types"; +export type { WeaponState } from "./engine/slices/weaponSlice"; diff --git a/packages/lib/src/utils/getPlayerWeapons.ts b/packages/lib/src/utils/getPlayerWeapons.ts index bc34259..885a894 100644 --- a/packages/lib/src/utils/getPlayerWeapons.ts +++ b/packages/lib/src/utils/getPlayerWeapons.ts @@ -1,5 +1,5 @@ -import { WeaponState } from "../engine/slices/weapon/weaponSlice.types"; import { objectEntries } from "./objectUtils"; +import type { WeaponState } from "../engine/slices/weaponSlice"; export const getActiveWeaponId = (playerId: string, weapons: Record) => { const activeWeapon = objectEntries(weapons).find(