From 5c623e5aa8fb8ec9f65a1f3f9161e6618805dbfc Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 31 Dec 2025 06:24:43 +0000 Subject: [PATCH 01/23] #31 - Restoring semblance of flight scene and tweaking menu options --- client/src/app/routes/hangar.js | 4 +- client/src/app/routes/singleplayer.js | 3 +- .../app/scenograph/objects/vehicles/person.js | 1 - .../scenograph/objects/vehicles/valiant.js | 5 ++- client/src/app/ui/menus/main_menu.js | 40 +++++++++++-------- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/client/src/app/routes/hangar.js b/client/src/app/routes/hangar.js index 9bf37e1f..a87326a0 100644 --- a/client/src/app/routes/hangar.js +++ b/client/src/app/routes/hangar.js @@ -36,9 +36,9 @@ export default class hangarRoute { loadHangar() { l.current_scene.scene.add(l.scenograph.objects.structures.hangar.mesh); - //this.targetStructure.visible = false; + l.scenograph.objects.structures.hangar.mesh.visible = true; - console.log(this.targetStructure.userData.config.hangars[0].position); + l.scenograph.objects.structures.hangar.mesh.position.x = this.targetStructure.userData.config.hangars[0].position.x; l.scenograph.objects.structures.hangar.mesh.position.y = this.targetStructure.userData.config.hangars[0].position.y; l.scenograph.objects.structures.hangar.mesh.position.z = this.targetStructure.userData.config.hangars[0].position.z; diff --git a/client/src/app/routes/singleplayer.js b/client/src/app/routes/singleplayer.js index ef3e383f..e5c2b667 100644 --- a/client/src/app/routes/singleplayer.js +++ b/client/src/app/routes/singleplayer.js @@ -25,7 +25,8 @@ export default class singlePlayerRoute { // Set client mode. l.mode = 'single_player'; - l.scenograph.actors.map.get('Player One').setMode('vehicle'); + l.scenograph.actors.player = l.scenograph.actors.get('Player One'); + l.scenograph.actors.player.setMode('vehicle'); } diff --git a/client/src/app/scenograph/objects/vehicles/person.js b/client/src/app/scenograph/objects/vehicles/person.js index d013233e..8c7f8ff3 100644 --- a/client/src/app/scenograph/objects/vehicles/person.js +++ b/client/src/app/scenograph/objects/vehicles/person.js @@ -178,5 +178,4 @@ export default class Person { } } - } diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 4179b4a2..c1bf0563 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -432,8 +432,7 @@ export default class Valiant { l.scenograph.cameras.player.position.x = xDiff + this.camera_distance * Math.sin( this.mesh.rotation.y ); l.scenograph.cameras.player.position.z = zDiff + this.camera_distance * Math.cos( this.mesh.rotation.y ); - if ( rY != 0 ) { - + if ( rY != 0 && Math.abs(l.scenograph.cameras.player.rotation.y) < .3925 ) { l.scenograph.cameras.player.rotation.y += rY; } else { @@ -506,8 +505,10 @@ export default class Valiant { if ( l.mode != 'hangar') this.updateAnimation( delta ); + // Update the ships state model. let [ rY, tY, tZ ] = this.mesh.userData.object.move( l.current_scene.stats.currentTime - l.current_scene.stats.lastTime ); + this.updateMesh(); this.updateCamera( rY, tY, tZ ); diff --git a/client/src/app/ui/menus/main_menu.js b/client/src/app/ui/menus/main_menu.js index 2b2ab675..c60047fd 100644 --- a/client/src/app/ui/menus/main_menu.js +++ b/client/src/app/ui/menus/main_menu.js @@ -41,7 +41,8 @@ export default class Main_Menu { //l.ui.hide_flight_instruments(); // Show game mode buttons. - this.buttons.single_player.hidden = false; + this.buttons.player_one.hidden = false; + this.buttons.player_two.hidden = false; this.buttons.multi_player.hidden = false; // Hide game exit button to return to main menu. @@ -70,14 +71,15 @@ export default class Main_Menu { l.ui.score_table.show(); }); - this.buttons.test = this.pane.addButton( { - title: 'Test - Hangar', + this.buttons.player_one = this.pane.addButton( { + title: 'P1: Overworld', } ); - this.buttons.test.on( 'click', () => { - new l.routes.hangar(); + this.buttons.player_one.on( 'click', () => { + new l.routes.singlePlayer(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu and change it's title @@ -89,17 +91,18 @@ export default class Main_Menu { // Show game scores button this.buttons.scores.hidden = false; - + } ); - this.buttons.single_player = this.pane.addButton( { - title: 'Single Player', + this.buttons.player_two = this.pane.addButton( { + title: 'P2: Hangar', } ); - this.buttons.single_player.on( 'click', () => { - new l.routes.singlePlayer(); + this.buttons.player_two.on( 'click', () => { + new l.routes.hangar(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu and change it's title @@ -111,7 +114,7 @@ export default class Main_Menu { // Show game scores button this.buttons.scores.hidden = false; - + } ); this.buttons.multi_player = this.pane.addButton( { @@ -122,7 +125,8 @@ export default class Main_Menu { new l.routes.multiPlayer(); // Hide game mode buttons. - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; // Hide main menu @@ -147,7 +151,8 @@ export default class Main_Menu { // Hide all the other buttons. this.buttons.scores.hidden = true; this.buttons.exit_game.hidden = true; - this.buttons.single_player.hidden = true; + this.buttons.player_one.hidden = true; + this.buttons.player_two.hidden = true; this.buttons.multi_player.hidden = true; this.buttons.settings.hidden = true; this.buttons.help.hidden = true; @@ -173,7 +178,8 @@ export default class Main_Menu { // Show all the other buttons. this.buttons.scores.hidden = l.mode !== 'home' ? false : true; this.buttons.exit_game.hidden = l.mode !== 'home' ? false : true; - this.buttons.single_player.hidden = l.mode !== 'home' ? true : false; + this.buttons.player_one.hidden = l.mode !== 'home' ? true : false; + this.buttons.player_two.hidden = l.mode !== 'home' ? true : false; this.buttons.multi_player.hidden = l.mode !== 'home' ? true : false; this.buttons.settings.hidden = false; this.buttons.help.hidden = l.mode !== 'home' ? true : false; @@ -228,4 +234,4 @@ export default class Main_Menu { return this; } -} \ No newline at end of file +} From ca51e728fa3bc4b5f5e35f2f239da17d75cf6d22 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 31 Dec 2025 07:20:08 +0000 Subject: [PATCH 02/23] #31 - Beginning finalisation of hangar scene and features, as well as final refactor --- client/src/app/scenograph/actors/player.js | 2 +- .../app/scenograph/objects/vehicles/raven.js | 10 +- .../scenograph/objects/vehicles/valiant.js | 135 ++++++------- game/src/objects/aircraft/valiant.ts | 15 -- game/src/objects/person.ts | 179 ------------------ .../objects/{aircraft => vehicles}/base.ts | 137 -------------- .../{person2.ts => vehicles/person.ts} | 2 +- .../objects/{aircraft => vehicles}/raven.ts | 0 game/src/objects/vehicles/valiant.ts | 128 +++++++++++++ game/src/world.ts | 7 +- 10 files changed, 209 insertions(+), 406 deletions(-) delete mode 100644 game/src/objects/aircraft/valiant.ts delete mode 100644 game/src/objects/person.ts rename game/src/objects/{aircraft => vehicles}/base.ts (54%) rename game/src/objects/{person2.ts => vehicles/person.ts} (98%) rename game/src/objects/{aircraft => vehicles}/raven.ts (100%) create mode 100644 game/src/objects/vehicles/valiant.ts diff --git a/client/src/app/scenograph/actors/player.js b/client/src/app/scenograph/actors/player.js index f3a8e113..d93c0153 100644 --- a/client/src/app/scenograph/actors/player.js +++ b/client/src/app/scenograph/actors/player.js @@ -53,7 +53,7 @@ export default class Player { async load() { // Setup aircraft, used for the intro sequence. - this.vehicle = new l.scenograph.objects.vehicles.valiant(); + this.vehicle = new l.scenograph.objects.vehicles.valiant(this.actorInstance); await this.vehicle.load(); l.current_scene.scene.add( this.vehicle.mesh diff --git a/client/src/app/scenograph/objects/vehicles/raven.js b/client/src/app/scenograph/objects/vehicles/raven.js index e1d12771..31e0159d 100644 --- a/client/src/app/scenograph/objects/vehicles/raven.js +++ b/client/src/app/scenograph/objects/vehicles/raven.js @@ -1,6 +1,6 @@ /** * Enemy bot. - * + * * Currently hardcoded to use the Raven aircraft. */ import * as THREE from 'three'; @@ -11,7 +11,7 @@ import * as THREE from 'three'; import l from '@/helpers/l.js'; import { brightenMaterial, proceduralMetalMaterial } from '@/scenograph/materials.js'; import PirateActor from '#/game/src/actors/pirate'; -import RavenObject from '#/game/src/objects/aircraft/raven'; +import RavenObject from '#/game/src/objects/vehicles/raven'; export default class Raven { @@ -140,10 +140,10 @@ export default class Raven { /** * Animate hook. - * + * * This method is called within the main animation loop and * therefore must only reference global objects or properties. - * + * * @method animate * @memberof Raven * @global @@ -158,5 +158,5 @@ export default class Raven { } } - + } diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index c1bf0563..9b0e74a8 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -14,7 +14,6 @@ import * as THREE from 'three'; import l from '@/helpers/l.js'; import { brightenMaterial, proceduralMetalMaterial } from '@/scenograph/materials.js'; import Player from '#/game/src/actors/player'; -import ValiantObject from '#/game/src/objects/aircraft/valiant'; export default class Valiant { @@ -45,13 +44,17 @@ export default class Valiant { // TrailRenderer effect showing a trailing effect on the thruster. trail; - constructor() { + constructor(actorInstance) { + // Set internal game accessor to the game world actor instance. + this.game = actorInstance; + this.default_camera_distance = -35; this.trail_position_y = 1.2; this.trail_position_z = 1.5; this.camera_distance = 0; this.ready = false; + console.log(this); } @@ -122,7 +125,6 @@ export default class Valiant { this.trail = l.current_scene.effects.trail.createTrail( this.mesh, 0, this.trail_position_y, this.trail_position_z ); - this.mesh.userData.object = new ValiantObject( this.mesh ); } createThrusterMesh( options ) { @@ -309,9 +311,9 @@ export default class Valiant { // Set the ship as ready. l.current_scene.objects.demoShip.ready = true; l.current_scene.objects.demoShip.camera_distance = l.current_scene.objects.demoShip.default_camera_distance + ( l.current_scene.room_depth / 2 ); - l.current_scene.objects.demoShip.mesh.userData.object.position.x = l.current_scene.objects.demoShip.mesh.position.x; - l.current_scene.objects.demoShip.mesh.userData.object.position.y = l.current_scene.objects.demoShip.mesh.position.y; - l.current_scene.objects.demoShip.mesh.userData.object.position.z = l.current_scene.objects.demoShip.mesh.position.z; + // l.current_scene.objects.demoShip.mesh.userData.object.position.x = l.current_scene.objects.demoShip.mesh.position.x; + // l.current_scene.objects.demoShip.mesh.userData.object.position.y = l.current_scene.objects.demoShip.mesh.position.y; + // l.current_scene.objects.demoShip.mesh.userData.object.position.z = l.current_scene.objects.demoShip.mesh.position.z; } ); } @@ -328,12 +330,12 @@ export default class Valiant { let changing = false; for ( const [ controlName, keyMapping ] of Object.entries( mappings ) ) { if ( l.scenograph.controls.keyboard.pressed( keyMapping ) ) { - this.mesh.userData.object.controls[ controlName ] = true; + this.game.actor.controls[ controlName ] = true; changing = true; } else { - this.mesh.userData.object.controls[ controlName ] = false; + this.game.actor.controls[ controlName ] = false; if ( l.scenograph.controls.touch ) { // Check if any touchpad controls are being pressed @@ -347,29 +349,29 @@ export default class Valiant { ) { changing = true; if ( l.scenograph.controls.touch.controls.moveUp ) { - this.mesh.userData.object.controls.moveUp = true; + this.game.actor.controls.moveUp = true; } if ( l.scenograph.controls.touch.controls.moveDown ) { - this.mesh.userData.object.controls.moveDown = true; + this.game.actor.controls.moveDown = true; } if ( l.scenograph.controls.touch.controls.moveForward ) { - this.mesh.userData.object.controls.throttleUp = true; + this.game.actor.controls.throttleUp = true; } if ( l.scenograph.controls.touch.controls.moveBackward ) { - this.mesh.userData.object.controls.throttleDown = true; + this.game.actor.controls.throttleDown = true; } if ( l.scenograph.controls.touch.controls.moveLeft ) { - this.mesh.userData.object.controls.moveLeft = true; + this.game.actor.controls.moveLeft = true; } if ( l.scenograph.controls.touch.controls.moveRight ) { - this.mesh.userData.object.controls.moveRight = true; + this.game.actor.controls.moveRight = true; } } } } } - this.mesh.userData.object.controls.changing = changing; + this.game.actor.controls.changing = changing; } @@ -378,52 +380,54 @@ export default class Valiant { this.mixer.update( delta ); } - // Rock the ship forward and back when moving horizontally - if ( this.mesh.userData.object.controls.throttleDown || this.mesh.userData.object.controls.throttleUp ) { - let pitchChange = this.mesh.userData.object.controls.throttleUp ? -1 : 1; - if ( Math.abs( this.mesh.rotation.x ) < 1 / 4 ) { - this.mesh.rotation.x += pitchChange / 10 / 180; + if ( this.game ) { + // Rock the ship forward and back when moving horizontally + if ( this.game.actor.controls.throttleDown || this.game.actor.controls.throttleUp ) { + let pitchChange = this.game.actor.controls.throttleUp ? -1 : 1; + if ( Math.abs( this.mesh.rotation.x ) < 1 / 4 ) { + this.mesh.rotation.x += pitchChange / 10 / 180; + } } - } - // Rock the ship forward and back when moving vertically - if ( - this.mesh.userData.object.controls.moveDown - || - this.mesh.userData.object.controls.moveUp - ) { - let elevationChange = this.mesh.userData.object.controls.moveDown ? -1 : 1; - if ( Math.abs( this.mesh.rotation.x ) < 1 / 8 ) { - this.mesh.rotation.x += elevationChange / 10 / 180; - } + // Rock the ship forward and back when moving vertically + if ( + this.game.actor.controls.moveDown + || + this.game.actor.controls.moveUp + ) { + let elevationChange = this.game.actor.controls.moveDown ? -1 : 1; + if ( Math.abs( this.mesh.rotation.x ) < 1 / 8 ) { + this.mesh.rotation.x += elevationChange / 10 / 180; + } - if ( Math.abs( l.scenograph.cameras.player.rotation.x ) < 1 / 8 ) { - let radian = ( Math.PI / 180 ); - l.scenograph.cameras.player.rotation.x += elevationChange * radian / 10; + if ( Math.abs( l.scenograph.cameras.player.rotation.x ) < 1 / 8 ) { + let radian = ( Math.PI / 180 ); + l.scenograph.cameras.player.rotation.x += elevationChange * radian / 10; + } + } + else { + if ( l.scenograph.controls.touch && !l.scenograph.controls.touch.controls.rotationPad.mouseDown ) + l.scenograph.cameras.player.rotation.x *= .9; } - } - else { - if ( l.scenograph.controls.touch && !l.scenograph.controls.touch.controls.rotationPad.mouseDown ) - l.scenograph.cameras.player.rotation.x *= .9; } } // Update the position of the aircraft to spot determined by game logic. - updateMesh() { - this.mesh.position.x = this.mesh.userData.object.position.x; - this.mesh.position.y = this.mesh.userData.object.position.y; - this.mesh.position.z = this.mesh.userData.object.position.z; - this.mesh.rotation.x = this.mesh.userData.object.rotation.x; - this.mesh.rotation.y = this.mesh.userData.object.rotation.y; - this.mesh.rotation.z = this.mesh.userData.object.rotation.z; + sync() { + this.mesh.position.x = this.game.object.position.x; + this.mesh.position.y = this.game.object.position.y; + this.mesh.position.z = this.game.object.position.z; + this.mesh.rotation.x = this.game.object.rotation.x; + this.mesh.rotation.y = this.game.object.rotation.y; + this.mesh.rotation.z = this.game.object.rotation.z; } updateCamera( rY, tY, tZ ) { var radian = ( Math.PI / 180 ); this.camera_distance = this.default_camera_distance + ( l.current_scene.room_depth / 2 ); - if ( this.mesh.userData.object.airSpeed < 0 ) { - this.camera_distance -= this.mesh.userData.object.airSpeed * 4; + if ( this.game.object.airSpeed < 0 ) { + this.camera_distance -= this.game.object.airSpeed * 4; } let xDiff = this.mesh.position.x; @@ -495,7 +499,7 @@ export default class Valiant { } if ( l.scenograph.modes.multiplayer.connected ) { - l.scenograph.modes.multiplayer.socket.emit( 'input', this.mesh.userData.object.controls ); + l.scenograph.modes.multiplayer.socket.emit( 'input', this.game.actor.controls ); } this.mesh.userData.actor.animate( delta ); @@ -505,15 +509,12 @@ export default class Valiant { if ( l.mode != 'hangar') this.updateAnimation( delta ); + if (this.game) { + this.sync(); + this.updateCamera( this.game.object.rY, this.game.object.tY, this.game.object.tZ ); - // Update the ships state model. - let [ rY, tY, tZ ] = this.mesh.userData.object.move( l.current_scene.stats.currentTime - l.current_scene.stats.lastTime ); - - this.updateMesh(); - - this.updateCamera( rY, tY, tZ ); - - this.animateTrail( rY ); + this.animateTrail( this.game.object.rY ); + } } } @@ -525,21 +526,21 @@ export default class Valiant { let trailOffset = 0; // Only offset the trail effect if we are going forward which is (z-1) in numerical terms - if ( this.mesh.userData.object.airSpeed < 0 ) { + if ( this.game.object.airSpeed < 0 ) { // Update ship thruster - this.animateThruster( this.mesh.userData.object.airSpeed, this.thruster.centralConeBurner, .5 ); - this.animateThruster( this.mesh.userData.object.airSpeed, this.thruster.outerCylBurner, .5 ); + this.animateThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, .5 ); + this.animateThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, .5 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.rearConeBurner, -1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.centralConeBurner, 1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.outerCylBurner, -1 ); - this.spinThruster( this.mesh.userData.object.airSpeed, this.thruster.innerCylBurner, 1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.rearConeBurner, -1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, 1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, -1 ); + this.spinThruster( this.game.object.airSpeed, this.thruster.innerCylBurner, 1 ); // Limit playback rate to 5x as large values freak out the browser. - this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.mesh.userData.object.airSpeed ) ); + this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.game.object.airSpeed ) ); - trailOffset += this.trail_position_z - Math.abs( this.mesh.userData.object.airSpeed ); + trailOffset += this.trail_position_z - Math.abs( this.game.object.airSpeed ); this.trail.mesh.material.uniforms.headColor.value.set( 255 / 255, 212 / 255, 148 / 255, .8 ); // RGBA. } @@ -548,11 +549,11 @@ export default class Valiant { } // Update the trail position based on above calculations. - this.trail.targetObject.position.y = this.trail_position_y + this.mesh.userData.object.verticalSpeed; + this.trail.targetObject.position.y = this.trail_position_y + this.game.object.verticalSpeed; this.trail.targetObject.position.z = trailOffset; if ( rY != 0 ) { - this.trail.targetObject.position.x = rY * this.mesh.userData.object.airSpeed; + this.trail.targetObject.position.x = rY * this.game.object.airSpeed; this.trail.targetObject.position.y += Math.abs( this.trail.targetObject.position.x ) / 4; } else { diff --git a/game/src/objects/aircraft/valiant.ts b/game/src/objects/aircraft/valiant.ts deleted file mode 100644 index ad75b337..00000000 --- a/game/src/objects/aircraft/valiant.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Valiant Aircraft, default ship and Kingdom of Winthrom main vehicle - */ - -import BaseAircraft from './base'; - -class Valiant extends BaseAircraft { - - constructor( mesh ) { - super( mesh ); // Call the constructor of the base class - } - -} - -module.exports = Valiant; diff --git a/game/src/objects/person.ts b/game/src/objects/person.ts deleted file mode 100644 index 6e8fc02f..00000000 --- a/game/src/objects/person.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Base Aircraft class - * - * @todo: - * - Add weight and wind resistance - */ - -import { normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../helpers'; - -export default class Person { - public score: { kills: number; deaths: number } = { kills: 0, deaths: 0 }; - public standing: number = 0; - public hitPoints: number = 100; - public airSpeed: number = 0; - public verticalSpeed: number = 0; - public maxForward: number = 16 / 60; // 8 km/h @ 60 FPS - public maxBackward: number = 16 / 60; - public maxUp: number = 4 / 60; - public maxDown: number = 16 / 60; // gravity? - - public position: { x: number; y: number; z: number } = { x: 0, y: 8.5, z: 0 }; - public startPosition: { x: number; y: number; z: number } = { x: 0, y: 8.5, z: 0 }; - public rotation: { x: number; y: number; z: number } = { x: 0, y: 0, z: 0 }; - - public controls: { - changing: boolean, - forward: boolean; - back: boolean; - jump: boolean; - crouch: boolean; - turnLeft: boolean; - turnRight: boolean; - } = { - changing: false, - forward: false, - back: false, - jump: false, - crouch: false, - turnLeft: false, - turnRight: false - }; - - constructor() { - } - - /** - * Change aircraft velocity based on current and what buttons are pushed by the player. - * - * @param currentVelocity - * @param increasePushed - * @param decreasePushed - */ - private _changeVelocity(stepIncrease, stepDecrease, currentVelocity, increasePushed, decreasePushed, increaseMax, decreaseMax, dragFactor): number { - let newVelocity = currentVelocity; - - if (increasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity - stepIncrease; - if (Math.abs(increaseMax) >= Math.abs(tempVelocity)) - newVelocity = tempVelocity; - } - else { - if (decreasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity + stepDecrease; - if (Math.abs(decreaseMax) >= tempVelocity) - newVelocity = tempVelocity; - } - else { - if (newVelocity != 0) { - - if (Math.abs(newVelocity) > 0.1) { - // Ease out the velocity exponentially to simulate drag - newVelocity *= dragFactor; - } - else { - newVelocity = 0; - } - } - - } - } - - return newVelocity; - } - - /** - * Move the aircraft based on velocity, direction and time delta between frames. - * - * @param time_delta - */ - public move( time_delta: number ): object { - let stepSize: number = .025 * normaliseSpeedDelta( time_delta ), - rY: number = 0, - tZ: number = 0, - tY: number = 0, - radian: number = - (Math.PI / 180) * stepSize * 100; - - if ( this.controls.forward || this.controls.back ){ - // Update Airspeed (horizontal velocity) - this.airSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), - stepSize, - this.airSpeed, - this.controls.forward, - this.controls.back, - this.maxForward, - this.maxBackward, - easeOutExpo( 0.987 ) - ); - } - else { - this.airSpeed = 0; - } - - - // Update Vertical Speed (velocity) - this.verticalSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), - this.verticalSpeed, - this.controls.crouch, // Note: Move Down/Up is reversed by design. - this.controls.jump, - this.maxDown, - this.maxUp, - easeInQuad( 0.321 ) - ); - - // Check the vertical speed exceeds minimum threshold for change in vertical position - if (Math.abs(this.verticalSpeed) > 0.01) { - tY = this.verticalSpeed; - } - - // Turning - if (this.controls.turnRight) { - rY += radian; - } - else { - if (this.controls.turnLeft) { - rY -= radian; - } - } - - // Check if we have significant airspeed - if (Math.abs(this.airSpeed) > 0.01) { - - // Set change in Z position based on airspeed - tZ = this.airSpeed; - - } - - if (rY != 0) { - if (Math.abs(this.rotation.z) < Math.PI / 4) { - this.rotation.z += rY / Math.PI; - } - - this.rotation.y += rY; - } - - let xDiff = tZ * Math.sin(this.rotation.y), - zDiff = tZ * Math.cos(this.rotation.y); - - // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. - if (this.position.y + tY >= 1 ) { - this.position.y += tY; - } else { - this.verticalSpeed = 0; - } - - this.position.x += xDiff; - this.position.z += zDiff; - - return [ rY, tY, tZ ]; - - } - -} diff --git a/game/src/objects/aircraft/base.ts b/game/src/objects/vehicles/base.ts similarity index 54% rename from game/src/objects/aircraft/base.ts rename to game/src/objects/vehicles/base.ts index c8e617c3..d85f5786 100644 --- a/game/src/objects/aircraft/base.ts +++ b/game/src/objects/vehicles/base.ts @@ -149,142 +149,5 @@ export default class BaseAircraft { return [ damage, targetDestroyed ]; } - /** - * Change aircraft velocity based on current and what buttons are pushed by the player. - * - * @param currentVelocity - * @param increasePushed - * @param decreasePushed - */ - private _changeVelocity(stepIncrease, stepDecrease, currentVelocity, increasePushed, decreasePushed, increaseMax, decreaseMax, dragFactor): number { - let newVelocity = currentVelocity; - - if (increasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity - stepIncrease; - if (Math.abs(increaseMax) >= Math.abs(tempVelocity)) - newVelocity = tempVelocity; - } - else { - if (decreasePushed) { - - // Check if the change puts us over the max. - let tempVelocity = newVelocity + stepDecrease; - if (Math.abs(decreaseMax) >= tempVelocity) - newVelocity = tempVelocity; - } - else { - if (newVelocity != 0) { - - if (Math.abs(newVelocity) > 0.1) { - // Ease out the velocity exponentially to simulate drag - newVelocity *= dragFactor; - } - else { - newVelocity = 0; - } - } - - } - } - - return newVelocity; - } - - /** - * Move the aircraft based on velocity, direction and time delta between frames. - * - * @param time_delta - */ - public move( time_delta: number ): object { - let stepSize: number = .05 * normaliseSpeedDelta( time_delta ), - rY: number = 0, - tZ: number = 0, - tY: number = 0, - radian: number = (Math.PI / 180); - - // Update Airspeed (horizontal velocity) - this.airSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), - stepSize, - this.airSpeed, - this.controls.throttleUp, - this.controls.throttleDown, - this.maxForward, - this.maxBackward, - easeOutExpo( 0.987 ) - ); - - // Update Vertical Speed (velocity) - this.verticalSpeed = this._changeVelocity( - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), - stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), - this.verticalSpeed, - this.controls.moveDown, // Note: Move Down/Up is reversed by design. - this.controls.moveUp, - this.maxDown, - this.maxUp, - easeInQuad( 0.321 ) - ); - - // Check the vertical speed exceeds minimum threshold for change in vertical position - if (Math.abs(this.verticalSpeed) > 0.01) { - tY = this.verticalSpeed; - } - - // Turning - if (this.controls.moveLeft) { - rY += radian; - } - else { - if (this.controls.moveRight) { - rY -= radian; - } - } - - // Check if we have significant airspeed - if (Math.abs(this.airSpeed) > 0.01) { - - // Set change in Z position based on airspeed - tZ = this.airSpeed; - - } - - // Animate the ship's rotation in the game client based on controls. - if ( - !(this.controls.throttleDown || this.controls.throttleUp) && - !(this.controls.moveDown || this.controls.moveUp) - ) { - this.rotation.x *= .9; - } - - if (rY != 0) { - if (Math.abs(this.rotation.z) < Math.PI / 4) { - this.rotation.z += rY / Math.PI; - } - - this.rotation.y += rY; - } - else { - this.rotation.z *= .9; - } - - let xDiff = tZ * Math.sin(this.rotation.y), - zDiff = tZ * Math.cos(this.rotation.y); - - // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. - if (this.position.y + tY >= 1 ) { - this.position.y += tY; - } else { - this.verticalSpeed = 0; - } - - this.position.x += xDiff; - this.position.z += zDiff; - - return [ rY, tY, tZ ]; - - } } diff --git a/game/src/objects/person2.ts b/game/src/objects/vehicles/person.ts similarity index 98% rename from game/src/objects/person2.ts rename to game/src/objects/vehicles/person.ts index a804f7b0..bb48c53c 100644 --- a/game/src/objects/person2.ts +++ b/game/src/objects/vehicles/person.ts @@ -4,7 +4,7 @@ import ObjectBase from './base'; import BaseActor from '../actors/base2'; -import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../helpers'; +import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../../helpers'; import { Vec3 } from '../types'; diff --git a/game/src/objects/aircraft/raven.ts b/game/src/objects/vehicles/raven.ts similarity index 100% rename from game/src/objects/aircraft/raven.ts rename to game/src/objects/vehicles/raven.ts diff --git a/game/src/objects/vehicles/valiant.ts b/game/src/objects/vehicles/valiant.ts new file mode 100644 index 00000000..8ff652ae --- /dev/null +++ b/game/src/objects/vehicles/valiant.ts @@ -0,0 +1,128 @@ +/** + * Valiant Aircraft, default ship and Kingdom of Winthrom main vehicle + */ + +import ObjectBase from '../base'; +import ActorBase from '../actors/base2'; + +import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../../helpers'; + +class Valiant extends ObjectBase { + + // Actor that controls this object. + public actor?: ActorBase; + + // Object world parameters + public hitPoints: number = 100; + public airSpeed: number = 0; + public verticalSpeed: number = 0; + public maxForward: number = 16 / 60; // 8 km/h @ 60 FPS + public maxBackward: number = 16 / 60; + public maxUp: number = 4 / 60; + public maxDown: number = 16 / 60; // gravity? + + constructor( mesh ) { + super( mesh ); // Call the constructor of the base class + } + + /** + * Move the aircraft based on velocity, direction and time delta between frames. + * + * @param time_delta + */ + public update( time_delta: number ): object { + if (!this.actor) return; + let stepSize: number = .05 * normaliseSpeedDelta( time_delta ), + rY: number = 0, + tZ: number = 0, + tY: number = 0, + radian: number = (Math.PI / 180); + + // Update Airspeed (horizontal velocity) + this.airSpeed = changeVelocity( + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.airSpeed ) / this.maxForward ) ), + stepSize, + this.airSpeed, + this.actor.controls.throttleUp, + this.actor.controls.throttleDown, + this.maxForward, + this.maxBackward, + easeOutExpo( 0.987 ) + ); + + // Update Vertical Speed (velocity) + this.verticalSpeed = changeVelocity( + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxUp ) ), + stepSize * easeInOutExpo( 1 - ( Math.abs ( this.verticalSpeed ) / this.maxDown ) ), + this.verticalSpeed, + this.actor.controls.moveDown, // Note: Move Down/Up is reversed by design. + this.actor.controls.moveUp, + this.maxDown, + this.maxUp, + easeInQuad( 0.321 ) + ); + + // Check the vertical speed exceeds minimum threshold for change in vertical position + if (Math.abs(this.verticalSpeed) > 0.01) { + tY = this.verticalSpeed; + } + + // Turning + if (this.actor.controls.moveLeft) { + rY += radian; + } + else { + if (this.actor.controls.moveRight) { + rY -= radian; + } + } + + // Check if we have significant airspeed + if (Math.abs(this.airSpeed) > 0.01) { + + // Set change in Z position based on airspeed + tZ = this.airSpeed; + + } + + // Animate the ship's rotation in the game client based on controls. + if ( + !(this.actor.controls.throttleDown || this.actor.controls.throttleUp) && + !(this.actor.controls.moveDown || this.actor.controls.moveUp) + ) { + this.rotation.x *= .9; + } + + if (rY != 0) { + if (Math.abs(this.rotation.z) < Math.PI / 4) { + this.rotation.z += rY / Math.PI; + } + + this.rotation.y += rY; + } + else { + this.rotation.z *= .9; + } + + let xDiff = tZ * Math.sin(this.rotation.y), + zDiff = tZ * Math.cos(this.rotation.y); + + // "1" is the floor limit as it's the ocean surface and the camera clips through the water any lower. + if (this.position.y + tY >= 1 ) { + this.position.y += tY; + } else { + this.verticalSpeed = 0; + } + + this.position.x += xDiff; + this.position.z += zDiff; + + this.rY = rY; + this.tY = tY; + this.tZ = tZ; + + } + +} + +module.exports = Valiant; diff --git a/game/src/world.ts b/game/src/world.ts index 3c329cb9..e3e094eb 100644 --- a/game/src/world.ts +++ b/game/src/world.ts @@ -10,7 +10,8 @@ import Overworld from "./scenes/overworld.yml"; import ActorPlayer from './actors/player2'; import ObjectHangar from './objects/structures/hangar'; -import ObjectPerson from './objects/person2'; +import ObjectPerson from './objects/vehicles/person'; +import ObjectValiant from './objects/vehicles/valiant'; interface WorldConfig { actors: Record; @@ -127,7 +128,11 @@ export default class World { else if (config.model == 'hangar') { return new ObjectHangar(config); } + else if (config.model == 'valiant') { + return new ObjectValiant(config); + } else { + console.log('Unknown object model:', config.model); return false; } } From d87a9f7985459a3a062c4d5ee254bebdb7324d1a Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 17 Jan 2026 12:58:29 +0000 Subject: [PATCH 03/23] #31 - Changing vehicles to all use the new base class --- client/src/app/routes/hangar.js | 6 +++--- client/src/app/scenograph/objects/vehicles/valiant.js | 2 +- client/src/app/scenograph/overlays/heads-up-display.js | 4 ++-- game/src/objects/vehicles/person.ts | 2 +- game/src/objects/vehicles/raven.ts | 2 +- game/src/systems/weapons.ts | 8 ++++++-- game/src/world.ts | 1 + 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/client/src/app/routes/hangar.js b/client/src/app/routes/hangar.js index a87326a0..a4849973 100644 --- a/client/src/app/routes/hangar.js +++ b/client/src/app/routes/hangar.js @@ -43,9 +43,9 @@ export default class hangarRoute { l.scenograph.objects.structures.hangar.mesh.position.y = this.targetStructure.userData.config.hangars[0].position.y; l.scenograph.objects.structures.hangar.mesh.position.z = this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.mesh.userData.object.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; + l.scenograph.actors.player.vehicle.game.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; + l.scenograph.actors.player.vehicle.game.object.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; + l.scenograph.actors.player.vehicle.game.object.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; l.scenograph.actors.player.actorInstance.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; l.scenograph.actors.player.actorInstance.object.position.z = 10 + this.targetStructure.userData.config.hangars[0].position.z; diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 9b0e74a8..175bceed 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -491,7 +491,7 @@ export default class Valiant { if ( l.current_scene.objects.demoShip.ready ) { - if ( l.current_scene.settings.game_controls ) { + if ( l.current_scene.settings.game_controls && this.game ) { if ( l.scenograph.actors.player.mode == 'vehicle' ) { // Detect keyboard input and pass it to the ship state model. diff --git a/client/src/app/scenograph/overlays/heads-up-display.js b/client/src/app/scenograph/overlays/heads-up-display.js index 4d282ebc..955f74e4 100644 --- a/client/src/app/scenograph/overlays/heads-up-display.js +++ b/client/src/app/scenograph/overlays/heads-up-display.js @@ -77,10 +77,10 @@ export default class HeadsUpDisplay { l.scenograph.overlays.hud.container.classList.remove('portrait'); } - let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.mesh.userData.object.airSpeed); + let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.airSpeed); l.scenograph.overlays.hud.aspdElement.innerHTML = `AIRSPEED: ${aspd}km/h`; - let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.mesh.userData.object.verticalSpeed); + let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.verticalSpeed); l.scenograph.overlays.hud.vspdElement.innerHTML = `VERT. SPD: ${vspd}km/h`; let heading = THREE.MathUtils.radToDeg( l.scenograph.actors.player.vehicle.mesh.rotation.y ); diff --git a/game/src/objects/vehicles/person.ts b/game/src/objects/vehicles/person.ts index bb48c53c..2108c566 100644 --- a/game/src/objects/vehicles/person.ts +++ b/game/src/objects/vehicles/person.ts @@ -2,7 +2,7 @@ * Person class */ -import ObjectBase from './base'; +import ObjectBase from '../base'; import BaseActor from '../actors/base2'; import { changeVelocity, normaliseSpeedDelta, easeOutExpo, easeInQuad, easeInOutExpo } from '../../helpers'; import { Vec3 } from '../types'; diff --git a/game/src/objects/vehicles/raven.ts b/game/src/objects/vehicles/raven.ts index cb20fe78..be37ea3a 100644 --- a/game/src/objects/vehicles/raven.ts +++ b/game/src/objects/vehicles/raven.ts @@ -2,7 +2,7 @@ * Raven Aircraft, default ship and Los Zaar main vehicle. */ -import BaseAircraft from './base'; +import BaseAircraft from '../base'; class Raven extends BaseAircraft { diff --git a/game/src/systems/weapons.ts b/game/src/systems/weapons.ts index a0af9edc..a28eae99 100644 --- a/game/src/systems/weapons.ts +++ b/game/src/systems/weapons.ts @@ -1,6 +1,6 @@ /** * Weapons Systems - * + * * Defines an interface that fires a vehicles weapons. */ @@ -48,10 +48,14 @@ export default class Weapons extends BaseSystem { target.locked && this.ready() ) { + if (! this.mesh.userData.object ) { + debugger; + } + let negativeStanding = false; // Check if the object has an object class game object and do a standing check. - if ( + if ( target.mesh.userData.hasOwnProperty('object') && target.mesh.userData.object.hasOwnProperty('standing') && target.mesh.userData.object.standing != this.mesh.userData.object.standing diff --git a/game/src/world.ts b/game/src/world.ts index e3e094eb..9fb88493 100644 --- a/game/src/world.ts +++ b/game/src/world.ts @@ -181,6 +181,7 @@ export default class World { } checkHangarCollisions(actorInstance: any) { + // Get object's proposed AABB at next position const actorAABB = actorInstance.object.getAABBNext(); From b961d98c029daddf5d7db2360455350779fa1daa Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 24 Jan 2026 15:11:29 +0000 Subject: [PATCH 04/23] #31 - Adding new ECS oriented overworld config --- game/ecs/scenes/overworld.yml | 166 ++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 game/ecs/scenes/overworld.yml diff --git a/game/ecs/scenes/overworld.yml b/game/ecs/scenes/overworld.yml new file mode 100644 index 00000000..de4b20b3 --- /dev/null +++ b/game/ecs/scenes/overworld.yml @@ -0,0 +1,166 @@ +entities: + - id: player1 + components: + Name: Player One + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: FF0000 + secondaryColor: FFFF00 + object: valiant + PlayerInput: {} + FactionStanding: { union: 0.5, winthrom: 1.0, zaar: 0.0 } + AI: defensive + Health: + current: 100 + max: 100 + + - id: player2 + components: + Name: Player Two + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: 0000FF + secondaryColor: 00FFFF + object: person + PlayerInput: {} + HangarAssigned: + structureId: lambda_city + hangarId: hangar_1 + FactionStanding: { union: 0.5, winthrom: 1.0, zaar: 0.0 } + AI: defensive + Health: + current: 100 + max: 100 + + - id: pirate_1 + components: + Name: Pirate One + Transform: + position: { x: -65000, y: -35000, z: 1500 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + primaryColor: 00FF00 + secondaryColor: FF00FF + object: raven + AI: aggressive + FactionStanding: { union: 0.5, winthrom: 0.0, zaar: 1.0 } + Health: + current: 100 + max: 100 + + - id: cargo_1 + components: + Name: Capy One + Transform: + position: { x: -35000, y: -2000, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + - id: cargo_2 + components: + Name: Capy Two + Transform: + position: { x: -34000, y: -1500, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + - id: cargo_3 + components: + Name: Capy Three + Transform: + position: { x: -36000, y: -1500, z: 10000 } + Renderable: + object: cargoShip + AI: passive + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + Health: + current: 100 + max: 100 + + # Static objects + - id: einstein_well + components: + Name: Einstein Well + Transform: + position: { x: 0, y: -7450, z: -70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: newton_well + components: + Name: Newton Well + Transform: + position: { x: 0, y: -7450, z: 70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: galileo_well + components: + Name: Galileo Well + Transform: + position: { x: -70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: planck_well + components: + Name: Planck Well + Transform: + position: { x: 70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: lambda_city + components: + Name: Lambda City + Transform: + position: { x: -35000, y: 1500, z: -65000 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + object: platform + Hangars: + - id: hangar_1 + name: Hangar 1 + position: { x: 0, y: 6100, z: -8620 } + rotation: { x: 0, y: 0, z: 0 } + - id: hangar_2 + name: Hangar 2 + position: { x: 0, y: 505, z: 0 } + rotation: { x: 0, y: 3.14159, z: 0 } + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_91 + components: + Name: Refinery 91 + Transform: + position: { x: 20000, y: 153, z: -20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_92 + components: + Name: Refinery 92 + Transform: + position: { x: -20000, y: 153, z: 20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } From df5f6f6bfb5d8382ef68e5bcdf6a57a82ff36c8e Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 24 Jan 2026 15:57:23 +0000 Subject: [PATCH 05/23] #31 - Adding start of ECS game engine architecture --- client/src/app/scenograph/director.js | 9 +- game/ecs/components/name.ts | 2 + game/ecs/world.ts | 122 ++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 game/ecs/components/name.ts create mode 100644 game/ecs/world.ts diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index 73a01759..46f81e66 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -17,7 +17,7 @@ import l from '@/helpers/l.js'; /** * World Simulation */ -import World from '#/game/src/world'; +import World from '#/game/ecs/world'; /** * Scene controllers @@ -231,9 +231,12 @@ export default class Director { // Load world instance from game classes. load( sceneName ) { - this.world = new World( sceneName ); + this.world = new World(sceneName); - return this; + console.log(this.world); + debugger; + + return this; } // Load the objects in world instance to the current scene. diff --git a/game/ecs/components/name.ts b/game/ecs/components/name.ts new file mode 100644 index 00000000..fbc467a1 --- /dev/null +++ b/game/ecs/components/name.ts @@ -0,0 +1,2 @@ +// components/Name.ts +export type Name = string; diff --git a/game/ecs/world.ts b/game/ecs/world.ts new file mode 100644 index 00000000..bcf016c1 --- /dev/null +++ b/game/ecs/world.ts @@ -0,0 +1,122 @@ +/** + * Game world simulation + * + * - Loads a scene definition + * - Builds entities using components + * - Advances simulation on a fixed timestep + * + */ + +import Overworld from "./scenes/overworld.yml"; + +import { Name } from "./components/name"; + +interface WorldConfig { + entities: Record; +} + +interface WorldInstance { + entities: Record; +} + +export default class World { + + public config: WorldConfig; + public instance: WorldInstance; + + public fixedDelta: number; + public lastUpdateTime: number; + + private accumulator = 0; + private running = false; + + /** + * Construct the game world instance. + * + * Parses config and builds a self updating virtual world simulation. + * + * @todo: + * - load abstract scene definition file overworld.yml and parse it + * - loop over config to load game world simulation in here and scenograph in the client + */ + constructor( sceneName: string ) { + if ( sceneName === 'Overworld' ) { + this.config = { + entities: Overworld.entities, + } + this.instance = { + entities: new Map(), + }; + + this.lastUpdateTime = performance.now(); + this.fixedDelta = 16; // ~60 FPS for logic + + this.load(); + this.start(); + } + } + + load() { + // Load entities. + for (const entityConfig of this.config.entities.values()) { + const entityInstance: any = { components: {}, config: entityConfig }; + + // Attach each component + for (const [componentName, componentData] of Object.entries(entityConfig.components)) { + switch (componentName) { + case 'Name': + entityInstance.components.Name = componentData; + break; + } + } + + this.instance.entities.set(entityConfig.id, entityInstance); + } + + } + + start() { + this.running = true; + this.lastUpdateTime = performance.now(); + + const loop = () => { + if (!this.running) return; + + const now = performance.now(); + const frameTime = now - this.lastUpdateTime; + this.lastUpdateTime = now; + + // Prevent spiral of death + this.accumulator += Math.min(frameTime, 250); + + while (this.accumulator >= this.fixedDelta) { + this.update(); + this.accumulator -= this.fixedDelta; + } + + setTimeout(loop, 0); + }; + + loop(); + } + + stop() { + this.running = false; + } + + update() { + // for (const actorInstance of this.instance.actors.values()) { + // if (actorInstance.actor) { + // actorInstance.actor.update(this.fixedDelta); + // } + // if (actorInstance.object) { + // actorInstance.object.update(this.fixedDelta); + // if (actorInstance.actor.controls.forward || actorInstance.actor.controls.back) { + // this.checkHangarCollisions(actorInstance); + // } + // } + // } + } + + +} From 1b24a7c6783e51ffd5e5cbea4273c0be6a39158e Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 24 Jan 2026 16:12:22 +0000 Subject: [PATCH 06/23] #31 - Adding Transform component and Vec3 types --- game/ecs/components/transform.ts | 8 ++++++++ game/ecs/types.ts | 11 +++++++++++ game/ecs/world.ts | 13 ++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 game/ecs/components/transform.ts create mode 100644 game/ecs/types.ts diff --git a/game/ecs/components/transform.ts b/game/ecs/components/transform.ts new file mode 100644 index 00000000..9195245f --- /dev/null +++ b/game/ecs/components/transform.ts @@ -0,0 +1,8 @@ +// components/Transform.ts + +import { Vec3 } from "../types"; + +export interface Transform { + position: Vec3 + rotation: Vec3 +} diff --git a/game/ecs/types.ts b/game/ecs/types.ts new file mode 100644 index 00000000..acffdfaa --- /dev/null +++ b/game/ecs/types.ts @@ -0,0 +1,11 @@ +// types.ts + +export interface Vec3 { + x: number + y: number + z: number +} + +export interface AABB { + halfSize: Vec3; +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts index bcf016c1..c9ce9827 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -9,7 +9,12 @@ import Overworld from "./scenes/overworld.yml"; +import { Vec3 } from "./types"; + import { Name } from "./components/name"; +import { Transform } from "./components/Transform"; + + interface WorldConfig { entities: Record; @@ -65,7 +70,13 @@ export default class World { for (const [componentName, componentData] of Object.entries(entityConfig.components)) { switch (componentName) { case 'Name': - entityInstance.components.Name = componentData; + entityInstance.components.Name = componentData as Name; + break; + case 'Transform': + entityInstance.components.Transform = { + position: componentData.position, + rotation: componentData.rotation + } as Transform; break; } } From 38bb0ea48ba1e95e013cba7cf6ea64ed8a86444d Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 31 Jan 2026 05:23:20 +0000 Subject: [PATCH 07/23] #31 - WIP Movement system --- game/ecs/systems/movement.ts | 25 +++++++++++++++++++++++++ game/ecs/world.ts | 15 +++------------ 2 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 game/ecs/systems/movement.ts diff --git a/game/ecs/systems/movement.ts b/game/ecs/systems/movement.ts new file mode 100644 index 00000000..c3dfd135 --- /dev/null +++ b/game/ecs/systems/movement.ts @@ -0,0 +1,25 @@ +import { Transform } from "../components/Transform"; +import { Velocity } from "../components/Velocity"; + +export function movementSystem(entities: Map, deltaMs: number) { + const dt = deltaMs / 1000; // convert ms to seconds + + for (const entity of entities.values()) { + const transform = entity.components.Transform as Transform | undefined; + const velocity = entity.components.Velocity as Velocity | undefined; + + if (transform && velocity) { + // Linear movement + transform.position.x += velocity.linear.x * dt; + transform.position.y += velocity.linear.y * dt; + transform.position.z += velocity.linear.z * dt; + + // Angular movement + if (velocity.angular) { + transform.rotation.x += velocity.angular.x * dt; + transform.rotation.y += velocity.angular.y * dt; + transform.rotation.z += velocity.angular.z * dt; + } + } + } +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts index c9ce9827..4f36e953 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -14,7 +14,7 @@ import { Vec3 } from "./types"; import { Name } from "./components/name"; import { Transform } from "./components/Transform"; - +import { movementSystem } from "./systems/movement"; interface WorldConfig { entities: Record; @@ -116,17 +116,8 @@ export default class World { } update() { - // for (const actorInstance of this.instance.actors.values()) { - // if (actorInstance.actor) { - // actorInstance.actor.update(this.fixedDelta); - // } - // if (actorInstance.object) { - // actorInstance.object.update(this.fixedDelta); - // if (actorInstance.actor.controls.forward || actorInstance.actor.controls.back) { - // this.checkHangarCollisions(actorInstance); - // } - // } - // } + movementSystem(this.instance.entities, this.fixedDelta); + // Later: call other systems here, e.g., AI, collision, rendering } From ddec4cd7e38f9acd0106ae66811cae93e9e678c3 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Mon, 9 Mar 2026 17:27:15 +0000 Subject: [PATCH 08/23] #31 - Implementing object configs and WIP Motion component --- game/ecs/components/motion.ts | 28 +++++++++++++++++++ game/ecs/data/objects/cargoShip.yml | 9 +++++++ game/ecs/data/objects/person.yml | 9 +++++++ game/ecs/data/objects/raven.yml | 9 +++++++ game/ecs/data/objects/valiant.yml | 9 +++++++ game/ecs/{ => data}/scenes/overworld.yml | 6 +++++ game/ecs/world.ts | 34 ++++++++++++++++++++++-- 7 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 game/ecs/components/motion.ts create mode 100644 game/ecs/data/objects/cargoShip.yml create mode 100644 game/ecs/data/objects/person.yml create mode 100644 game/ecs/data/objects/raven.yml create mode 100644 game/ecs/data/objects/valiant.yml rename game/ecs/{ => data}/scenes/overworld.yml (97%) diff --git a/game/ecs/components/motion.ts b/game/ecs/components/motion.ts new file mode 100644 index 00000000..a4aae3ce --- /dev/null +++ b/game/ecs/components/motion.ts @@ -0,0 +1,28 @@ +// components/Motion.ts + +export interface Motion { + // Used by the Movement System to limit changes. + limits: { + acceleration: { + forward: number, + backward: number, + up: number, + down: number + }, + velocity: { + forward: number, + backward: number, + up: number, + down: number + } + + }; + + velocity: { + horizontal: number; // forward/back thrust + vertical: number; // up/down thrust + }; + + altitude: number; // current altitude above sea level in metres. + heading: number; // yaw in radians +} diff --git a/game/ecs/data/objects/cargoShip.yml b/game/ecs/data/objects/cargoShip.yml new file mode 100644 index 00000000..a5373f5e --- /dev/null +++ b/game/ecs/data/objects/cargoShip.yml @@ -0,0 +1,9 @@ +name: Cargo Ship +maxSpeed: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + +primaryColor: FF0000 +secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/person.yml b/game/ecs/data/objects/person.yml new file mode 100644 index 00000000..7b51fbb5 --- /dev/null +++ b/game/ecs/data/objects/person.yml @@ -0,0 +1,9 @@ +name: Person +maxSpeed: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + +primaryColor: FF0000 +secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/raven.yml b/game/ecs/data/objects/raven.yml new file mode 100644 index 00000000..c786d040 --- /dev/null +++ b/game/ecs/data/objects/raven.yml @@ -0,0 +1,9 @@ +name: Raven +maxSpeed: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + +primaryColor: FF0000 +secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/valiant.yml b/game/ecs/data/objects/valiant.yml new file mode 100644 index 00000000..78781ab1 --- /dev/null +++ b/game/ecs/data/objects/valiant.yml @@ -0,0 +1,9 @@ +name: Valiant +maxSpeed: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + +primaryColor: FF0000 +secondaryColor: FFFF00 diff --git a/game/ecs/scenes/overworld.yml b/game/ecs/data/scenes/overworld.yml similarity index 97% rename from game/ecs/scenes/overworld.yml rename to game/ecs/data/scenes/overworld.yml index de4b20b3..0815141c 100644 --- a/game/ecs/scenes/overworld.yml +++ b/game/ecs/data/scenes/overworld.yml @@ -5,6 +5,7 @@ entities: Transform: position: { x: -65000, y: -35000, z: 1500 } rotation: { x: 0, y: -12.56, z: 0 } + Movable: true Renderable: primaryColor: FF0000 secondaryColor: FFFF00 @@ -22,6 +23,7 @@ entities: Transform: position: { x: -65000, y: -35000, z: 1500 } rotation: { x: 0, y: -12.56, z: 0 } + Movable: true Renderable: primaryColor: 0000FF secondaryColor: 00FFFF @@ -42,6 +44,7 @@ entities: Transform: position: { x: -65000, y: -35000, z: 1500 } rotation: { x: 0, y: -12.56, z: 0 } + Movable: true Renderable: primaryColor: 00FF00 secondaryColor: FF00FF @@ -57,6 +60,7 @@ entities: Name: Capy One Transform: position: { x: -35000, y: -2000, z: 10000 } + Movable: true Renderable: object: cargoShip AI: passive @@ -70,6 +74,7 @@ entities: Name: Capy Two Transform: position: { x: -34000, y: -1500, z: 10000 } + Movable: true Renderable: object: cargoShip AI: passive @@ -83,6 +88,7 @@ entities: Name: Capy Three Transform: position: { x: -36000, y: -1500, z: 10000 } + Movable: true Renderable: object: cargoShip AI: passive diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 4f36e953..6760a485 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -7,12 +7,17 @@ * */ -import Overworld from "./scenes/overworld.yml"; -import { Vec3 } from "./types"; +// Import YAML configs. +import CargoShip from "./data/objects/cargoShip.yml"; +import Person from "./data/objects/person.yml"; +import Raven from "./data/objects/raven.yml"; +import Valiant from "./data/objects/valiant.yml"; +import Overworld from "./data/scenes/overworld.yml"; import { Name } from "./components/name"; import { Transform } from "./components/Transform"; +import { Motion } from "./components/Motion"; import { movementSystem } from "./systems/movement"; @@ -78,6 +83,31 @@ export default class World { rotation: componentData.rotation } as Transform; break; + case 'Movable': + entityInstance.components.Motion = { + velocity: { + horizontal: 0, + vertical: 0 + }, + altitude: 0, + heading: 0 + } as Motion; + break; + case 'Renderable': + // Load object specific settings from config. + if (componentData.object === 'cargoShip') { + entityInstance.maxSpeed = CargoShip.maxSpeed; + } + if (componentData.object === 'person') { + entityInstance.maxSpeed = Person.maxSpeed; + } + if (componentData.object === 'raven') { + entityInstance.maxSpeed = Raven.maxSpeed; + } + if (componentData.object === 'valiant') { + entityInstance.maxSpeed = Valiant.maxSpeed; + } + break; } } From dc5dfb701dc8a9a8d46748364cf4cb10ad450fed Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Mon, 9 Mar 2026 17:38:34 +0000 Subject: [PATCH 09/23] #31 - Flattening entity structure --- game/ecs/world.ts | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 6760a485..fbe19331 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -21,18 +21,10 @@ import { Motion } from "./components/Motion"; import { movementSystem } from "./systems/movement"; -interface WorldConfig { - entities: Record; -} - -interface WorldInstance { - entities: Record; -} - export default class World { - public config: WorldConfig; - public instance: WorldInstance; + public configs: Record; + public instances: Record; public fixedDelta: number; public lastUpdateTime: number; @@ -51,12 +43,8 @@ export default class World { */ constructor( sceneName: string ) { if ( sceneName === 'Overworld' ) { - this.config = { - entities: Overworld.entities, - } - this.instance = { - entities: new Map(), - }; + this.configs = Overworld.entities; + this.instances = new Map(); this.lastUpdateTime = performance.now(); this.fixedDelta = 16; // ~60 FPS for logic @@ -68,7 +56,7 @@ export default class World { load() { // Load entities. - for (const entityConfig of this.config.entities.values()) { + for (const entityConfig of this.configs.values()) { const entityInstance: any = { components: {}, config: entityConfig }; // Attach each component @@ -111,7 +99,7 @@ export default class World { } } - this.instance.entities.set(entityConfig.id, entityInstance); + this.instances.set(entityConfig.id, entityInstance); } } @@ -146,7 +134,7 @@ export default class World { } update() { - movementSystem(this.instance.entities, this.fixedDelta); + movementSystem(this.instances, this.fixedDelta); // Later: call other systems here, e.g., AI, collision, rendering } From 94abf5ea9cbff1ef0fde1bfd56b786c2f64e4756 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Mon, 9 Mar 2026 18:01:04 +0000 Subject: [PATCH 10/23] #31 - Renaming max speed to limits --- client/src/app/scenograph/director.js | 10 +++----- game/ecs/data/objects/cargoShip.yml | 16 ++++++++---- game/ecs/data/objects/person.yml | 16 ++++++++---- game/ecs/data/objects/raven.yml | 16 ++++++++---- game/ecs/data/objects/valiant.yml | 16 ++++++++---- game/ecs/world.ts | 36 +++++++++++++-------------- 6 files changed, 65 insertions(+), 45 deletions(-) diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index 46f81e66..3e877cf0 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -233,9 +233,6 @@ export default class Director { load( sceneName ) { this.world = new World(sceneName); - console.log(this.world); - debugger; - return this; } @@ -249,8 +246,9 @@ export default class Director { } async loadInstance() { - - await this.world.config.objects.forEach( async object_config => { + console.log(this.world); + debugger; + await this.world.entities.forEach( async object_config => { if ( object_config.model == 'extractor' ) { l.scenograph.director.loadObject( object_config, @@ -269,9 +267,7 @@ export default class Director { await l.scenograph.objects.structures.refinery.get() ); } - } ); - this.world.config.actors.forEach( async object_config => { if ( object_config.class == 'cargoShip' ) { l.scenograph.director.loadObject( object_config, diff --git a/game/ecs/data/objects/cargoShip.yml b/game/ecs/data/objects/cargoShip.yml index a5373f5e..8e212617 100644 --- a/game/ecs/data/objects/cargoShip.yml +++ b/game/ecs/data/objects/cargoShip.yml @@ -1,9 +1,15 @@ name: Cargo Ship -maxSpeed: - forward: 18.5 - backward: 2.0 - up: 9.25 - down: 18.5 +limits: + acceleration: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + velocity: + forward: 600 + backward: 300 + up: 150 + down: 600 primaryColor: FF0000 secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/person.yml b/game/ecs/data/objects/person.yml index 7b51fbb5..8dd0a4ac 100644 --- a/game/ecs/data/objects/person.yml +++ b/game/ecs/data/objects/person.yml @@ -1,9 +1,15 @@ name: Person -maxSpeed: - forward: 18.5 - backward: 2.0 - up: 9.25 - down: 18.5 +limits: + acceleration: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + velocity: + forward: 600 + backward: 300 + up: 150 + down: 600 primaryColor: FF0000 secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/raven.yml b/game/ecs/data/objects/raven.yml index c786d040..996c392c 100644 --- a/game/ecs/data/objects/raven.yml +++ b/game/ecs/data/objects/raven.yml @@ -1,9 +1,15 @@ name: Raven -maxSpeed: - forward: 18.5 - backward: 2.0 - up: 9.25 - down: 18.5 +limits: + acceleration: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + velocity: + forward: 600 + backward: 300 + up: 150 + down: 600 primaryColor: FF0000 secondaryColor: FFFF00 diff --git a/game/ecs/data/objects/valiant.yml b/game/ecs/data/objects/valiant.yml index 78781ab1..70c7fc88 100644 --- a/game/ecs/data/objects/valiant.yml +++ b/game/ecs/data/objects/valiant.yml @@ -1,9 +1,15 @@ name: Valiant -maxSpeed: - forward: 18.5 - backward: 2.0 - up: 9.25 - down: 18.5 +limits: + acceleration: + forward: 18.5 + backward: 2.0 + up: 9.25 + down: 18.5 + velocity: + forward: 600 + backward: 300 + up: 150 + down: 600 primaryColor: FF0000 secondaryColor: FFFF00 diff --git a/game/ecs/world.ts b/game/ecs/world.ts index fbe19331..c84a56e8 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -24,7 +24,7 @@ import { movementSystem } from "./systems/movement"; export default class World { public configs: Record; - public instances: Record; + public entities: Record; public fixedDelta: number; public lastUpdateTime: number; @@ -44,7 +44,7 @@ export default class World { constructor( sceneName: string ) { if ( sceneName === 'Overworld' ) { this.configs = Overworld.entities; - this.instances = new Map(); + this.entities = new Map(); this.lastUpdateTime = performance.now(); this.fixedDelta = 16; // ~60 FPS for logic @@ -80,26 +80,26 @@ export default class World { altitude: 0, heading: 0 } as Motion; - break; - case 'Renderable': - // Load object specific settings from config. - if (componentData.object === 'cargoShip') { - entityInstance.maxSpeed = CargoShip.maxSpeed; - } - if (componentData.object === 'person') { - entityInstance.maxSpeed = Person.maxSpeed; - } - if (componentData.object === 'raven') { - entityInstance.maxSpeed = Raven.maxSpeed; - } - if (componentData.object === 'valiant') { - entityInstance.maxSpeed = Valiant.maxSpeed; + if (entityConfig.components.Renderable && entityConfig.components.Renderable.object) { + // Load object specific settings from config. + if (entityConfig.components.Renderable.object === 'cargoShip') { + entityInstance.components.Motion.limits = CargoShip.limits; + } + if (entityConfig.components.Renderable.object === 'person') { + entityInstance.components.Motion.limits = Person.limits; + } + if (entityConfig.components.Renderable.object === 'raven') { + entityInstance.components.Motion.limits = Raven.limits; + } + if (entityConfig.components.Renderable.object === 'valiant') { + entityInstance.components.Motion.limits = Valiant.limits; + } } break; } } - this.instances.set(entityConfig.id, entityInstance); + this.entities.set(entityConfig.id, entityInstance); } } @@ -134,7 +134,7 @@ export default class World { } update() { - movementSystem(this.instances, this.fixedDelta); + movementSystem(this.entities, this.fixedDelta); // Later: call other systems here, e.g., AI, collision, rendering } From 6271727529987b075984f25c6c1e849162fb9e6b Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 11 Mar 2026 18:44:49 +0000 Subject: [PATCH 11/23] #31 - Updating Director loadObject and setting default transforms --- client/src/app/scenograph/director.js | 56 +++++++++++++-------------- game/ecs/world.ts | 6 ++- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index 3e877cf0..f1a16668 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -247,68 +247,66 @@ export default class Director { async loadInstance() { console.log(this.world); - debugger; - await this.world.entities.forEach( async object_config => { - if ( object_config.model == 'extractor' ) { + await this.world.entities.forEach(async entity => { + + if ( entity.config.components.Renderable.object == 'extractor' ) { l.scenograph.director.loadObject( - object_config, + entity, await l.scenograph.objects.structures.extractor.get() ); } - if ( object_config.model == 'platform' ) { + if ( entity.config.components.Renderable.model == 'platform' ) { l.scenograph.director.loadObject( - object_config, + entity, await l.scenograph.objects.structures.platform.get() ); } - if ( object_config.model == 'refinery' ) { + if ( entity.config.components.Renderable.model == 'refinery' ) { l.scenograph.director.loadObject( - object_config, + entity, await l.scenograph.objects.structures.refinery.get() ); } - if ( object_config.class == 'cargoShip' ) { + if ( entity.config.components.Renderable.class == 'cargoShip' ) { l.scenograph.director.loadObject( - object_config, + entity, await l.scenograph.objects.vehicles.cargoShip.get() ); } - if ( object_config.class == 'pirate' ) { + if ( entity.config.components.Renderable.class == 'pirate' ) { l.scenograph.director.loadObject( - object_config, + entity, await l.scenograph.objects.vehicles.raven.get() ); } - } ); - - this.world.instance.actors.forEach(async actorInstance => { - if ( actorInstance.config.class == 'player' ) { - await l.scenograph.actors.registerActor( actorInstance ); + if ( entity.config.components.Renderable.class == 'player' ) { + await l.scenograph.actors.registerActor( entity ); } }); } - async loadObject( config, object ) { - object.position.x = config.position.x; - object.position.y = config.position.y; - object.position.z = config.position.z; + async loadObject(entity, scenographObject) { + console.log(entity, scenographObject); + scenographObject.position.x = entity.components.Transform.position.x; + scenographObject.position.y = entity.components.Transform.position.y; + scenographObject.position.z = entity.components.Transform.position.z; - if ( config.rotation ) { - object.rotation.x = config.rotation.x; - object.rotation.y = config.rotation.y; - object.rotation.z = config.rotation.z; + if ( entity.rotation ) { + scenographObject.rotation.x = entity.components.Transform.rotation.x; + scenographObject.rotation.y = entity.components.Transform.rotation.y; + scenographObject.rotation.z = entity.components.Transform.rotation.z; } - object.name = config.name; - object.userData.config = config; + scenographObject.name = entity.components.Name; + scenographObject.userData.entity = entity; - // @todo: add to current_scene array relevant to object class. + // @todo: add to current_scene array relevant to scenographObject class. l.current_scene.scene.add( - object + scenographObject ); } diff --git a/game/ecs/world.ts b/game/ecs/world.ts index c84a56e8..8b741454 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -15,6 +15,8 @@ import Raven from "./data/objects/raven.yml"; import Valiant from "./data/objects/valiant.yml"; import Overworld from "./data/scenes/overworld.yml"; +import { Vec3 } from "./types"; + import { Name } from "./components/name"; import { Transform } from "./components/Transform"; import { Motion } from "./components/Motion"; @@ -67,8 +69,8 @@ export default class World { break; case 'Transform': entityInstance.components.Transform = { - position: componentData.position, - rotation: componentData.rotation + position: componentData.position ? componentData.position : {x: 0, y: 0, z: 0} as Vec3, + rotation: componentData.rotation ? componentData.rotation : {x: 0, y: 0, z: 0} as Vec3 } as Transform; break; case 'Movable': From 7e9c5e4e9c2d01d8318635491ed23fc6e427284e Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Fri, 13 Mar 2026 14:54:24 +0000 Subject: [PATCH 12/23] #31 - Flipping overworld scene load order and director tweaks --- client/src/app/scenograph/director.js | 10 +- .../scenograph/objects/vehicles/cargo_ship.js | 27 ++-- game/ecs/data/scenes/overworld.yml | 150 +++++++++--------- 3 files changed, 96 insertions(+), 91 deletions(-) diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index f1a16668..b48e7e70 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -255,33 +255,33 @@ export default class Director { await l.scenograph.objects.structures.extractor.get() ); } - if ( entity.config.components.Renderable.model == 'platform' ) { + if ( entity.config.components.Renderable.object == 'platform' ) { l.scenograph.director.loadObject( entity, await l.scenograph.objects.structures.platform.get() ); } - if ( entity.config.components.Renderable.model == 'refinery' ) { + if ( entity.config.components.Renderable.object == 'refinery' ) { l.scenograph.director.loadObject( entity, await l.scenograph.objects.structures.refinery.get() ); } - if ( entity.config.components.Renderable.class == 'cargoShip' ) { + if ( entity.config.components.Renderable.object == 'cargoShip' ) { l.scenograph.director.loadObject( entity, await l.scenograph.objects.vehicles.cargoShip.get() ); } - if ( entity.config.components.Renderable.class == 'pirate' ) { + if ( entity.config.components.Renderable.object == 'pirate' ) { l.scenograph.director.loadObject( entity, await l.scenograph.objects.vehicles.raven.get() ); } - if ( entity.config.components.Renderable.class == 'player' ) { + if ( entity.config.components.Renderable.object == 'valiant' || entity.config.components.Renderable.object == 'person' ) { await l.scenograph.actors.registerActor( entity ); } }); diff --git a/client/src/app/scenograph/objects/vehicles/cargo_ship.js b/client/src/app/scenograph/objects/vehicles/cargo_ship.js index e2bcaf4c..2f38421e 100644 --- a/client/src/app/scenograph/objects/vehicles/cargo_ship.js +++ b/client/src/app/scenograph/objects/vehicles/cargo_ship.js @@ -41,7 +41,7 @@ export default class CargoShip { /** * Paths - * + * * @todo: Refactor into a common path setting and updating class. */ getPath() { @@ -62,7 +62,7 @@ export default class CargoShip { } async getAll() { - + let path = this.getPath(); const path_points = []; @@ -90,10 +90,10 @@ export default class CargoShip { await this.load(); this.locations.forEach( async ( location, i ) => { - + let mesh = this.mesh.clone(); mesh.userData.path = this.getPath(); - + // Bump each starting point for the cargo ships for ( let j = 0; j < i; j++) { mesh.userData.path.advance(); @@ -121,15 +121,18 @@ export default class CargoShip { mesh.userData.path = this.getPath(); let i = this.instances.length; - + // Bump each starting point for the cargo ships for ( let j = 0; j < i; j++) { mesh.userData.path.advance(); } - mesh.position.copy( mesh.userData.path.current() ); mesh.name = 'Cargo Ship #' + ( i + 1 ); + console.log(mesh.name, mesh.position, mesh.userData); + mesh.position.copy( mesh.userData.path.current() ); + + mesh.userData.objectClass = 'cargoShip'; mesh.userData.targetable = true; mesh.userData.size = this.size; @@ -139,7 +142,7 @@ export default class CargoShip { mesh.matrixAutoUpdate = false; - this.instances.push( mesh ); + this.instances.push(mesh); return mesh; } @@ -202,10 +205,10 @@ export default class CargoShip { /** * Animate hook. - * + * * This method is called within the main animation loop and * therefore must only reference global objects or properties. - * + * * @method animate * @memberof CargoShips * @global @@ -217,10 +220,10 @@ export default class CargoShip { l.scenograph.objects.vehicles.cargoShip.instances.forEach( ( cargo_ship ) => { cargo_ship.userData.actor.animate( delta ); - + } ); } - + } - + } diff --git a/game/ecs/data/scenes/overworld.yml b/game/ecs/data/scenes/overworld.yml index 0815141c..20e40e70 100644 --- a/game/ecs/data/scenes/overworld.yml +++ b/game/ecs/data/scenes/overworld.yml @@ -1,4 +1,80 @@ entities: + + # Static objects + - id: einstein_well + components: + Name: Einstein Well + Transform: + position: { x: 0, y: -7450, z: -70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: newton_well + components: + Name: Newton Well + Transform: + position: { x: 0, y: -7450, z: 70000 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: galileo_well + components: + Name: Galileo Well + Transform: + position: { x: -70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: planck_well + components: + Name: Planck Well + Transform: + position: { x: 70000, y: -7450, z: 0 } + Renderable: + object: extractor + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: lambda_city + components: + Name: Lambda City + Transform: + position: { x: -35000, y: 1500, z: -65000 } + rotation: { x: 0, y: -12.56, z: 0 } + Renderable: + object: platform + Hangars: + - id: hangar_1 + name: Hangar 1 + position: { x: 0, y: 6100, z: -8620 } + rotation: { x: 0, y: 0, z: 0 } + - id: hangar_2 + name: Hangar 2 + position: { x: 0, y: 505, z: 0 } + rotation: { x: 0, y: 3.14159, z: 0 } + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_91 + components: + Name: Refinery 91 + Transform: + position: { x: 20000, y: 153, z: -20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + - id: refinery_92 + components: + Name: Refinery 92 + Transform: + position: { x: -20000, y: 153, z: 20000 } + Renderable: + object: refinery + FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } + + # Vehicles - id: player1 components: Name: Player One @@ -96,77 +172,3 @@ entities: Health: current: 100 max: 100 - - # Static objects - - id: einstein_well - components: - Name: Einstein Well - Transform: - position: { x: 0, y: -7450, z: -70000 } - Renderable: - object: extractor - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: newton_well - components: - Name: Newton Well - Transform: - position: { x: 0, y: -7450, z: 70000 } - Renderable: - object: extractor - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: galileo_well - components: - Name: Galileo Well - Transform: - position: { x: -70000, y: -7450, z: 0 } - Renderable: - object: extractor - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: planck_well - components: - Name: Planck Well - Transform: - position: { x: 70000, y: -7450, z: 0 } - Renderable: - object: extractor - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: lambda_city - components: - Name: Lambda City - Transform: - position: { x: -35000, y: 1500, z: -65000 } - rotation: { x: 0, y: -12.56, z: 0 } - Renderable: - object: platform - Hangars: - - id: hangar_1 - name: Hangar 1 - position: { x: 0, y: 6100, z: -8620 } - rotation: { x: 0, y: 0, z: 0 } - - id: hangar_2 - name: Hangar 2 - position: { x: 0, y: 505, z: 0 } - rotation: { x: 0, y: 3.14159, z: 0 } - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: refinery_91 - components: - Name: Refinery 91 - Transform: - position: { x: 20000, y: 153, z: -20000 } - Renderable: - object: refinery - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } - - - id: refinery_92 - components: - Name: Refinery 92 - Transform: - position: { x: -20000, y: 153, z: 20000 } - Renderable: - object: refinery - FactionStanding: { union: 1.0, winthrom: 0.5, zaar: 0.0 } From b82551994e6e0ace4d829d0042d49a9a43098cf7 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Thu, 9 Apr 2026 20:36:26 +0000 Subject: [PATCH 13/23] entity --- client/src/app/scenograph/actors.js | 8 +-- client/src/app/scenograph/actors/player.js | 8 +-- client/src/app/scenograph/director.js | 2 +- .../app/scenograph/objects/vehicles/person.js | 4 +- .../scenograph/objects/vehicles/valiant.js | 55 ++++++++++--------- game/ecs/components/playerInput.ts | 11 ++++ game/ecs/world.ts | 19 ++++++- 7 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 game/ecs/components/playerInput.ts diff --git a/client/src/app/scenograph/actors.js b/client/src/app/scenograph/actors.js index 20bca219..2f09ea7b 100644 --- a/client/src/app/scenograph/actors.js +++ b/client/src/app/scenograph/actors.js @@ -23,11 +23,11 @@ export default class Actors { this.map = new Map(); } - async registerActor(actorInstance) { - if ( actorInstance.config.class == 'player' ) { - let player = new Player( actorInstance ); + async registerActor(actorEntity) { + if (actorEntity.config.components.PlayerInput) { + let player = new Player( actorEntity ); await player.load(); - this.map.set(actorInstance.config.name, player); + this.map.set(actorEntity.components.Name, player); } } diff --git a/client/src/app/scenograph/actors/player.js b/client/src/app/scenograph/actors/player.js index d93c0153..15cff105 100644 --- a/client/src/app/scenograph/actors/player.js +++ b/client/src/app/scenograph/actors/player.js @@ -28,9 +28,9 @@ export default class Player { // Vehicle model. vehicle; - constructor( actorInstance ) { + constructor( actorEntity ) { - this.actorInstance = actorInstance; + this.actorEntity = actorEntity; this.ready = false; @@ -53,7 +53,7 @@ export default class Player { async load() { // Setup aircraft, used for the intro sequence. - this.vehicle = new l.scenograph.objects.vehicles.valiant(this.actorInstance); + this.vehicle = new l.scenograph.objects.vehicles.valiant(this.actorEntity); await this.vehicle.load(); l.current_scene.scene.add( this.vehicle.mesh @@ -63,7 +63,7 @@ export default class Player { ); // Setup person, used for the hangar scene. - this.person = new l.scenograph.objects.vehicles.person(this.actorInstance); + this.person = new l.scenograph.objects.vehicles.person(this.actorEntity); l.current_scene.scene.add( this.person.mesh ); diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index b48e7e70..6eeada70 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -281,7 +281,7 @@ export default class Director { ); } - if ( entity.config.components.Renderable.object == 'valiant' || entity.config.components.Renderable.object == 'person' ) { + if (entity.config.components.Renderable.object == 'valiant' || entity.config.components.Renderable.object == 'person') { await l.scenograph.actors.registerActor( entity ); } }); diff --git a/client/src/app/scenograph/objects/vehicles/person.js b/client/src/app/scenograph/objects/vehicles/person.js index 8c7f8ff3..6a8a09f0 100644 --- a/client/src/app/scenograph/objects/vehicles/person.js +++ b/client/src/app/scenograph/objects/vehicles/person.js @@ -14,9 +14,9 @@ import l from '@/helpers/l.js'; export default class Person { - constructor(actorInstance) { + constructor(actorEntity) { // Set internal game accessor to the game world actor instance. - this.game = actorInstance; + this.game = actorEntity; this.default_camera_distance = l.scenograph.width < l.scenograph.height ? -5 : -2.5; diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 175bceed..0475391c 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -44,9 +44,12 @@ export default class Valiant { // TrailRenderer effect showing a trailing effect on the thruster. trail; - constructor(actorInstance) { - // Set internal game accessor to the game world actor instance. - this.game = actorInstance; + constructor(actorEntity) { + + if (actorEntity) { + // Set internal game accessor to the game world actor entity. + this.game = actorEntity; + } this.default_camera_distance = -35; this.trail_position_y = 1.2; @@ -330,12 +333,12 @@ export default class Valiant { let changing = false; for ( const [ controlName, keyMapping ] of Object.entries( mappings ) ) { if ( l.scenograph.controls.keyboard.pressed( keyMapping ) ) { - this.game.actor.controls[ controlName ] = true; + this.game.components.PlayerInput[ controlName ] = true; changing = true; } else { - this.game.actor.controls[ controlName ] = false; + this.game.components.PlayerInput[ controlName ] = false; if ( l.scenograph.controls.touch ) { // Check if any touchpad controls are being pressed @@ -349,29 +352,29 @@ export default class Valiant { ) { changing = true; if ( l.scenograph.controls.touch.controls.moveUp ) { - this.game.actor.controls.moveUp = true; + this.game.components.PlayerInput.moveUp = true; } if ( l.scenograph.controls.touch.controls.moveDown ) { - this.game.actor.controls.moveDown = true; + this.game.components.PlayerInput.moveDown = true; } if ( l.scenograph.controls.touch.controls.moveForward ) { - this.game.actor.controls.throttleUp = true; + this.game.components.PlayerInput.throttleUp = true; } if ( l.scenograph.controls.touch.controls.moveBackward ) { - this.game.actor.controls.throttleDown = true; + this.game.components.PlayerInput.throttleDown = true; } if ( l.scenograph.controls.touch.controls.moveLeft ) { - this.game.actor.controls.moveLeft = true; + this.game.components.PlayerInput.moveLeft = true; } if ( l.scenograph.controls.touch.controls.moveRight ) { - this.game.actor.controls.moveRight = true; + this.game.components.PlayerInput.moveRight = true; } } } } } - this.game.actor.controls.changing = changing; + this.game.components.PlayerInput.changing = changing; } @@ -380,10 +383,10 @@ export default class Valiant { this.mixer.update( delta ); } - if ( this.game ) { + if (this.game) { // Rock the ship forward and back when moving horizontally - if ( this.game.actor.controls.throttleDown || this.game.actor.controls.throttleUp ) { - let pitchChange = this.game.actor.controls.throttleUp ? -1 : 1; + if ( this.game.components.PlayerInput.throttleDown || this.game.components.PlayerInput.throttleUp ) { + let pitchChange = this.game.components.PlayerInput.throttleUp ? -1 : 1; if ( Math.abs( this.mesh.rotation.x ) < 1 / 4 ) { this.mesh.rotation.x += pitchChange / 10 / 180; } @@ -391,11 +394,11 @@ export default class Valiant { // Rock the ship forward and back when moving vertically if ( - this.game.actor.controls.moveDown + this.game.components.PlayerInput.moveDown || - this.game.actor.controls.moveUp + this.game.components.PlayerInput.moveUp ) { - let elevationChange = this.game.actor.controls.moveDown ? -1 : 1; + let elevationChange = this.game.components.PlayerInput.moveDown ? -1 : 1; if ( Math.abs( this.mesh.rotation.x ) < 1 / 8 ) { this.mesh.rotation.x += elevationChange / 10 / 180; } @@ -414,12 +417,14 @@ export default class Valiant { // Update the position of the aircraft to spot determined by game logic. sync() { - this.mesh.position.x = this.game.object.position.x; - this.mesh.position.y = this.game.object.position.y; - this.mesh.position.z = this.game.object.position.z; - this.mesh.rotation.x = this.game.object.rotation.x; - this.mesh.rotation.y = this.game.object.rotation.y; - this.mesh.rotation.z = this.game.object.rotation.z; + console.log(this.game); + debugger; + this.mesh.position.x = this.game.components.Transform.position.x; + this.mesh.position.y = this.game.components.Transform.position.y; + this.mesh.position.z = this.game.components.Transform.position.z; + this.mesh.rotation.x = this.game.components.Transform.rotation.x; + this.mesh.rotation.y = this.game.components.Transform.rotation.y; + this.mesh.rotation.z = this.game.components.Transform.rotation.z; } updateCamera( rY, tY, tZ ) { @@ -499,7 +504,7 @@ export default class Valiant { } if ( l.scenograph.modes.multiplayer.connected ) { - l.scenograph.modes.multiplayer.socket.emit( 'input', this.game.actor.controls ); + l.scenograph.modes.multiplayer.socket.emit( 'input', this.game.components.PlayerInput ); } this.mesh.userData.actor.animate( delta ); diff --git a/game/ecs/components/playerInput.ts b/game/ecs/components/playerInput.ts new file mode 100644 index 00000000..13700035 --- /dev/null +++ b/game/ecs/components/playerInput.ts @@ -0,0 +1,11 @@ +// components/playerInput.ts + +export interface PlayerInput { + changing: boolean, + throttleUp: boolean, + throttleDown: boolean, + moveUp: boolean, + moveDown: boolean, + moveLeft: boolean, + moveRight: boolean, +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 8b741454..0bb74379 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -15,12 +15,16 @@ import Raven from "./data/objects/raven.yml"; import Valiant from "./data/objects/valiant.yml"; import Overworld from "./data/scenes/overworld.yml"; +// Base includes import { Vec3 } from "./types"; +// Components. +import { Motion } from "./components/Motion"; import { Name } from "./components/name"; +import { PlayerInput } from "./components/playerInput"; import { Transform } from "./components/Transform"; -import { Motion } from "./components/Motion"; +// Systems. import { movementSystem } from "./systems/movement"; export default class World { @@ -64,6 +68,19 @@ export default class World { // Attach each component for (const [componentName, componentData] of Object.entries(entityConfig.components)) { switch (componentName) { + case 'PlayerInput': + let initialControlState = { + changing: false, + throttleUp: false, + throttleDown: false, + moveUp: false, + moveDown: false, + moveLeft: false, + moveRight: false, + }; + entityInstance.components.PlayerInput = initialControlState as PlayerInput; + break; + case 'Name': entityInstance.components.Name = componentData as Name; break; From 6f71a692354ea3b7b05e00506a37d6b92f04fe3b Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Fri, 10 Apr 2026 06:54:00 +0000 Subject: [PATCH 14/23] #31 - Updating instance variable references to use motion components --- .../scenograph/objects/vehicles/valiant.js | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 0475391c..85e22932 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -417,8 +417,8 @@ export default class Valiant { // Update the position of the aircraft to spot determined by game logic. sync() { - console.log(this.game); - debugger; + // console.log(this.game); + // debugger; this.mesh.position.x = this.game.components.Transform.position.x; this.mesh.position.y = this.game.components.Transform.position.y; this.mesh.position.z = this.game.components.Transform.position.z; @@ -427,12 +427,12 @@ export default class Valiant { this.mesh.rotation.z = this.game.components.Transform.rotation.z; } - updateCamera( rY, tY, tZ ) { + updateCamera( heading, verticalSpeed, horizontalSpeed ) { var radian = ( Math.PI / 180 ); this.camera_distance = this.default_camera_distance + ( l.current_scene.room_depth / 2 ); - if ( this.game.object.airSpeed < 0 ) { - this.camera_distance -= this.game.object.airSpeed * 4; + if ( this.game.components.Motion.velocity.horizontal < 0 ) { + this.camera_distance -= this.game.components.Motion.velocity.horizontal * 4; } let xDiff = this.mesh.position.x; @@ -441,8 +441,8 @@ export default class Valiant { l.scenograph.cameras.player.position.x = xDiff + this.camera_distance * Math.sin( this.mesh.rotation.y ); l.scenograph.cameras.player.position.z = zDiff + this.camera_distance * Math.cos( this.mesh.rotation.y ); - if ( rY != 0 && Math.abs(l.scenograph.cameras.player.rotation.y) < .3925 ) { - l.scenograph.cameras.player.rotation.y += rY; + if ( heading != 0 && Math.abs(l.scenograph.cameras.player.rotation.y) < .3925 ) { + l.scenograph.cameras.player.rotation.y += heading; } else { // Check there is y difference and the rotation pad isn't being pressed. @@ -469,11 +469,11 @@ export default class Valiant { } - let xDiff2 = tZ * Math.sin( this.mesh.rotation.y ), - zDiff2 = tZ * Math.cos( this.mesh.rotation.y ); + let xDiff2 = horizontalSpeed * Math.sin( this.mesh.rotation.y ), + zDiff2 = horizontalSpeed * Math.cos( this.mesh.rotation.y ); - if ( this.mesh.position.y + tY >= 1 ) { - l.scenograph.cameras.player.position.y += tY; + if ( this.mesh.position.y + verticalSpeed >= 1 ) { + l.scenograph.cameras.player.position.y += verticalSpeed; } l.scenograph.cameras.player.position.x += xDiff2; @@ -515,37 +515,39 @@ export default class Valiant { this.updateAnimation( delta ); if (this.game) { + // console.log(this.game); + // debugger; this.sync(); - this.updateCamera( this.game.object.rY, this.game.object.tY, this.game.object.tZ ); + this.updateCamera( this.game.components.Motion.heading, this.game.components.Motion.velocity.vertical, this.game.components.Motion.velocity.horizontal ); - this.animateTrail( this.game.object.rY ); + this.animateTrail( this.game.components.Motion.heading ); } } } - animateTrail( rY ) { + animateTrail( heading ) { if ( this.trail ) { // Fix the trail being too far behind. let trailOffset = 0; // Only offset the trail effect if we are going forward which is (z-1) in numerical terms - if ( this.game.object.airSpeed < 0 ) { + if ( this.game.components.Motion.velocity.horizontal < 0 ) { // Update ship thruster - this.animateThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, .5 ); - this.animateThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, .5 ); + this.animateThruster( this.game.components.Motion.velocity.horizontal, this.thruster.centralConeBurner, .5 ); + this.animateThruster( this.game.components.Motion.velocity.horizontal, this.thruster.outerCylBurner, .5 ); - this.spinThruster( this.game.object.airSpeed, this.thruster.rearConeBurner, -1 ); - this.spinThruster( this.game.object.airSpeed, this.thruster.centralConeBurner, 1 ); - this.spinThruster( this.game.object.airSpeed, this.thruster.outerCylBurner, -1 ); - this.spinThruster( this.game.object.airSpeed, this.thruster.innerCylBurner, 1 ); + this.spinThruster( this.game.components.Motion.velocity.horizontal, this.thruster.rearConeBurner, -1 ); + this.spinThruster( this.game.components.Motion.velocity.horizontal, this.thruster.centralConeBurner, 1 ); + this.spinThruster( this.game.components.Motion.velocity.horizontal, this.thruster.outerCylBurner, -1 ); + this.spinThruster( this.game.components.Motion.velocity.horizontal, this.thruster.innerCylBurner, 1 ); // Limit playback rate to 5x as large values freak out the browser. - this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.game.object.airSpeed ) ); + this.thruster.videoElement.playbackRate = Math.min( 5, 0.25 + Math.abs( this.game.components.Motion.velocity.horizontal ) ); - trailOffset += this.trail_position_z - Math.abs( this.game.object.airSpeed ); + trailOffset += this.trail_position_z - Math.abs( this.game.components.Motion.velocity.horizontal ); this.trail.mesh.material.uniforms.headColor.value.set( 255 / 255, 212 / 255, 148 / 255, .8 ); // RGBA. } @@ -554,11 +556,11 @@ export default class Valiant { } // Update the trail position based on above calculations. - this.trail.targetObject.position.y = this.trail_position_y + this.game.object.verticalSpeed; + this.trail.targetObject.position.y = this.trail_position_y + this.game.components.Motion.velocity.vertical; this.trail.targetObject.position.z = trailOffset; - if ( rY != 0 ) { - this.trail.targetObject.position.x = rY * this.game.object.airSpeed; + if ( heading != 0 ) { + this.trail.targetObject.position.x = heading * this.game.components.Motion.velocity.horizontal; this.trail.targetObject.position.y += Math.abs( this.trail.targetObject.position.x ) / 4; } else { From 9ebf6d425d80c26495736a822101f323725a7c32 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Fri, 10 Apr 2026 07:15:49 +0000 Subject: [PATCH 15/23] #31 - Resetting vehicle rotation Y to 0 in config --- client/src/app/scenograph/objects/vehicles/valiant.js | 2 -- game/ecs/data/scenes/overworld.yml | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 85e22932..06dd0683 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -417,8 +417,6 @@ export default class Valiant { // Update the position of the aircraft to spot determined by game logic. sync() { - // console.log(this.game); - // debugger; this.mesh.position.x = this.game.components.Transform.position.x; this.mesh.position.y = this.game.components.Transform.position.y; this.mesh.position.z = this.game.components.Transform.position.z; diff --git a/game/ecs/data/scenes/overworld.yml b/game/ecs/data/scenes/overworld.yml index 20e40e70..f25f4e7c 100644 --- a/game/ecs/data/scenes/overworld.yml +++ b/game/ecs/data/scenes/overworld.yml @@ -80,7 +80,7 @@ entities: Name: Player One Transform: position: { x: -65000, y: -35000, z: 1500 } - rotation: { x: 0, y: -12.56, z: 0 } + rotation: { x: 0, y: 0, z: 0 } Movable: true Renderable: primaryColor: FF0000 @@ -98,7 +98,7 @@ entities: Name: Player Two Transform: position: { x: -65000, y: -35000, z: 1500 } - rotation: { x: 0, y: -12.56, z: 0 } + rotation: { x: 0, y: 0, z: 0 } Movable: true Renderable: primaryColor: 0000FF @@ -119,7 +119,7 @@ entities: Name: Pirate One Transform: position: { x: -65000, y: -35000, z: 1500 } - rotation: { x: 0, y: -12.56, z: 0 } + rotation: { x: 0, y: 0, z: 0 } Movable: true Renderable: primaryColor: 00FF00 From d8944ba4da064d9362ea7fb4ec365841b56168ce Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Tue, 28 Apr 2026 15:13:36 +0000 Subject: [PATCH 16/23] #31 - Updating remaining player game object references --- client/src/app/routes/hangar.js | 6 +++--- client/src/app/scenograph/overlays/heads-up-display.js | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/app/routes/hangar.js b/client/src/app/routes/hangar.js index a4849973..f5b7e578 100644 --- a/client/src/app/routes/hangar.js +++ b/client/src/app/routes/hangar.js @@ -43,9 +43,9 @@ export default class hangarRoute { l.scenograph.objects.structures.hangar.mesh.position.y = this.targetStructure.userData.config.hangars[0].position.y; l.scenograph.objects.structures.hangar.mesh.position.z = this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.game.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; - l.scenograph.actors.player.vehicle.game.object.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; - l.scenograph.actors.player.vehicle.game.object.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; + l.scenograph.actors.player.vehicle.game.components.Transform.position.position.x = this.targetStructure.userData.config.hangars[0].position.x; + l.scenograph.actors.player.vehicle.game.components.Transform.position.position.z = - 2.5 + this.targetStructure.userData.config.hangars[0].position.z; + l.scenograph.actors.player.vehicle.game.components.Transform.position.position.y = l.scenograph.objects.structures.hangar.mesh.position.y - 7.5; l.scenograph.actors.player.actorInstance.object.position.x = this.targetStructure.userData.config.hangars[0].position.x; l.scenograph.actors.player.actorInstance.object.position.z = 10 + this.targetStructure.userData.config.hangars[0].position.z; diff --git a/client/src/app/scenograph/overlays/heads-up-display.js b/client/src/app/scenograph/overlays/heads-up-display.js index 955f74e4..8b04fb22 100644 --- a/client/src/app/scenograph/overlays/heads-up-display.js +++ b/client/src/app/scenograph/overlays/heads-up-display.js @@ -77,10 +77,12 @@ export default class HeadsUpDisplay { l.scenograph.overlays.hud.container.classList.remove('portrait'); } - let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.airSpeed); + let aspd = -l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.components.Motion.velocity.horizontal +); l.scenograph.overlays.hud.aspdElement.innerHTML = `AIRSPEED: ${aspd}km/h`; - let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.object.verticalSpeed); + let vspd = l.scenograph.overlays.hud.frameToSecond(l.scenograph.actors.player.vehicle.game.components.Motion.velocity.vertical +); l.scenograph.overlays.hud.vspdElement.innerHTML = `VERT. SPD: ${vspd}km/h`; let heading = THREE.MathUtils.radToDeg( l.scenograph.actors.player.vehicle.mesh.rotation.y ); From 172e2654edb2f6231df4c1af96ce5d724e8885e6 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Tue, 28 Apr 2026 17:46:30 +0000 Subject: [PATCH 17/23] #31 - Implementing scanner components and vehicle configs --- .../scenograph/objects/vehicles/valiant.js | 10 +++--- game/ecs/components/scanner.ts | 10 ++++++ game/ecs/data/objects/cargoShip.yml | 4 +++ game/ecs/data/objects/person.yml | 4 +++ game/ecs/data/objects/raven.yml | 4 +++ game/ecs/data/objects/valiant.yml | 4 +++ game/ecs/types.ts | 9 +++++ game/ecs/world.ts | 35 +++++++++++++++++-- 8 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 game/ecs/components/scanner.ts diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 06dd0683..75262888 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -13,7 +13,6 @@ import * as THREE from 'three'; */ import l from '@/helpers/l.js'; import { brightenMaterial, proceduralMetalMaterial } from '@/scenograph/materials.js'; -import Player from '#/game/src/actors/player'; export default class Valiant { @@ -115,8 +114,10 @@ export default class Valiant { this.mesh.userData.targetable = true; this.mesh.userData.objectClass = 'player'; - this.mesh.userData.actor = new Player( this.mesh, l.current_scene.scene ); - l.scenograph.entityManager.add( this.mesh.userData.actor.entity ); + + // @todo #31: This needs review to ensure entity manager is populated. + //this.mesh.userData.actor = new Player( this.mesh, l.current_scene.scene ); + //l.scenograph.entityManager.add( this.mesh.userData.actor.entity ); this.createThruster(); @@ -505,7 +506,8 @@ export default class Valiant { l.scenograph.modes.multiplayer.socket.emit( 'input', this.game.components.PlayerInput ); } - this.mesh.userData.actor.animate( delta ); + // @todo: #31: Uncomment once actors are re-implemented in ECS + // this.mesh.userData.actor.animate( delta ); } diff --git a/game/ecs/components/scanner.ts b/game/ecs/components/scanner.ts new file mode 100644 index 00000000..51e7a107 --- /dev/null +++ b/game/ecs/components/scanner.ts @@ -0,0 +1,10 @@ +// components/scanner.ts + +import { ScanState } from "../types"; + +export interface Scanner { + range: number; + fieldOfView: number; + + targets: Record; +} diff --git a/game/ecs/data/objects/cargoShip.yml b/game/ecs/data/objects/cargoShip.yml index 8e212617..148298fc 100644 --- a/game/ecs/data/objects/cargoShip.yml +++ b/game/ecs/data/objects/cargoShip.yml @@ -13,3 +13,7 @@ limits: primaryColor: FF0000 secondaryColor: FFFF00 + +scanner: + range: 1500 + fov: 1.57 diff --git a/game/ecs/data/objects/person.yml b/game/ecs/data/objects/person.yml index 8dd0a4ac..986d5177 100644 --- a/game/ecs/data/objects/person.yml +++ b/game/ecs/data/objects/person.yml @@ -13,3 +13,7 @@ limits: primaryColor: FF0000 secondaryColor: FFFF00 + +scanner: + range: 1500 + fov: 1.57 diff --git a/game/ecs/data/objects/raven.yml b/game/ecs/data/objects/raven.yml index 996c392c..f1664b74 100644 --- a/game/ecs/data/objects/raven.yml +++ b/game/ecs/data/objects/raven.yml @@ -13,3 +13,7 @@ limits: primaryColor: FF0000 secondaryColor: FFFF00 + +scanner: + range: 1500 + fov: 1.57 diff --git a/game/ecs/data/objects/valiant.yml b/game/ecs/data/objects/valiant.yml index 70c7fc88..d7114419 100644 --- a/game/ecs/data/objects/valiant.yml +++ b/game/ecs/data/objects/valiant.yml @@ -13,3 +13,7 @@ limits: primaryColor: FF0000 secondaryColor: FFFF00 + +scanner: + range: 1500 + fov: 1.57 diff --git a/game/ecs/types.ts b/game/ecs/types.ts index acffdfaa..c8298dd5 100644 --- a/game/ecs/types.ts +++ b/game/ecs/types.ts @@ -9,3 +9,12 @@ export interface Vec3 { export interface AABB { halfSize: Vec3; } + +export interface ScanState { + scanTime: number; + lostTime: number; + + tracking: boolean; + locking: boolean; + locked: boolean; +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 0bb74379..498979d3 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -19,16 +19,19 @@ import Overworld from "./data/scenes/overworld.yml"; import { Vec3 } from "./types"; // Components. -import { Motion } from "./components/Motion"; +import { Motion } from "./components/motion"; import { Name } from "./components/name"; import { PlayerInput } from "./components/playerInput"; -import { Transform } from "./components/Transform"; +import { Scanner } from "./components/scanner"; +import { Transform } from "./components/transform"; // Systems. import { movementSystem } from "./systems/movement"; export default class World { + private objectConfigMap: Record; + public configs: Record; public entities: Record; @@ -48,7 +51,14 @@ export default class World { * - loop over config to load game world simulation in here and scenograph in the client */ constructor( sceneName: string ) { - if ( sceneName === 'Overworld' ) { + if (sceneName === 'Overworld') { + this.objectConfigMap = { + cargoShip: CargoShip, + person: Person, + raven: Raven, + valiant: Valiant + } as const; + this.configs = Overworld.entities; this.entities = new Map(); @@ -103,15 +113,30 @@ export default class World { // Load object specific settings from config. if (entityConfig.components.Renderable.object === 'cargoShip') { entityInstance.components.Motion.limits = CargoShip.limits; + entityInstance.components.Scanner = { + range: CargoShip.scanner.range, + fieldOfView: CargoShip.scanner.fov, + targets: {} + } as Scanner; } if (entityConfig.components.Renderable.object === 'person') { entityInstance.components.Motion.limits = Person.limits; } if (entityConfig.components.Renderable.object === 'raven') { entityInstance.components.Motion.limits = Raven.limits; + entityInstance.components.Scanner = { + range: Raven.scanner.range, + fieldOfView: Raven.scanner.fov, + targets: {} + } as Scanner; } if (entityConfig.components.Renderable.object === 'valiant') { entityInstance.components.Motion.limits = Valiant.limits; + entityInstance.components.Scanner = { + range: Valiant.scanner.range, + fieldOfView: Valiant.scanner.fov, + targets: {} + } as Scanner; } } break; @@ -123,6 +148,10 @@ export default class World { } + getObjectConfig(objectName) { + return this.objectConfigMap.get(objectName); + } + start() { this.running = true; this.lastUpdateTime = performance.now(); From b008a79933a9dc93394d94c8647e80bed4e09233 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Tue, 5 May 2026 15:37:33 +0000 Subject: [PATCH 18/23] code --- .../scenograph/objects/vehicles/valiant.js | 2 +- client/src/app/scenograph/overlays/map.js | 2 +- .../src/app/scenograph/overlays/scanners.js | 36 +++++++++---------- game/ecs/components/scanner.ts | 2 +- game/ecs/world.ts | 6 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/client/src/app/scenograph/objects/vehicles/valiant.js b/client/src/app/scenograph/objects/vehicles/valiant.js index 75262888..43562a6b 100644 --- a/client/src/app/scenograph/objects/vehicles/valiant.js +++ b/client/src/app/scenograph/objects/vehicles/valiant.js @@ -56,7 +56,7 @@ export default class Valiant { this.camera_distance = 0; this.ready = false; - console.log(this); + } diff --git a/client/src/app/scenograph/overlays/map.js b/client/src/app/scenograph/overlays/map.js index a9eca0ff..bf976578 100644 --- a/client/src/app/scenograph/overlays/map.js +++ b/client/src/app/scenograph/overlays/map.js @@ -110,7 +110,7 @@ export default class Map { let topEdge = l.scenograph.actors.player.vehicle.mesh.position.z - l.scenograph.overlays.map.distance / 2; - l.scenograph.actors.player.vehicle.mesh.userData.actor.scanners.targets.forEach( target => { + l.scenograph.actors.get('Player One').vehicle.game.components.Scanner.targets.forEach( target => { let distance = target.mesh.position.distanceTo( l.scenograph.actors.player.vehicle.mesh.position ); // Check if the object is within the mapping distance. diff --git a/client/src/app/scenograph/overlays/scanners.js b/client/src/app/scenograph/overlays/scanners.js index 8fda3c40..bd215133 100644 --- a/client/src/app/scenograph/overlays/scanners.js +++ b/client/src/app/scenograph/overlays/scanners.js @@ -34,7 +34,7 @@ export default class Scanners { /** * Get the marker symbol. * - * @param {*} symbol + * @param {*} symbol * @returns custom HTMLElement */ getSymbolElement( symbol ) { @@ -43,17 +43,17 @@ export default class Scanners { element.querySelector('.symbol').innerHTML = l.scenograph.overlays.map.icons[ symbol ]; element.firstChild.classList.add( symbol ); const shape = element.querySelector('.symbol path, .symbol rect'); - + if ( shape.style ) { shape.style = ''; } - + return element.firstChild; } /** * Obtains the 2D screen co-ordinate for a 3D object target. - * + * * @param {*} THREE.Object3D currently in the scene * @returns [x,y] screen space coordinates * @global @@ -112,24 +112,24 @@ export default class Scanners { /** * Animate hook. - * + * * This method is called within the main animation loop and * therefore must only reference global objects or properties. - * + * * @method animate * @memberof Scanners * @global * @note All references within this method should be globally accessible. **/ animate( delta ) { - + const frustum = new THREE.Frustum() const matrix = new THREE.Matrix4().multiplyMatrices(l.scenograph.cameras.active.projectionMatrix, l.scenograph.cameras.active.matrixWorldInverse) - frustum.setFromProjectionMatrix(matrix) + frustum.setFromProjectionMatrix(matrix) l.scenograph.cameras.active.updateProjectionMatrix(); // Use the players scanners to update the overlays. - l.scenograph.actors.player.vehicle.mesh.userData.actor.scanners.targets.forEach( target => l.scenograph.overlays.scanners.animateTarget( delta, target, frustum ) ); + l.scenograph.actors.get('Player One').vehicle.game.components.Scanner.targets.forEach( target => l.scenograph.overlays.scanners.animateTarget( delta, target, frustum ) ); l.scenograph.overlays.scanners.removeOldTargets(); @@ -139,10 +139,10 @@ export default class Scanners { /** * Animate the target in the UI overlay - * - * @param {*} delta - * @param {*} trackedObject - * @param {*} frustum + * + * @param {*} delta + * @param {*} trackedObject + * @param {*} frustum */ animateTarget( delta, target, frustum ) { let [ x, y ] = l.scenograph.overlays.scanners.getScreenCoordinates( target.mesh, frustum ); @@ -161,8 +161,8 @@ export default class Scanners { else { domElement.classList.add('tracking'); domElement.classList.remove('locked'); - domElement.classList.remove('locking'); - } + domElement.classList.remove('locking'); + } } } @@ -182,9 +182,9 @@ export default class Scanners { * Show/hide markers of objects that are respawning. */ toggleRespawningTargets() { - + const overlayKeys = Object.keys(l.scenograph.overlays.scanners.trackedObjects); - const scannerKeys = l.scenograph.actors.player.vehicle.mesh.userData.actor.scanners.targets.map(t => t.mesh.uuid); + const scannerKeys = l.scenograph.actors.get('Player One').vehicle.game.components.Scanner.targets.map(t => t.mesh.uuid); // Hide targets missing from player scanners, theoretically those are ones being relocated by the engine / respawning. const respawningTargets = overlayKeys.filter(k => !scannerKeys.includes(k)); @@ -207,7 +207,7 @@ export default class Scanners { // Delete the marker domElement from memory. delete l.scenograph.overlays.scanners.trackedObjects[ uuid ]; - } + } } } diff --git a/game/ecs/components/scanner.ts b/game/ecs/components/scanner.ts index 51e7a107..c5a55ff1 100644 --- a/game/ecs/components/scanner.ts +++ b/game/ecs/components/scanner.ts @@ -6,5 +6,5 @@ export interface Scanner { range: number; fieldOfView: number; - targets: Record; + targets: Array; } diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 498979d3..b4d911d4 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -116,7 +116,7 @@ export default class World { entityInstance.components.Scanner = { range: CargoShip.scanner.range, fieldOfView: CargoShip.scanner.fov, - targets: {} + targets: [] } as Scanner; } if (entityConfig.components.Renderable.object === 'person') { @@ -127,7 +127,7 @@ export default class World { entityInstance.components.Scanner = { range: Raven.scanner.range, fieldOfView: Raven.scanner.fov, - targets: {} + targets: [] } as Scanner; } if (entityConfig.components.Renderable.object === 'valiant') { @@ -135,7 +135,7 @@ export default class World { entityInstance.components.Scanner = { range: Valiant.scanner.range, fieldOfView: Valiant.scanner.fov, - targets: {} + targets: [] } as Scanner; } } From 3735252ea8571c9a834b012949772b6bcb0afc3c Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 9 May 2026 05:21:42 +0000 Subject: [PATCH 19/23] #31 - Adding dummy scanner and weapons via ECS --- .../app/scenograph/controls/touch/weapons.js | 26 +++++++++---------- game/ecs/components/scanner.ts | 6 ++++- game/ecs/components/weapon.ts | 13 ++++++++++ game/ecs/world.ts | 18 ++++++++++--- 4 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 game/ecs/components/weapon.ts diff --git a/client/src/app/scenograph/controls/touch/weapons.js b/client/src/app/scenograph/controls/touch/weapons.js index 3147dc6c..620cff0b 100644 --- a/client/src/app/scenograph/controls/touch/weapons.js +++ b/client/src/app/scenograph/controls/touch/weapons.js @@ -1,6 +1,6 @@ /** * Touchscreen Weapon Controls - * + * */ /** @@ -9,24 +9,24 @@ import l from '@/helpers/l.js'; export default class WeaponControls { - + /** * Switch indicating we are currently attacking. - * - * @type {boolean} + * + * @type {boolean} */ attack; /** * Switch indicating auto attack is on. - * - * @type {boolean} + * + * @type {boolean} */ autoAttack; /** * HTML Container - * + * * @type {HTMLElement} */ container; @@ -39,7 +39,7 @@ export default class WeaponControls { this.container.querySelector('.switch input').onchange = ()=>{ l.scenograph.controls.touch.weapons.autoAttack = l.scenograph.controls.touch.weapons.container.querySelector('.switch input').checked; - }; + }; this.container.querySelector('button').onclick = ()=>{ l.scenograph.controls.touch.weapons.attack = true; @@ -53,10 +53,10 @@ export default class WeaponControls { /** * Update hook. - * + * * This method is called within the UI setInterval updater, allowing * HTML content to be updated at different rate than the 3D frame rate. - * + * * @method update * @memberof WeaponControls * @global @@ -67,9 +67,9 @@ export default class WeaponControls { let timeRemaining = 0; - if ( parseInt(l.current_scene.stats.currentTime) < parseInt(l.scenograph.actors.player.vehicle.mesh.userData.actor.weapons.last) + parseInt(l.scenograph.actors.player.vehicle.mesh.userData.actor.weapons.timeout) ) { - timeRemaining = parseInt(l.scenograph.actors.player.vehicle.mesh.userData.actor.weapons.timeout) - ( - parseInt(l.current_scene.stats.currentTime) - parseInt(l.scenograph.actors.player.vehicle.mesh.userData.actor.weapons.last) + if ( parseInt(l.current_scene.stats.currentTime) < parseInt(l.scenograph.actors.player.vehicle.game.components.Weapon.last) + parseInt(l.scenograph.actors.player.vehicle.game.components.Weapon.timeout) ) { + timeRemaining = parseInt(l.scenograph.actors.player.vehicle.game.components.Weapon.timeout) - ( + parseInt(l.current_scene.stats.currentTime) - parseInt(l.scenograph.actors.player.vehicle.game.components.Weapon.last) ); } diff --git a/game/ecs/components/scanner.ts b/game/ecs/components/scanner.ts index c5a55ff1..8c337611 100644 --- a/game/ecs/components/scanner.ts +++ b/game/ecs/components/scanner.ts @@ -6,5 +6,9 @@ export interface Scanner { range: number; fieldOfView: number; - targets: Array; + targets: Array; + + // Animation. + last; + timeout; } diff --git a/game/ecs/components/weapon.ts b/game/ecs/components/weapon.ts new file mode 100644 index 00000000..1c06a511 --- /dev/null +++ b/game/ecs/components/weapon.ts @@ -0,0 +1,13 @@ +// components/weapon.ts + +export interface Weapon { + // Origin mesh that fired the missile. + mesh; + + // Scanner system that determine where we're firing. + scanner; + + // Animation. + last; + timeout; +} diff --git a/game/ecs/world.ts b/game/ecs/world.ts index b4d911d4..44c8b097 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -24,6 +24,7 @@ import { Name } from "./components/name"; import { PlayerInput } from "./components/playerInput"; import { Scanner } from "./components/scanner"; import { Transform } from "./components/transform"; +import { Weapon } from "./components/weapon"; // Systems. import { movementSystem } from "./systems/movement"; @@ -116,7 +117,9 @@ export default class World { entityInstance.components.Scanner = { range: CargoShip.scanner.range, fieldOfView: CargoShip.scanner.fov, - targets: [] + targets: [], + last: 0, + timeout: 0, } as Scanner; } if (entityConfig.components.Renderable.object === 'person') { @@ -127,7 +130,9 @@ export default class World { entityInstance.components.Scanner = { range: Raven.scanner.range, fieldOfView: Raven.scanner.fov, - targets: [] + targets: [], + last: 0, + timeout: 0, } as Scanner; } if (entityConfig.components.Renderable.object === 'valiant') { @@ -135,8 +140,15 @@ export default class World { entityInstance.components.Scanner = { range: Valiant.scanner.range, fieldOfView: Valiant.scanner.fov, - targets: [] + targets: [], + last: 0, + timeout: 0, } as Scanner; + entityInstance.components.Weapon = { + scanner: entityInstance.components.Scanner, + last: 0, + timeout: 0, + } as Weapon; } } break; From 808032c01898369ffc45c5a4f7a3a4286a1aea46 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Sat, 9 May 2026 08:46:32 +0000 Subject: [PATCH 20/23] #31 - AI ECS component and begun writing scanner subsystem --- game/ecs/components/ai.ts | 8 ++ game/ecs/systems/scanner.ts | 159 ++++++++++++++++++++++++++++++++++++ game/ecs/world.ts | 68 ++++++++------- 3 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 game/ecs/components/ai.ts create mode 100644 game/ecs/systems/scanner.ts diff --git a/game/ecs/components/ai.ts b/game/ecs/components/ai.ts new file mode 100644 index 00000000..53a4f061 --- /dev/null +++ b/game/ecs/components/ai.ts @@ -0,0 +1,8 @@ +// components/ai.ts + +import * as YUKA from 'yuka'; + +export interface AI { + entity: YUKA.GameEntity; + tactics: String; +} diff --git a/game/ecs/systems/scanner.ts b/game/ecs/systems/scanner.ts new file mode 100644 index 00000000..8acd92d9 --- /dev/null +++ b/game/ecs/systems/scanner.ts @@ -0,0 +1,159 @@ +import { Transform } from "../components/Transform"; +import { Velocity } from "../components/Velocity"; + +export function scannerSystem(entities: Map, deltaMs: number) { + const dt = deltaMs / 1000; // convert ms to seconds + + // Get list of targetable things in the entire scene. + let targetable = []; + for (const entity of entities.values()) { + // Check if they are targetable based on having HP config. + // @todo: Find a tidier way to do this + if (entity.config.components.Health) { + targetable.push(entity); + } + } + + // Update all scanner components with their individual target lists based on line of sight. + for (const entity of entities.values()) { + if (entity.components.Scanner) { + for (const target of targetable) { + + // Skip self. + if (entity.components.Name === target.components.Name) { + continue; + } + console.log(entity, target); + debugger; + const targetVisible = entity.components.AI.entity.vision.visible(target.mesh.position) === true; + } + + } + } + debugger; +} + +// getTargetable() { +// return this.scene.children.filter( scene_obj => +// scene_obj.userData.targetable +// ); +// } + +// export function scan( delta ) { +// let currentTargets = this.getTargetable(); + +// // Check if we are already tracking this target. +// this.targets.forEach( ( target, index ) => { + +// let trackingObject = currentTargets.filter( object => (object.uuid == target.mesh.uuid ) ); +// trackingObject = trackingObject.length > 0 ? trackingObject[0] : false; + +// // Remove tracking if object no longer in the scene. +// if ( ! trackingObject ) { +// this.targets.splice( index, 1 ); +// } +// } ); + +// // Update targets tracked in the array. +// for ( const target of currentTargets ) { + +// // Skip self. +// if ( target.uuid === this.mesh.uuid ) { +// continue; +// } + +// // Check if we are already tracking this target. +// let trackingObject = this.targets.filter( object => (object.mesh.uuid == target.uuid ) ); +// trackingObject = trackingObject.length > 0 ? trackingObject[0] : false; + +// if ( ! trackingObject ) { +// trackingObject = { +// mesh: target, +// locked: false, +// locking: false, +// tracking: false, +// scanTime: 0, +// lostTime: 0 +// } +// this.targets.push( trackingObject ); +// } + +// } + +// // Update the targets scanner statuses. +// this.targets.forEach( ( target, index ) => { +// const targetVisible = this.entity.vision.visible( target.mesh.position ) === true; + +// // If the scanner's vision can see the target, start scanning. +// if ( targetVisible ) { +// target.tracking = true; +// target.scanTime += delta; +// target.lostTime = 0; + +// if ( target.scanTime >= 1 ) { +// if ( target.scanTime >= 3 ) { +// target.locked = true; +// target.locking = false; +// } +// else { +// target.locked = false; +// target.locking = true; +// } +// } +// else { +// target.locked = false; +// target.locking = false; +// } +// } +// else { +// target.lostTime += delta; + +// if ( target.scanTime < 1 ) { +// target.scanTime = 0; +// target.lostTime = 0; +// } +// else { +// // Allow 3 seconds before a target is downgraded when locked. +// if ( target.lostTime >= 3 && target.locked ) { +// target.locked = false; + +// target.lostTime = 0; +// } +// else { +// // Allow 1 seconds before a target is downgraded when locking. +// if ( target.lostTime >= 1 && target.locking ) { +// target.locking = false; + +// target.lostTime = 0; +// } +// } + +// // Allow 1 seconds before a target is lost when tracking. +// if ( +// target.lostTime >= 1 && +// target.tracking && +// ! target.locked && +// ! target.locking +// ) { +// target.scanTime = 0; +// target.lostTime = 0; +// target.locked = false; +// target.locking = false; +// } + +// } +// } +// } ); + +// } + +// untrackTarget( uuid ) { +// this.targets.forEach( ( target ) => { +// if ( target.mesh.uuid == uuid ) { +// target.locked = false; +// target.locking = false; +// target.tracking = false; +// target.scanTime = 0; +// } +// }); +// } diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 44c8b097..89a96179 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -7,6 +7,7 @@ * */ +import * as YUKA from 'yuka'; // Import YAML configs. import CargoShip from "./data/objects/cargoShip.yml"; @@ -19,6 +20,7 @@ import Overworld from "./data/scenes/overworld.yml"; import { Vec3 } from "./types"; // Components. +import { AI } from "./components/ai"; import { Motion } from "./components/motion"; import { Name } from "./components/name"; import { PlayerInput } from "./components/playerInput"; @@ -28,6 +30,7 @@ import { Weapon } from "./components/weapon"; // Systems. import { movementSystem } from "./systems/movement"; +import { scannerSystem } from './systems/scanner'; export default class World { @@ -113,42 +116,17 @@ export default class World { if (entityConfig.components.Renderable && entityConfig.components.Renderable.object) { // Load object specific settings from config. if (entityConfig.components.Renderable.object === 'cargoShip') { - entityInstance.components.Motion.limits = CargoShip.limits; - entityInstance.components.Scanner = { - range: CargoShip.scanner.range, - fieldOfView: CargoShip.scanner.fov, - targets: [], - last: 0, - timeout: 0, - } as Scanner; + this.loadVehicle(entityInstance, CargoShip); } if (entityConfig.components.Renderable.object === 'person') { entityInstance.components.Motion.limits = Person.limits; } if (entityConfig.components.Renderable.object === 'raven') { - entityInstance.components.Motion.limits = Raven.limits; - entityInstance.components.Scanner = { - range: Raven.scanner.range, - fieldOfView: Raven.scanner.fov, - targets: [], - last: 0, - timeout: 0, - } as Scanner; + this.loadVehicle(entityInstance, Raven, true); } if (entityConfig.components.Renderable.object === 'valiant') { - entityInstance.components.Motion.limits = Valiant.limits; - entityInstance.components.Scanner = { - range: Valiant.scanner.range, - fieldOfView: Valiant.scanner.fov, - targets: [], - last: 0, - timeout: 0, - } as Scanner; - entityInstance.components.Weapon = { - scanner: entityInstance.components.Scanner, - last: 0, - timeout: 0, - } as Weapon; + this.loadVehicle(entityInstance, Valiant, true); + } } break; @@ -160,6 +138,37 @@ export default class World { } + loadVehicle(entityInstance, vehicleConfig, hasWeapon = false) { + entityInstance.components.AI = { + entity: new YUKA.GameEntity(), + tactics: entityInstance.config.components.AI + } as AI; + + const vision = new YUKA.Vision( entityInstance.components.AI.entity ); + vision.range = 1500; + vision.fieldOfView = Math.PI / 2; // 90 degrees + entityInstance.components.AI.entity.vision = vision; + + entityInstance.components.Motion.limits = vehicleConfig.limits; + + entityInstance.components.Scanner = { + range: vehicleConfig.scanner.range, + fieldOfView: vehicleConfig.scanner.fov, + targets: [], + last: 0, + timeout: 0, + } as Scanner; + + if ( hasWeapon ) { + entityInstance.components.Weapon = { + scanner: entityInstance.components.Scanner, + last: 0, + timeout: 0, + } as Weapon; + } + + } + getObjectConfig(objectName) { return this.objectConfigMap.get(objectName); } @@ -195,6 +204,7 @@ export default class World { update() { movementSystem(this.entities, this.fixedDelta); + scannerSystem(this.entities, this.fixedDelta); // Later: call other systems here, e.g., AI, collision, rendering } From 78b29acdb98c702977c218564b9d5c10358f0270 Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 13 May 2026 04:38:11 +0000 Subject: [PATCH 21/23] #31 - Fixing debugging tools --- client/src/app/ui/menus/debugging_tools.js | 8 ++++---- game/ecs/data/scenes/overworld.yml | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/client/src/app/ui/menus/debugging_tools.js b/client/src/app/ui/menus/debugging_tools.js index 176aa054..2194b104 100644 --- a/client/src/app/ui/menus/debugging_tools.js +++ b/client/src/app/ui/menus/debugging_tools.js @@ -73,19 +73,19 @@ export default class Debugging_Tools { expanded: false, } ); - shipState.addBinding( l.scenograph.actors.player.vehicle.controls, 'throttleUp', { + shipState.addBinding( l.scenograph.actors.get('Player One').vehicle.game.components.PlayerInput, 'throttleUp', { readonly: true, interval: 200 } ) - shipState.addBinding( l.scenograph.actors.player.vehicle.controls, 'throttleDown', { + shipState.addBinding( l.scenograph.actors.get('Player One').vehicle.game.components.PlayerInput, 'throttleDown', { readonly: true, interval: 200 } ) - shipState.addBinding( l.scenograph.actors.player.vehicle.controls, 'moveLeft', { + shipState.addBinding( l.scenograph.actors.get('Player One').vehicle.game.components.PlayerInput, 'moveLeft', { readonly: true, interval: 200 } ) - shipState.addBinding( l.scenograph.actors.player.vehicle.controls, 'moveRight', { + shipState.addBinding( l.scenograph.actors.get('Player One').vehicle.game.components.PlayerInput, 'moveRight', { readonly: true, interval: 200 } ) diff --git a/game/ecs/data/scenes/overworld.yml b/game/ecs/data/scenes/overworld.yml index f25f4e7c..d6b4dd1e 100644 --- a/game/ecs/data/scenes/overworld.yml +++ b/game/ecs/data/scenes/overworld.yml @@ -110,9 +110,6 @@ entities: hangarId: hangar_1 FactionStanding: { union: 0.5, winthrom: 1.0, zaar: 0.0 } AI: defensive - Health: - current: 100 - max: 100 - id: pirate_1 components: From b0211f7e340e8f7000b72961fde59c55107c423c Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 13 May 2026 04:54:00 +0000 Subject: [PATCH 22/23] #31 - Restored cargo ship to scene --- client/src/app/scenograph/director.js | 2 +- .../scenograph/objects/vehicles/cargo_ship.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/src/app/scenograph/director.js b/client/src/app/scenograph/director.js index 6eeada70..6043c262 100644 --- a/client/src/app/scenograph/director.js +++ b/client/src/app/scenograph/director.js @@ -271,7 +271,7 @@ export default class Director { if ( entity.config.components.Renderable.object == 'cargoShip' ) { l.scenograph.director.loadObject( entity, - await l.scenograph.objects.vehicles.cargoShip.get() + await l.scenograph.objects.vehicles.cargoShip.get(entity) ); } if ( entity.config.components.Renderable.object == 'pirate' ) { diff --git a/client/src/app/scenograph/objects/vehicles/cargo_ship.js b/client/src/app/scenograph/objects/vehicles/cargo_ship.js index 2f38421e..2075fbea 100644 --- a/client/src/app/scenograph/objects/vehicles/cargo_ship.js +++ b/client/src/app/scenograph/objects/vehicles/cargo_ship.js @@ -14,7 +14,6 @@ import * as YUKA from 'yuka'; import l from '@/helpers/l.js'; import { proceduralMetalMaterial } from '@/scenograph/materials.js'; import { SUBTRACTION, Brush, Evaluator } from 'three-bvh-csg'; -import cargoShip from '../../../../../../game/src/actors/cargoShip'; export default class CargoShip { @@ -33,7 +32,9 @@ export default class CargoShip { // The scale of the mesh. size; + constructor() { + this.instances = []; this.ready = false; this.size = 1000; @@ -105,9 +106,6 @@ export default class CargoShip { mesh.userData.objectClass = 'cargoShip'; mesh.userData.targetable = true; mesh.userData.size = this.size; - mesh.userData.actor = new cargoShip( mesh, l.current_scene.scene ); - - l.scenograph.entityManager.add( mesh.userData.actor.entity ); mesh.matrixAutoUpdate = false; @@ -116,7 +114,14 @@ export default class CargoShip { } - async get() { + async get(actorEntity) { + if (actorEntity) { + // Set internal game accessor to the game world actor entity. + this.game = actorEntity; + l.scenograph.entityManager.add(this.game.components.AI.entity); + console.log(this.game.components.AI.entity) + } + let mesh = this.mesh.clone(); mesh.userData.path = this.getPath(); @@ -136,9 +141,6 @@ export default class CargoShip { mesh.userData.objectClass = 'cargoShip'; mesh.userData.targetable = true; mesh.userData.size = this.size; - mesh.userData.actor = new cargoShip( mesh, l.current_scene.scene ); - - l.scenograph.entityManager.add( mesh.userData.actor.entity ); mesh.matrixAutoUpdate = false; From 1e8cb6ee28c01a4185f17f3198c90a4783c37a9e Mon Sep 17 00:00:00 2001 From: Paul Brzeski Date: Wed, 13 May 2026 05:48:23 +0000 Subject: [PATCH 23/23] #31 - WIP AI system in game engine --- .../scenograph/objects/vehicles/cargo_ship.js | 23 +++++++++---------- game/ecs/systems/ai.ts | 21 +++++++++++++++++ game/ecs/systems/scanner.ts | 10 ++++---- game/ecs/world.ts | 6 +++++ 4 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 game/ecs/systems/ai.ts diff --git a/client/src/app/scenograph/objects/vehicles/cargo_ship.js b/client/src/app/scenograph/objects/vehicles/cargo_ship.js index 2075fbea..8947e075 100644 --- a/client/src/app/scenograph/objects/vehicles/cargo_ship.js +++ b/client/src/app/scenograph/objects/vehicles/cargo_ship.js @@ -115,12 +115,6 @@ export default class CargoShip { } async get(actorEntity) { - if (actorEntity) { - // Set internal game accessor to the game world actor entity. - this.game = actorEntity; - l.scenograph.entityManager.add(this.game.components.AI.entity); - console.log(this.game.components.AI.entity) - } let mesh = this.mesh.clone(); mesh.userData.path = this.getPath(); @@ -134,7 +128,6 @@ export default class CargoShip { mesh.name = 'Cargo Ship #' + ( i + 1 ); - console.log(mesh.name, mesh.position, mesh.userData); mesh.position.copy( mesh.userData.path.current() ); @@ -146,6 +139,12 @@ export default class CargoShip { this.instances.push(mesh); + if (actorEntity) { + // Set internal game accessor to the game world actor entity. + mesh.game = actorEntity; + l.scenograph.entityManager.add(mesh.game.components.AI.entity); + } + return mesh; } @@ -218,13 +217,13 @@ export default class CargoShip { **/ animate( delta ) { - if ( l.current_scene.settings.game_controls ) { - l.scenograph.objects.vehicles.cargoShip.instances.forEach( ( cargo_ship ) => { + // if ( l.current_scene.settings.game_controls ) { + // l.scenograph.objects.vehicles.cargoShip.instances.forEach( ( cargo_ship ) => { - cargo_ship.userData.actor.animate( delta ); + // cargo_ship.game.components.AI.entity.animate( delta ); - } ); - } + // } ); + // } } diff --git a/game/ecs/systems/ai.ts b/game/ecs/systems/ai.ts new file mode 100644 index 00000000..085378b7 --- /dev/null +++ b/game/ecs/systems/ai.ts @@ -0,0 +1,21 @@ +import { AI } from "../components/AI"; +import { Transform } from "../components/Transform"; + +import * as YUKA from 'yuka'; + +export function aiSystem(entities: Map, deltaMs: number) { + const dt = deltaMs / 1000; // convert ms to seconds + + for (const entity of entities.values()) { + const transform = entity.components.Transform as Transform | undefined; + const ai = entity.components.AI as AI | undefined; + + if (transform && ai) { + // Reverse sync for now so things are in the correct place until movement is added + ai.entity.position.x = transform.position.x; + ai.entity.position.y = transform.position.y; + ai.entity.position.z = transform.position.z; + + } + } +} diff --git a/game/ecs/systems/scanner.ts b/game/ecs/systems/scanner.ts index 8acd92d9..846f9415 100644 --- a/game/ecs/systems/scanner.ts +++ b/game/ecs/systems/scanner.ts @@ -23,14 +23,16 @@ export function scannerSystem(entities: Map, deltaMs: number) { if (entity.components.Name === target.components.Name) { continue; } - console.log(entity, target); - debugger; - const targetVisible = entity.components.AI.entity.vision.visible(target.mesh.position) === true; + + // debugger; + const targetVisible = entity.components.AI.entity.vision.visible(target.components.Transform.position) === true; + + if (targetVisible) + console.log(entity, target, targetVisible); } } } - debugger; } // getTargetable() { diff --git a/game/ecs/world.ts b/game/ecs/world.ts index 89a96179..528fe5ae 100644 --- a/game/ecs/world.ts +++ b/game/ecs/world.ts @@ -29,6 +29,7 @@ import { Transform } from "./components/transform"; import { Weapon } from "./components/weapon"; // Systems. +import { aiSystem } from "./systems/ai"; import { movementSystem } from "./systems/movement"; import { scannerSystem } from './systems/scanner'; @@ -203,8 +204,13 @@ export default class World { } update() { + aiSystem(this.entities, this.fixedDelta); movementSystem(this.entities, this.fixedDelta); scannerSystem(this.entities, this.fixedDelta); + + // @todo: move to AI system + + // Later: call other systems here, e.g., AI, collision, rendering }