update
This commit is contained in:
parent
c39ab3ac41
commit
e9585f8570
@ -113,9 +113,17 @@ async def websocket_endpoint(websocket: WebSocket, nickname: str):
|
|||||||
player_id = await ws_manager.get_player_id(websocket)
|
player_id = await ws_manager.get_player_id(websocket)
|
||||||
if player_id:
|
if player_id:
|
||||||
player = game_state.get_or_create_player(nickname, player_id)
|
player = game_state.get_or_create_player(nickname, player_id)
|
||||||
|
|
||||||
|
# Trigger economy update on login to ensure player stats are current
|
||||||
|
# This is especially important after server restarts
|
||||||
|
logger.info(f"Player {player_id} connected, triggering economy update to sync stats.")
|
||||||
|
await trigger_economy_update_and_save()
|
||||||
|
|
||||||
|
# Send updated player data after economy sync
|
||||||
|
updated_player = game_state.players.get(player_id, player)
|
||||||
await websocket.send_json({
|
await websocket.send_json({
|
||||||
"type": "init",
|
"type": "init",
|
||||||
"player": player.to_dict(),
|
"player": updated_player.to_dict(),
|
||||||
"game_state": game_state.get_state()
|
"game_state": game_state.get_state()
|
||||||
})
|
})
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export class GameRenderer {
|
|||||||
// Map of building labels
|
// Map of building labels
|
||||||
|
|
||||||
this.hoveredTile = null;
|
this.hoveredTile = null;
|
||||||
this.cameraPos = { x: 0, y: 50, z: 50 };
|
this.cameraPos = { x: 0, y: 0, z: 0 };
|
||||||
this.cameraZoom = 1; // Re-introduced for proper orthographic zoom
|
this.cameraZoom = 1; // Re-introduced for proper orthographic zoom
|
||||||
|
|
||||||
this.TILE_SIZE = 2;
|
this.TILE_SIZE = 2;
|
||||||
@ -33,7 +33,8 @@ export class GameRenderer {
|
|||||||
this.camera = new THREE.OrthographicCamera(
|
this.camera = new THREE.OrthographicCamera(
|
||||||
-40, 40, 30, -30, 0.1, 1000
|
-40, 40, 30, -30, 0.1, 1000
|
||||||
);
|
);
|
||||||
this.camera.position.set(0, 50, 50);
|
// Transport Tycoon style isometric view
|
||||||
|
this.camera.position.set(50, 50, 50);
|
||||||
this.camera.lookAt(0, 0, 0);
|
this.camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
// Create renderer
|
// Create renderer
|
||||||
@ -43,17 +44,36 @@ export class GameRenderer {
|
|||||||
});
|
});
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
this.renderer.shadowMap.enabled = true;
|
this.renderer.shadowMap.enabled = true;
|
||||||
|
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows
|
||||||
|
|
||||||
// Add lights
|
// Add lights
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4); // Reduced ambient for better shadows
|
||||||
this.scene.add(ambientLight);
|
this.scene.add(ambientLight);
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0); // Increased intensity
|
||||||
directionalLight.position.set(10, 20, 10);
|
directionalLight.position.set(30, 40, 30); // Higher position for better shadows
|
||||||
directionalLight.castShadow = true;
|
directionalLight.castShadow = true;
|
||||||
|
|
||||||
|
// Configure shadow properties for better quality
|
||||||
|
directionalLight.shadow.mapSize.width = 2048;
|
||||||
|
directionalLight.shadow.mapSize.height = 2048;
|
||||||
|
directionalLight.shadow.camera.near = 0.5;
|
||||||
|
directionalLight.shadow.camera.far = 200;
|
||||||
|
|
||||||
|
// Set shadow camera bounds to cover the play area
|
||||||
|
const shadowDistance = 50;
|
||||||
|
directionalLight.shadow.camera.left = -shadowDistance;
|
||||||
|
directionalLight.shadow.camera.right = shadowDistance;
|
||||||
|
directionalLight.shadow.camera.top = shadowDistance;
|
||||||
|
directionalLight.shadow.camera.bottom = -shadowDistance;
|
||||||
|
|
||||||
|
directionalLight.shadow.bias = -0.0001; // Reduce shadow acne
|
||||||
|
|
||||||
this.scene.add(directionalLight);
|
this.scene.add(directionalLight);
|
||||||
// Create ground
|
// Create ground
|
||||||
this.createGround();
|
this.createGround();
|
||||||
|
// Create grid
|
||||||
|
this.createGrid();
|
||||||
// Handle window resize
|
// Handle window resize
|
||||||
window.addEventListener('resize', () => this.onResize());
|
window.addEventListener('resize', () => this.onResize());
|
||||||
}
|
}
|
||||||
@ -67,6 +87,49 @@ export class GameRenderer {
|
|||||||
this.scene.add(ground);
|
this.scene.add(ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createGrid() {
|
||||||
|
const gridGroup = new THREE.Group();
|
||||||
|
const gridSize = 200; // Grid extends from -100 to +100
|
||||||
|
const divisions = gridSize / this.TILE_SIZE; // Number of divisions
|
||||||
|
|
||||||
|
// Create grid lines material - subtle gray
|
||||||
|
const gridMaterial = new THREE.LineBasicMaterial({
|
||||||
|
color: 0x666666,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.3
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create vertical lines (parallel to Z axis)
|
||||||
|
// Offset by half tile size so grid squares are centered on buildings
|
||||||
|
for (let i = -divisions/2; i <= divisions/2; i++) {
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
|
const x = (i * this.TILE_SIZE) - (this.TILE_SIZE / 2);
|
||||||
|
const vertices = new Float32Array([
|
||||||
|
x, 0.02, -gridSize/2, // Start point
|
||||||
|
x, 0.02, gridSize/2 // End point
|
||||||
|
]);
|
||||||
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||||
|
const line = new THREE.Line(geometry, gridMaterial);
|
||||||
|
gridGroup.add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create horizontal lines (parallel to X axis)
|
||||||
|
// Offset by half tile size so grid squares are centered on buildings
|
||||||
|
for (let i = -divisions/2; i <= divisions/2; i++) {
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
|
const z = (i * this.TILE_SIZE) - (this.TILE_SIZE / 2);
|
||||||
|
const vertices = new Float32Array([
|
||||||
|
-gridSize/2, 0.02, z, // Start point
|
||||||
|
gridSize/2, 0.02, z // End point
|
||||||
|
]);
|
||||||
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||||
|
const line = new THREE.Line(geometry, gridMaterial);
|
||||||
|
gridGroup.add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene.add(gridGroup);
|
||||||
|
}
|
||||||
|
|
||||||
createTile(x, y, color = 0x90EE90) {
|
createTile(x, y, color = 0x90EE90) {
|
||||||
const geometry = new THREE.PlaneGeometry(this.TILE_SIZE - 0.1, this.TILE_SIZE - 0.1);
|
const geometry = new THREE.PlaneGeometry(this.TILE_SIZE - 0.1, this.TILE_SIZE - 0.1);
|
||||||
const material = new THREE.MeshBasicMaterial({
|
const material = new THREE.MeshBasicMaterial({
|
||||||
@ -87,27 +150,36 @@ export class GameRenderer {
|
|||||||
// Get player color for accents
|
// Get player color for accents
|
||||||
const playerColor = this.getPlayerColor(owner_id);
|
const playerColor = this.getPlayerColor(owner_id);
|
||||||
|
|
||||||
|
// Determine building orientation based on adjacent roads
|
||||||
|
const orientation = this.getBuildingOrientation(x, y, type);
|
||||||
|
|
||||||
// Create building group
|
// Create building group
|
||||||
const buildingGroup = new THREE.Group();
|
const buildingGroup = new THREE.Group();
|
||||||
buildingGroup.position.set(x * this.TILE_SIZE, 0, y * this.TILE_SIZE);
|
buildingGroup.position.set(x * this.TILE_SIZE, 0, y * this.TILE_SIZE);
|
||||||
buildingGroup.userData = { x, y, owner_id, type, name };
|
buildingGroup.userData = { x, y, owner_id, type, name };
|
||||||
|
|
||||||
|
// Add foundation/base for all non-road buildings
|
||||||
|
if (type !== 'road') {
|
||||||
|
const foundation = this.createTileFoundation();
|
||||||
|
buildingGroup.add(foundation);
|
||||||
|
}
|
||||||
|
|
||||||
// Create building based on type
|
// Create building based on type
|
||||||
let buildingMesh;
|
let buildingMesh;
|
||||||
if (type.includes('house')) {
|
if (type.includes('house')) {
|
||||||
buildingMesh = this.createHouse(type, playerColor);
|
buildingMesh = this.createHouse(type, playerColor, orientation);
|
||||||
} else if (type.includes('shop') || type === 'supermarket' || type === 'mall') {
|
} else if (type.includes('shop') || type === 'supermarket' || type === 'mall') {
|
||||||
buildingMesh = this.createCommercialBuilding(type, playerColor);
|
buildingMesh = this.createCommercialBuilding(type, playerColor, orientation);
|
||||||
} else if (type.includes('factory')) {
|
} else if (type.includes('factory')) {
|
||||||
buildingMesh = this.createFactory(type, playerColor);
|
buildingMesh = this.createFactory(type, playerColor, orientation);
|
||||||
} else if (type === 'road') {
|
} else if (type === 'road') {
|
||||||
buildingMesh = this.createRoad();
|
buildingMesh = this.createRoad();
|
||||||
} else if (type === 'park' || type === 'plaza') {
|
} else if (type === 'park' || type === 'plaza') {
|
||||||
buildingMesh = this.createPark(type, playerColor);
|
buildingMesh = this.createPark(type, playerColor);
|
||||||
} else if (type === 'town_hall') {
|
} else if (type === 'town_hall') {
|
||||||
buildingMesh = this.createTownHall(playerColor);
|
buildingMesh = this.createTownHall(playerColor, orientation);
|
||||||
} else if (type === 'power_plant') {
|
} else if (type === 'power_plant') {
|
||||||
buildingMesh = this.createPowerPlant(playerColor);
|
buildingMesh = this.createPowerPlant(playerColor, orientation);
|
||||||
} else {
|
} else {
|
||||||
// Fallback to simple building
|
// Fallback to simple building
|
||||||
buildingMesh = this.createSimpleBuilding(2, 0x808080);
|
buildingMesh = this.createSimpleBuilding(2, 0x808080);
|
||||||
@ -126,6 +198,78 @@ export class GameRenderer {
|
|||||||
return new THREE.Color(0xff6b6b); // Default reddish color
|
return new THREE.Color(0xff6b6b); // Default reddish color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBuildingOrientation(x, y, buildingType) {
|
||||||
|
// Roads and parks don't need orientation
|
||||||
|
if (buildingType === 'road' || buildingType === 'park' || buildingType === 'plaza') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check adjacent tiles for roads
|
||||||
|
const adjacentRoads = this.getAdjacentRoads(x, y);
|
||||||
|
|
||||||
|
// If no roads, face south (default)
|
||||||
|
if (adjacentRoads.length === 0) {
|
||||||
|
return 0; // 0 = south, 90 = west, 180 = north, 270 = east
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority order: south, east, north, west
|
||||||
|
// Buildings prefer to face south, then east, etc.
|
||||||
|
const priorities = ['south', 'east', 'north', 'west'];
|
||||||
|
|
||||||
|
for (const direction of priorities) {
|
||||||
|
if (adjacentRoads.includes(direction)) {
|
||||||
|
switch (direction) {
|
||||||
|
case 'south': return 0; // Face south (positive Z)
|
||||||
|
case 'east': return 270; // Face east (positive X)
|
||||||
|
case 'north': return 180; // Face north (negative Z)
|
||||||
|
case 'west': return 90; // Face west (negative X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Default to south
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdjacentRoads(x, y) {
|
||||||
|
const roads = [];
|
||||||
|
if (!this.gameState || !this.gameState.buildings) return roads;
|
||||||
|
|
||||||
|
const directions = [
|
||||||
|
{ dir: 'north', dx: 0, dy: -1 },
|
||||||
|
{ dir: 'south', dx: 0, dy: 1 },
|
||||||
|
{ dir: 'east', dx: 1, dy: 0 },
|
||||||
|
{ dir: 'west', dx: -1, dy: 0 }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { dir, dx, dy } of directions) {
|
||||||
|
const checkX = x + dx;
|
||||||
|
const checkY = y + dy;
|
||||||
|
const key = `${checkX},${checkY}`;
|
||||||
|
const building = this.gameState.buildings[key];
|
||||||
|
|
||||||
|
if (building && building.type === 'road') {
|
||||||
|
roads.push(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roads;
|
||||||
|
}
|
||||||
|
|
||||||
|
createTileFoundation() {
|
||||||
|
// Create a full tile-sized foundation with road color
|
||||||
|
const geometry = new THREE.BoxGeometry(
|
||||||
|
this.TILE_SIZE,
|
||||||
|
0.05,
|
||||||
|
this.TILE_SIZE
|
||||||
|
);
|
||||||
|
const material = new THREE.MeshLambertMaterial({ color: 0x2F4F4F }); // Same as road color
|
||||||
|
const foundation = new THREE.Mesh(geometry, material);
|
||||||
|
foundation.position.y = 0.025; // Half the foundation height
|
||||||
|
foundation.castShadow = true;
|
||||||
|
foundation.receiveShadow = true;
|
||||||
|
return foundation;
|
||||||
|
}
|
||||||
|
|
||||||
createSimpleBuilding(height, color) {
|
createSimpleBuilding(height, color) {
|
||||||
const geometry = new THREE.BoxGeometry(
|
const geometry = new THREE.BoxGeometry(
|
||||||
this.TILE_SIZE - 0.2,
|
this.TILE_SIZE - 0.2,
|
||||||
@ -140,7 +284,7 @@ export class GameRenderer {
|
|||||||
return building;
|
return building;
|
||||||
}
|
}
|
||||||
|
|
||||||
createHouse(type, playerColor) {
|
createHouse(type, playerColor, orientation = 0) {
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
// Determine house size and height
|
// Determine house size and height
|
||||||
@ -179,11 +323,29 @@ export class GameRenderer {
|
|||||||
roof.castShadow = true;
|
roof.castShadow = true;
|
||||||
group.add(roof);
|
group.add(roof);
|
||||||
|
|
||||||
// Door
|
// Door - position based on orientation (always faces road)
|
||||||
const doorGeometry = new THREE.BoxGeometry(0.3, 0.6, 0.05);
|
const doorGeometry = new THREE.BoxGeometry(0.3, 0.6, 0.05);
|
||||||
const doorMaterial = new THREE.MeshLambertMaterial({ color: 0x8b4513 });
|
const doorMaterial = new THREE.MeshLambertMaterial({ color: 0x8b4513 });
|
||||||
const door = new THREE.Mesh(doorGeometry, doorMaterial);
|
const door = new THREE.Mesh(doorGeometry, doorMaterial);
|
||||||
|
|
||||||
|
// Position door based on orientation
|
||||||
|
switch (orientation) {
|
||||||
|
case 0: // Face south (positive Z)
|
||||||
door.position.set(0, 0.3, houseDepth / 2 + 0.02);
|
door.position.set(0, 0.3, houseDepth / 2 + 0.02);
|
||||||
|
break;
|
||||||
|
case 90: // Face west (negative X)
|
||||||
|
door.position.set(-houseWidth / 2 - 0.02, 0.3, 0);
|
||||||
|
door.rotation.y = Math.PI / 2;
|
||||||
|
break;
|
||||||
|
case 180: // Face north (negative Z)
|
||||||
|
door.position.set(0, 0.3, -houseDepth / 2 - 0.02);
|
||||||
|
door.rotation.y = Math.PI;
|
||||||
|
break;
|
||||||
|
case 270: // Face east (positive X)
|
||||||
|
door.position.set(houseWidth / 2 + 0.02, 0.3, 0);
|
||||||
|
door.rotation.y = -Math.PI / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
group.add(door);
|
group.add(door);
|
||||||
|
|
||||||
// Windows with player color frames
|
// Windows with player color frames
|
||||||
@ -211,9 +373,25 @@ export class GameRenderer {
|
|||||||
return windowGroup;
|
return windowGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add windows
|
// Add windows - position based on orientation (front-facing)
|
||||||
|
switch (orientation) {
|
||||||
|
case 0: // Face south (positive Z)
|
||||||
group.add(createWindow(-houseWidth/3, wallHeight/2, houseDepth/2 + 0.02));
|
group.add(createWindow(-houseWidth/3, wallHeight/2, houseDepth/2 + 0.02));
|
||||||
group.add(createWindow(houseWidth/3, wallHeight/2, houseDepth/2 + 0.02));
|
group.add(createWindow(houseWidth/3, wallHeight/2, houseDepth/2 + 0.02));
|
||||||
|
break;
|
||||||
|
case 90: // Face west (negative X)
|
||||||
|
group.add(createWindow(-houseWidth/2 - 0.02, wallHeight/2, -houseDepth/3));
|
||||||
|
group.add(createWindow(-houseWidth/2 - 0.02, wallHeight/2, houseDepth/3));
|
||||||
|
break;
|
||||||
|
case 180: // Face north (negative Z)
|
||||||
|
group.add(createWindow(-houseWidth/3, wallHeight/2, -houseDepth/2 - 0.02));
|
||||||
|
group.add(createWindow(houseWidth/3, wallHeight/2, -houseDepth/2 - 0.02));
|
||||||
|
break;
|
||||||
|
case 270: // Face east (positive X)
|
||||||
|
group.add(createWindow(houseWidth/2 + 0.02, wallHeight/2, -houseDepth/3));
|
||||||
|
group.add(createWindow(houseWidth/2 + 0.02, wallHeight/2, houseDepth/3));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Chimney for medium and large houses
|
// Chimney for medium and large houses
|
||||||
if (type !== 'small_house') {
|
if (type !== 'small_house') {
|
||||||
@ -228,7 +406,7 @@ export class GameRenderer {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
createCommercialBuilding(type, playerColor) {
|
createCommercialBuilding(type, playerColor, orientation = 0) {
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
let width = 1.6;
|
let width = 1.6;
|
||||||
@ -255,14 +433,11 @@ export class GameRenderer {
|
|||||||
building.receiveShadow = true;
|
building.receiveShadow = true;
|
||||||
group.add(building);
|
group.add(building);
|
||||||
|
|
||||||
// Storefront sign - player color
|
// Storefront elements - position based on orientation
|
||||||
const signGeometry = new THREE.BoxGeometry(width * 0.9, 0.3, 0.05);
|
const signGeometry = new THREE.BoxGeometry(width * 0.9, 0.3, 0.05);
|
||||||
const signMaterial = new THREE.MeshLambertMaterial({ color: signColor });
|
const signMaterial = new THREE.MeshLambertMaterial({ color: signColor });
|
||||||
const sign = new THREE.Mesh(signGeometry, signMaterial);
|
const sign = new THREE.Mesh(signGeometry, signMaterial);
|
||||||
sign.position.set(0, height * 0.8, depth / 2 + 0.02);
|
|
||||||
group.add(sign);
|
|
||||||
|
|
||||||
// Large windows
|
|
||||||
const windowGeometry = new THREE.BoxGeometry(width * 0.7, height * 0.4, 0.05);
|
const windowGeometry = new THREE.BoxGeometry(width * 0.7, height * 0.4, 0.05);
|
||||||
const windowMaterial = new THREE.MeshLambertMaterial({
|
const windowMaterial = new THREE.MeshLambertMaterial({
|
||||||
color: 0x4169e1,
|
color: 0x4169e1,
|
||||||
@ -270,14 +445,46 @@ export class GameRenderer {
|
|||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
});
|
});
|
||||||
const windows = new THREE.Mesh(windowGeometry, windowMaterial);
|
const windows = new THREE.Mesh(windowGeometry, windowMaterial);
|
||||||
windows.position.set(0, height * 0.3, depth / 2 + 0.01);
|
|
||||||
group.add(windows);
|
|
||||||
|
|
||||||
// Entrance door
|
|
||||||
const doorGeometry = new THREE.BoxGeometry(0.4, 0.8, 0.05);
|
const doorGeometry = new THREE.BoxGeometry(0.4, 0.8, 0.05);
|
||||||
const doorMaterial = new THREE.MeshLambertMaterial({ color: playerColor });
|
const doorMaterial = new THREE.MeshLambertMaterial({ color: playerColor });
|
||||||
const door = new THREE.Mesh(doorGeometry, doorMaterial);
|
const door = new THREE.Mesh(doorGeometry, doorMaterial);
|
||||||
|
|
||||||
|
// Position storefront based on orientation
|
||||||
|
switch (orientation) {
|
||||||
|
case 0: // Face south (positive Z)
|
||||||
|
sign.position.set(0, height * 0.8, depth / 2 + 0.02);
|
||||||
|
windows.position.set(0, height * 0.3, depth / 2 + 0.01);
|
||||||
door.position.set(0, 0.4, depth / 2 + 0.02);
|
door.position.set(0, 0.4, depth / 2 + 0.02);
|
||||||
|
break;
|
||||||
|
case 90: // Face west (negative X)
|
||||||
|
sign.position.set(-width / 2 - 0.02, height * 0.8, 0);
|
||||||
|
sign.rotation.y = Math.PI / 2;
|
||||||
|
windows.position.set(-width / 2 - 0.01, height * 0.3, 0);
|
||||||
|
windows.rotation.y = Math.PI / 2;
|
||||||
|
door.position.set(-width / 2 - 0.02, 0.4, 0);
|
||||||
|
door.rotation.y = Math.PI / 2;
|
||||||
|
break;
|
||||||
|
case 180: // Face north (negative Z)
|
||||||
|
sign.position.set(0, height * 0.8, -depth / 2 - 0.02);
|
||||||
|
sign.rotation.y = Math.PI;
|
||||||
|
windows.position.set(0, height * 0.3, -depth / 2 - 0.01);
|
||||||
|
windows.rotation.y = Math.PI;
|
||||||
|
door.position.set(0, 0.4, -depth / 2 - 0.02);
|
||||||
|
door.rotation.y = Math.PI;
|
||||||
|
break;
|
||||||
|
case 270: // Face east (positive X)
|
||||||
|
sign.position.set(width / 2 + 0.02, height * 0.8, 0);
|
||||||
|
sign.rotation.y = -Math.PI / 2;
|
||||||
|
windows.position.set(width / 2 + 0.01, height * 0.3, 0);
|
||||||
|
windows.rotation.y = -Math.PI / 2;
|
||||||
|
door.position.set(width / 2 + 0.02, 0.4, 0);
|
||||||
|
door.rotation.y = -Math.PI / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.add(sign);
|
||||||
|
group.add(windows);
|
||||||
group.add(door);
|
group.add(door);
|
||||||
|
|
||||||
// Air conditioning units on roof for larger buildings
|
// Air conditioning units on roof for larger buildings
|
||||||
@ -296,7 +503,7 @@ export class GameRenderer {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
createFactory(type, playerColor) {
|
createFactory(type, playerColor, orientation = 0) {
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
let width = 1.6;
|
let width = 1.6;
|
||||||
@ -373,6 +580,7 @@ export class GameRenderer {
|
|||||||
const material = new THREE.MeshLambertMaterial({ color: 0x2F4F4F });
|
const material = new THREE.MeshLambertMaterial({ color: 0x2F4F4F });
|
||||||
const road = new THREE.Mesh(geometry, material);
|
const road = new THREE.Mesh(geometry, material);
|
||||||
road.position.y = 0.05;
|
road.position.y = 0.05;
|
||||||
|
road.castShadow = true;
|
||||||
road.receiveShadow = true;
|
road.receiveShadow = true;
|
||||||
|
|
||||||
return road;
|
return road;
|
||||||
@ -442,7 +650,7 @@ export class GameRenderer {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
createTownHall(playerColor) {
|
createTownHall(playerColor, orientation = 0) {
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
// Main building
|
// Main building
|
||||||
@ -499,7 +707,7 @@ export class GameRenderer {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
createPowerPlant(playerColor) {
|
createPowerPlant(playerColor, orientation = 0) {
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
// Main building
|
// Main building
|
||||||
@ -595,6 +803,35 @@ export class GameRenderer {
|
|||||||
const building = this.createBuilding(buildingData);
|
const building = this.createBuilding(buildingData);
|
||||||
this.buildings.set(key, building);
|
this.buildings.set(key, building);
|
||||||
this.scene.add(building);
|
this.scene.add(building);
|
||||||
|
|
||||||
|
// If this is a road, update adjacent building orientations
|
||||||
|
if (buildingData.type === 'road') {
|
||||||
|
this.updateAdjacentBuildingOrientations(buildingData.x, buildingData.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAdjacentBuildingOrientations(roadX, roadY) {
|
||||||
|
if (!this.gameState || !this.gameState.buildings) return;
|
||||||
|
|
||||||
|
const directions = [
|
||||||
|
{ dx: 0, dy: -1 }, // North
|
||||||
|
{ dx: 0, dy: 1 }, // South
|
||||||
|
{ dx: 1, dy: 0 }, // East
|
||||||
|
{ dx: -1, dy: 0 } // West
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { dx, dy } of directions) {
|
||||||
|
const checkX = roadX + dx;
|
||||||
|
const checkY = roadY + dy;
|
||||||
|
const key = `${checkX},${checkY}`;
|
||||||
|
const buildingData = this.gameState.buildings[key];
|
||||||
|
|
||||||
|
if (buildingData && buildingData.type !== 'road') {
|
||||||
|
// Rebuild this building with new orientation
|
||||||
|
this.removeBuilding(checkX, checkY);
|
||||||
|
this.addBuilding(buildingData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBuilding(x, y) {
|
removeBuilding(x, y) {
|
||||||
@ -683,10 +920,17 @@ export class GameRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCameraPosition() {
|
updateCameraPosition() {
|
||||||
|
// Transport Tycoon style isometric positioning
|
||||||
this.camera.position.set(
|
this.camera.position.set(
|
||||||
this.cameraPos.x, this.cameraPos.y, this.cameraPos.z
|
this.cameraPos.x + 50,
|
||||||
|
50,
|
||||||
|
this.cameraPos.z + 50
|
||||||
|
);
|
||||||
|
this.camera.lookAt(
|
||||||
|
this.cameraPos.x,
|
||||||
|
0,
|
||||||
|
this.cameraPos.z
|
||||||
);
|
);
|
||||||
this.camera.lookAt(this.cameraPos.x, 0, this.cameraPos.z - 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startRenderLoop() {
|
startRenderLoop() {
|
||||||
|
|||||||
@ -119,19 +119,19 @@ export class InputHandler {
|
|||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
this.app.renderer.moveCamera(s, -s); // Move diagonally to pan straight up
|
this.app.renderer.moveCamera(0, -s); // Move straight up in world coordinates
|
||||||
moved = true;
|
moved = true;
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
this.app.renderer.moveCamera(-s, s); // Move diagonally to pan straight down
|
this.app.renderer.moveCamera(0, s); // Move straight down in world coordinates
|
||||||
moved = true;
|
moved = true;
|
||||||
break;
|
break;
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
this.app.renderer.moveCamera(-s, -s); // Move diagonally to pan straight left
|
this.app.renderer.moveCamera(-s, 0); // Move straight left in world coordinates
|
||||||
moved = true;
|
moved = true;
|
||||||
break;
|
break;
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
this.app.renderer.moveCamera(s, s); // Move diagonally to pan straight right
|
this.app.renderer.moveCamera(s, 0); // Move straight right in world coordinates
|
||||||
moved = true;
|
moved = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user