Skip to content
This repository was archived by the owner on Oct 19, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/app/game/StaticEntityManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as THREE from 'three';

interface StaticEntity {
id: string;
name: string;
position: THREE.Vector3;
rotation: THREE.Euler;
}

export class StaticEntityManager {
private scene: THREE.Scene;
private staticEntities: Map<string, THREE.Mesh> = new Map();

constructor(scene: THREE.Scene) {
this.scene = scene;
}

public async addStaticEntity(entity: StaticEntity): Promise<void> {
if (this.staticEntities.has(entity.id)) {
console.warn(`Static entity with ID ${entity.id} already exists`);
return;
}

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x808080 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(entity.position);
mesh.rotation.copy(entity.rotation);
mesh.userData = {
type: 'staticEntity',
id: entity.id,
name: entity.name
};

this.staticEntities.set(entity.id, mesh);
this.scene.add(mesh);
}

public createPortal(position: THREE.Vector3, destinationMap: string): THREE.Mesh {
Copy link

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portals created here are not tracked in staticEntities, so removeAllStaticEntities or removeStaticEntity won't remove them. You may want to store portal meshes in the manager or provide a separate cleanup method.

Copilot uses AI. Check for mistakes.
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
transparent: true,
opacity: 0.5
});
const portal = new THREE.Mesh(geometry, material);
portal.position.copy(position);
portal.userData = {
type: 'portal',
destinationMap: destinationMap
};
this.scene.add(portal);
return portal;
}

public updateStaticEntityPosition(id: string, position: THREE.Vector3): void {
const entity = this.staticEntities.get(id);
if (entity) {
entity.position.copy(position);
}
}

public removeStaticEntity(id: string): void {
const entity = this.staticEntities.get(id);
if (entity) {
this.scene.remove(entity);
this.staticEntities.delete(id);
}
}

