From e36479e7fbd1e9f9d79f6d7a5ebe63a73eb59047 Mon Sep 17 00:00:00 2001 From: Benjamin Bours Date: Sun, 17 Dec 2023 13:36:23 +0000 Subject: [PATCH] Add basic second level support --- TODO.md | 1 + back/src/socket/socket.gateway.ts | 8 ++- front/app/Game/levels/levels.controller.ts | 5 +- front/app/Menu/Menu.tsx | 2 +- packages/core/lib/GameState.ts | 35 ++++++++-- packages/core/lib/levels/ProjectionLevel.ts | 52 +++++++++++++++ packages/core/lib/levels/index.ts | 1 + packages/core/lib/physics/movementHelpers.ts | 67 ++++++++++++-------- 8 files changed, 134 insertions(+), 37 deletions(-) create mode 100644 packages/core/lib/levels/ProjectionLevel.ts diff --git a/TODO.md b/TODO.md index d8b08629..5497412f 100644 --- a/TODO.md +++ b/TODO.md @@ -36,3 +36,4 @@ ## To fix - manage disconnect event while in game +- Fix issue when you finished first level and then you go with your team mate in the second one diff --git a/back/src/socket/socket.gateway.ts b/back/src/socket/socket.gateway.ts index 63edd97a..5bc8b7df 100644 --- a/back/src/socket/socket.gateway.ts +++ b/back/src/socket/socket.gateway.ts @@ -21,6 +21,7 @@ import { SocketEvent, GameState, PositionLevel, + ProjectionLevel, FLOOR, TimeSyncPayload, PhysicLoop, @@ -249,6 +250,8 @@ export class SocketGateway { switch (players[0].player.selectedLevel) { case Levels.CRACK_THE_DOOR: return new PositionLevel(); + case Levels.LEARN_TO_FLY: + return new ProjectionLevel(); } })(); @@ -300,7 +303,10 @@ export class SocketGateway { this.registerGameLoop(game.id, level); } - registerGameLoop = (gameId: number, level: PositionLevel) => { + registerGameLoop = ( + gameId: number, + level: PositionLevel | ProjectionLevel, + ) => { // TODO: The following variable declared here and accessible in the process // input queue closure are potential memory leaks. // Let's try to declare them only once somewhere else, or to update diff --git a/front/app/Game/levels/levels.controller.ts b/front/app/Game/levels/levels.controller.ts index 5e75a153..ebf2baa2 100644 --- a/front/app/Game/levels/levels.controller.ts +++ b/front/app/Game/levels/levels.controller.ts @@ -1,7 +1,7 @@ // vendors import { Scene, Vector3 } from 'three'; // our libs -import { Levels } from '@benjaminbours/composite-core'; +import { Levels, ProjectionLevel } from '@benjaminbours/composite-core'; // local import { LightPlayer, ShadowPlayer, Player } from '../Player'; import { PositionLevelWithGraphic } from './PositionLevelWithGraphic'; @@ -9,7 +9,7 @@ import { PositionLevelWithGraphic } from './PositionLevelWithGraphic'; export default class LevelController { public levels: { [Levels.CRACK_THE_DOOR]: PositionLevelWithGraphic; - [Levels.LEARN_TO_FLY]?: PositionLevelWithGraphic; + [Levels.LEARN_TO_FLY]: ProjectionLevel; [Levels.THE_HIGH_SPHERES]?: PositionLevelWithGraphic; }; @@ -18,6 +18,7 @@ export default class LevelController { this.levels = { // [Levels.]: new TestLevel(), [Levels.CRACK_THE_DOOR]: new PositionLevelWithGraphic(), + [Levels.LEARN_TO_FLY]: new ProjectionLevel(), }; } diff --git a/front/app/Menu/Menu.tsx b/front/app/Menu/Menu.tsx index dcef27c4..e5343967 100644 --- a/front/app/Menu/Menu.tsx +++ b/front/app/Menu/Menu.tsx @@ -308,7 +308,7 @@ export function Menu({ id: Levels.LEARN_TO_FLY, name: 'Learn to fly', img: '/learn_to_fly.png', - disabled: true, + disabled: false, }, { id: Levels.THE_HIGH_SPHERES, diff --git a/packages/core/lib/GameState.ts b/packages/core/lib/GameState.ts index 80d46541..18b556cf 100644 --- a/packages/core/lib/GameState.ts +++ b/packages/core/lib/GameState.ts @@ -7,21 +7,28 @@ export enum Levels { } interface Level { - doors: { - [key: string]: number[]; - }; end_level: number[]; } export interface PositionLevelState extends Level { + doors: { + [key: string]: number[]; + }; id: Levels.CRACK_THE_DOOR; } +export interface ProjectionLevelState extends Level { + id: Levels.LEARN_TO_FLY; +} + interface OtherLevelState extends Level { id: Levels.LEARN_TO_FLY; } -export type LevelState = PositionLevelState | OtherLevelState; +export type LevelState = + | PositionLevelState + | ProjectionLevelState + | OtherLevelState; // orders of properties are very important here export class RedisGameState { @@ -43,6 +50,22 @@ export class RedisGameState { ) {} static parseGameState(state: GameState) { + // TODO: Remove code duplication, function is copy pasted from apply world update + const isPositionLevel = ( + value: LevelState, + ): value is PositionLevelState => + Boolean((value as PositionLevelState).doors); + + const doors: [string, string] = (() => { + if (isPositionLevel(state.level)) { + return [ + state.level.doors.ground.join(), + state.level.doors.roof.join(), + ]; + } + return ['', '']; + })(); + return new RedisGameState( String(state.level.id), String(state.players[1].position.x), @@ -56,8 +79,7 @@ export class RedisGameState { String(state.lastValidatedInput), String(state.game_time), state.level.end_level.join(), - (state.level as PositionLevelState).doors.ground.join(), - (state.level as PositionLevelState).doors.roof.join(), + ...doors, ); } } @@ -98,7 +120,6 @@ export class GameState { return { id: level, end_level: parseActivators(state.end_level), - doors: {}, }; } })() as LevelState; diff --git a/packages/core/lib/levels/ProjectionLevel.ts b/packages/core/lib/levels/ProjectionLevel.ts new file mode 100644 index 00000000..9d290449 --- /dev/null +++ b/packages/core/lib/levels/ProjectionLevel.ts @@ -0,0 +1,52 @@ +// vendors +import { Group, Object3D, Vector3 } from 'three'; +// local +import { + ElementName, + createArchGroup, + createWall, + createWallDoor, + positionOnGrid, +} from './levels.utils'; +import { InteractiveArea } from '../elements/InteractiveArea'; +import { Levels, ProjectionLevelState } from '../GameState'; + +export class ProjectionLevel extends Group { + public collidingElements: Object3D[] = []; + public interactiveElements: any[] = []; + public name = 'projection-level'; + + public startPosition = { + light: new Vector3(10, 20, 0), // start level + shadow: new Vector3(200, 20, 0), + }; + + public state: ProjectionLevelState = { + id: Levels.LEARN_TO_FLY, + end_level: [], + }; + + constructor() { + super(); + const wallBlockingLeftPath = createWall( + new Vector3(4, 2, 0), + new Vector3(-2, 0, 2), + new Vector3(0, 90, 0), + ); + this.add(wallBlockingLeftPath); + this.collidingElements.push(wallBlockingLeftPath); + + const arches = [ + createArchGroup(1, new Vector3(4, 0, 0)), + createArchGroup(3, new Vector3(5, 0, 0)), + createArchGroup(2, new Vector3(2, 0, 0)), + createArchGroup(3, new Vector3(10, 0, 0)), + ]; + + arches.forEach((arch) => { + this.add(arch); + // TODO: Add only the platform to the list of colliding elements + this.collidingElements.push(arch); + }); + } +} diff --git a/packages/core/lib/levels/index.ts b/packages/core/lib/levels/index.ts index e7a0e23d..aec6c874 100644 --- a/packages/core/lib/levels/index.ts +++ b/packages/core/lib/levels/index.ts @@ -1,2 +1,3 @@ export * from './levels.utils'; export * from './PositionLevel'; +export * from './ProjectionLevel'; diff --git a/packages/core/lib/physics/movementHelpers.ts b/packages/core/lib/physics/movementHelpers.ts index 24a2032e..f9ff3072 100644 --- a/packages/core/lib/physics/movementHelpers.ts +++ b/packages/core/lib/physics/movementHelpers.ts @@ -6,7 +6,12 @@ import { MovableComponentState, Side, } from '../types'; -import { GameState } from '../GameState'; +import { + GameState, + LevelState, + Levels, + PositionLevelState, +} from '../GameState'; import { computeVelocityX } from './velocity'; import { INearestObjects } from './raycaster'; import { AREA_DOOR_OPENER_SUFFIX, ElementName } from '../levels'; @@ -224,36 +229,46 @@ function applyWorldUpdate( collisionResult: INearestObjects, context: Context, ) { - let doorNameActivating: string | undefined = undefined; - if (collisionResult.down && isTouchingDoorOpener(collisionResult.down)) { - const elem = collisionResult.down.object.parent as InteractiveArea; - doorNameActivating = `${elem.name.replace( - `_${AREA_DOOR_OPENER_SUFFIX}`, - '', - )}`; - if (gameState.level.doors[doorNameActivating].indexOf(side) === -1) { - gameState.level.doors[doorNameActivating].push(side); + const isPositionLevel = (value: LevelState): value is PositionLevelState => + Boolean((value as PositionLevelState).doors); + + if (isPositionLevel(gameState.level)) { + let doorNameActivating: string | undefined = undefined; + if ( + collisionResult.down && + isTouchingDoorOpener(collisionResult.down) + ) { + const elem = collisionResult.down.object.parent as InteractiveArea; + doorNameActivating = `${elem.name.replace( + `_${AREA_DOOR_OPENER_SUFFIX}`, + '', + )}`; + if ( + gameState.level.doors[doorNameActivating].indexOf(side) === -1 + ) { + gameState.level.doors[doorNameActivating].push(side); + } } - } - for (const key in gameState.level.doors) { - const activators = gameState.level.doors[key]; + for (const key in gameState.level.doors) { + const activators = gameState.level.doors[key]; - // if this door opener is not the one we are currently activating - // remove us from the list of activators - if (key !== doorNameActivating) { - const index = activators.indexOf(side); - if (index !== -1) { - activators.splice(index, 1); + // if this door opener is not the one we are currently activating + // remove us from the list of activators + if (key !== doorNameActivating) { + const index = activators.indexOf(side); + if (index !== -1) { + activators.splice(index, 1); + } } - } - if (context === 'server') { - const wallDoor = obstacles.find( - (e) => e.name === ElementName.WALL_DOOR(key), - ); - if (wallDoor) { - updateDoor(wallDoor, activators.length > 0); + if (context === 'server') { + const wallDoor = obstacles.find( + (e) => e.name === ElementName.WALL_DOOR(key), + ); + if (wallDoor) { + updateDoor(wallDoor, activators.length > 0); + } } } }