public async removeAllStaticEntities(): Promise<void> {
Copy link

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is marked async but contains no await expressions. You can remove async and return void for clarity.

Suggested change
public async removeAllStaticEntities(): Promise<void> {
public removeAllStaticEntities(): void {

Copilot uses AI. Check for mistakes.
for (const [id] of this.staticEntities) {
this.removeStaticEntity(id);
}
}

public hasStaticEntity(id: string): boolean {
return this.staticEntities.has(id);
}

public getStaticEntityIds(): Map<string, THREE.Mesh> {
return this.staticEntities;
}

public getStaticEntityPosition(id: string): THREE.Vector3 | undefined {
return this.staticEntities.get(id)?.position;
}
}
30 changes: 29 additions & 1 deletion src/app/game/game.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { PlayerData } from '../models/player/PlayerData';
import { AlienManager } from './AlienManager';
import { PlayerManager } from './PlayerManager';
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';
import { StaticEntityManager } from './StaticEntityManager';

@Component({
selector: 'app-game',
Expand All @@ -29,6 +30,7 @@ export class GameComponent implements OnInit, OnDestroy {
public entities: Map<string, PlayerDto> = new Map();
public alienManager?: AlienManager;
public playerManager?: PlayerManager;
public staticEntityManager?: StaticEntityManager;
public stats?: Stats;
public currentMapName?: string;
public playerData: PlayerData | undefined;
Expand All @@ -43,7 +45,7 @@ export class GameComponent implements OnInit, OnDestroy {
constructor(
private hubService: HubService,
private route: ActivatedRoute,
private keyboardService: KeyboardService
public keyboardService: KeyboardService
) {}

ngOnInit(): void {
Expand All @@ -64,6 +66,9 @@ export class GameComponent implements OnInit, OnDestroy {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
if (this.labelRenderer) {
this.labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
}
}

Expand All @@ -76,6 +81,14 @@ export class GameComponent implements OnInit, OnDestroy {
console.warn('HubMessage: Received error:', error);
});

hubService.on('teleportSuccessful', (message: string) => {
console.log('Teleportation successful:', message);
});

hubService.on('teleportFailed', (error: string) => {
Copy link

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're registering teleport event listeners in ngOnInit but not removing them in ngOnDestroy. Add corresponding off calls to prevent potential memory leaks.

Copilot uses AI. Check for mistakes.
console.error('Teleportation failed:', error);
});

hubService.on('loginSuccessful', (response: PlayerData) => {
console.log('HubMessage: Login successful:', response);
this.playerData = response;
Expand Down Expand Up @@ -149,6 +162,7 @@ export class GameComponent implements OnInit, OnDestroy {
await initializeThreeJs(this);
this.playerManager = new PlayerManager(this.scene!);
this.alienManager = new AlienManager(this.scene!);
this.staticEntityManager = new StaticEntityManager(this.scene!);
this.setupPerformanceMeters();
this.animate();
}
Expand All @@ -172,6 +186,16 @@ export class GameComponent implements OnInit, OnDestroy {
this.alienManager.animate();
}

// Update player position for portal detection
if (this.playerData && this.playerManager) {
const playerPosition = this.playerManager.getPlayerPosition(
this.playerData.id
);
if (playerPosition) {
this.keyboardService.setPlayerPosition(playerPosition);
}
}

// Update orbit controls to follow player if available
if (this.playerData && this.playerManager && this.controls) {
const playerPosition = this.playerManager.getPlayerPosition(
Expand Down Expand Up @@ -232,6 +256,10 @@ export class GameComponent implements OnInit, OnDestroy {
if (this.scene) {
this.scene.clear();
}
this.keyboardService.clearPortals();
if (this.staticEntityManager) {
this.staticEntityManager.removeAllStaticEntities();
}
}

// Public method to handle player movement
Expand Down
81 changes: 76 additions & 5 deletions src/app/game/game.utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';
import { PlayerDto, SpaceMapData, AlienDto } from './types/SpaceMapData';
import {
PlayerDto,
SpaceMapData,
AlienDto,
StaticEntityDto,
} from './types/SpaceMapData';
import { GameComponent } from './game.component';

// Define layers
Expand Down Expand Up @@ -124,20 +129,20 @@ export async function loadNewSpacemap(
spaceMapData: SpaceMapData
): Promise<void> {
if (component.isLoadingSpacemap) {
console.log('Skipping spacemap update - still loading previous map');
return;
}

try {
component.isLoadingSpacemap = true;
console.log('Loading new space map: ', spaceMapData);
console.log('Loading new spacemap: ', spaceMapData);
component.currentMapName = spaceMapData.mapName;
await clearScene(component);
await loadMapEnvironment(component, spaceMapData);

// Load players and aliens for the new map
// Load players, aliens, and static entities for the new map
Copy link

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial map loading uses loadStaticEntities only and does not create portal meshes, so portals won’t be available until the map is updated. Consider invoking portal creation logic on the first load or reusing updateSpacemap flow.

Copilot uses AI. Check for mistakes.
await loadPlayers(spaceMapData.mapObject.players, component);
await loadAliens(spaceMapData.mapObject.aliens, component);
await loadStaticEntities(spaceMapData.mapObject.staticEntities, component);
Copy link

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loadStaticEntities function is declared to accept StaticEntityDto[] but is being passed an array that may include PortalDto items. Consider updating the signature to accept the union type (StaticEntityDto | PortalDto)[] or filter out portal entries before calling.

Suggested change
await loadStaticEntities(spaceMapData.mapObject.staticEntities, component);
const filteredStaticEntities = spaceMapData.mapObject.staticEntities.filter(
(entity): entity is StaticEntityDto => 'staticEntitySpecificProperty' in entity
);
await loadStaticEntities(filteredStaticEntities, component);

Copilot uses AI. Check for mistakes.
} finally {
component.isLoadingSpacemap = false;
}
Expand Down Expand Up @@ -384,17 +389,83 @@ export async function loadAliens(aliens: AlienDto[], component: GameComponent) {
}
}

export async function loadStaticEntities(
staticEntities: StaticEntityDto[],
component: GameComponent
): Promise<void> {
if (!component.staticEntityManager) {
console.error('Static entity manager not initialized');
return;
}

for (const entity of staticEntities) {
const position = parsePositionDTOtoVector3(entity.position);
await component.staticEntityManager.addStaticEntity({
id: entity.id,
name: entity.name,
position: position,
rotation: new THREE.Euler(0, 0, 0),
});
}
}

export async function updateSpacemap(
component: GameComponent,
spaceMapData: SpaceMapData
): Promise<void> {
if (component.isLoadingSpacemap) {
console.log('Skipping spacemap update - still loading previous map');
return;
}

try {
component.isLoadingSpacemap = true;

// Clear existing portals
component.keyboardService.clearPortals();

// Handle static entities and portals
if (component.staticEntityManager) {
const staticEntities = spaceMapData.mapObject.staticEntities;
const currentStaticEntityIds = new Set(
staticEntities.map((entity) => entity.id)
);

// Remove static entities that are no longer in the map
for (const [id] of component.staticEntityManager.getStaticEntityIds()) {
if (!currentStaticEntityIds.has(id)) {
component.staticEntityManager.removeStaticEntity(id);
}
}

// Add or update static entities
for (const entity of staticEntities) {
const position = parsePositionDTOtoVector3(entity.position);
if (component.staticEntityManager.hasStaticEntity(entity.id)) {
component.staticEntityManager.updateStaticEntityPosition(
entity.id,
position
);
} else {
if ('destinationMap' in entity) {
// This is a portal
const portalMesh = component.staticEntityManager.createPortal(
position,
entity.destinationMap
);
component.keyboardService.addPortal(portalMesh);
} else {
// This is a regular static entity
await component.staticEntityManager.addStaticEntity({
id: entity.id,
name: entity.name,
position: position,
rotation: new THREE.Euler(0, 0, 0),
});
}
}
}
}

const entities = component.entities;
const entitiesIn = spaceMapData.mapObject.players;

Expand Down
13 changes: 11 additions & 2 deletions src/app/game/services/hub.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ export class HubService {
});
}

public on<T = unknown>(event: string, callback: (...args: T[]) => void): void {
public on<T = unknown>(
event: string,
callback: (...args: T[]) => void
): void {
this.hubConnection?.on(event, callback);
}

public off<T = unknown>(event: string, callback: (...args: T[]) => void): void {
public off<T = unknown>(
event: string,
callback: (...args: T[]) => void
): void {
this.hubConnection?.off(event, callback);
}

Expand Down Expand Up @@ -73,6 +79,9 @@ type ServerRequestTypes = {
z: number;
};
};
requestTeleport: {
destinationMap: string;
};
};

type RequestLogin = {
Expand Down
Loading