393 lines
12 KiB
JavaScript
393 lines
12 KiB
JavaScript
/**
|
|
* 🏛️ STRUCTURE SYSTEM
|
|
* Generates and manages structures across the 500x500 world
|
|
* - Creates buildings, ruins, landmarks across biomes
|
|
* - Handles roads and paths between biomes
|
|
* - Biome-aware structure placement
|
|
*/
|
|
|
|
class StructureSystem {
|
|
constructor(worldWidth, worldHeight, biomeSystem, riverSystem, lakeSystem) {
|
|
this.worldWidth = worldWidth;
|
|
this.worldHeight = worldHeight;
|
|
this.biomeSystem = biomeSystem;
|
|
this.riverSystem = riverSystem;
|
|
this.lakeSystem = lakeSystem;
|
|
|
|
// Structure map (marks where structures are)
|
|
this.structureMap = Array(worldHeight).fill(null).map(() => Array(worldWidth).fill(null));
|
|
|
|
// Road map (marks where roads are)
|
|
this.roadMap = Array(worldHeight).fill(null).map(() => Array(worldWidth).fill(false));
|
|
|
|
// List of all structures
|
|
this.structures = [];
|
|
|
|
// List of all landmarks
|
|
this.landmarks = [];
|
|
|
|
// Road paths
|
|
this.roads = [];
|
|
|
|
// Structure types by biome
|
|
this.structureTypes = {
|
|
'Grassland': ['farm', 'house', 'barn', 'windmill', 'well'],
|
|
'Forest': ['cabin', 'ruins', 'treehouse', 'camp', 'shrine'],
|
|
'Desert': ['pyramid', 'ruins', 'oasis_camp', 'tomb', 'pillar'],
|
|
'Mountain': ['mine', 'cave', 'tower', 'ruins', 'altar'],
|
|
'Swamp': ['hut', 'ruins', 'totem', 'bog_shrine', 'abandoned_dock']
|
|
};
|
|
|
|
// Landmark types (unique, 1-3 per world)
|
|
this.landmarkTypes = [
|
|
{ type: 'ancient_temple', biome: 'Forest', count: 1 },
|
|
{ type: 'great_pyramid', biome: 'Desert', count: 1 },
|
|
{ type: 'mountain_peak', biome: 'Mountain', count: 1 },
|
|
{ type: 'abandoned_city', biome: 'Grassland', count: 1 },
|
|
{ type: 'dragon_skeleton', biome: 'Swamp', count: 1 }
|
|
];
|
|
}
|
|
|
|
// Generate all structures and roads
|
|
generateAll() {
|
|
console.log('🏛️ StructureSystem: Starting structure generation...');
|
|
|
|
// 1. Generate roads between biome centers
|
|
this.generateRoads();
|
|
|
|
// 2. Generate landmarks (major points of interest)
|
|
this.generateLandmarks();
|
|
|
|
// 3. Generate regular structures
|
|
this.generateStructures();
|
|
|
|
console.log(`✅ Generated ${this.structures.length} structures, ${this.landmarks.length} landmarks, ${this.roads.length} roads`);
|
|
}
|
|
|
|
// Generate roads connecting biome centers
|
|
generateRoads() {
|
|
console.log('🛤️ Generating roads...');
|
|
|
|
// Find biome centers
|
|
const biomeLocations = {
|
|
'Grassland': [],
|
|
'Forest': [],
|
|
'Desert': [],
|
|
'Mountain': [],
|
|
'Swamp': []
|
|
};
|
|
|
|
// Sample the world to find biome centers
|
|
const sampleRate = 50;
|
|
for (let x = 0; x < this.worldWidth; x += sampleRate) {
|
|
for (let y = 0; y < this.worldHeight; y += sampleRate) {
|
|
const biome = this.biomeSystem.getBiome(x, y);
|
|
if (!biomeLocations[biome]) biomeLocations[biome] = [];
|
|
biomeLocations[biome].push({ x, y });
|
|
}
|
|
}
|
|
|
|
// Create main roads connecting different biomes
|
|
const roadPoints = [];
|
|
|
|
// Central hub (spawn point)
|
|
roadPoints.push({ x: 250, y: 250, name: 'Center' });
|
|
|
|
// Add one major location per biome
|
|
for (const [biomeName, locations] of Object.entries(biomeLocations)) {
|
|
if (locations.length > 0) {
|
|
// Pick central location
|
|
const centerIdx = Math.floor(locations.length / 2);
|
|
roadPoints.push({
|
|
x: locations[centerIdx].x,
|
|
y: locations[centerIdx].y,
|
|
name: `${biomeName} Hub`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Connect road points
|
|
for (let i = 0; i < roadPoints.length; i++) {
|
|
const start = roadPoints[i];
|
|
|
|
// Connect to nearest 2-3 other points
|
|
const distances = roadPoints
|
|
.map((point, idx) => ({
|
|
idx,
|
|
dist: Math.sqrt((point.x - start.x) ** 2 + (point.y - start.y) ** 2)
|
|
}))
|
|
.filter(d => d.idx !== i)
|
|
.sort((a, b) => a.dist - b.dist);
|
|
|
|
// Connect to 1-2 nearest points
|
|
const connectCount = Math.min(2, distances.length);
|
|
for (let j = 0; j < connectCount; j++) {
|
|
const end = roadPoints[distances[j].idx];
|
|
this.createRoad(start, end);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a road between two points
|
|
createRoad(start, end) {
|
|
const path = [];
|
|
const dx = end.x - start.x;
|
|
const dy = end.y - start.y;
|
|
const steps = Math.max(Math.abs(dx), Math.abs(dy));
|
|
|
|
// Create path with some randomness (looks more natural)
|
|
for (let i = 0; i <= steps; i++) {
|
|
const t = i / steps;
|
|
let x = Math.round(start.x + dx * t);
|
|
let y = Math.round(start.y + dy * t);
|
|
|
|
// Add slight curve (Perlin-like noise)
|
|
const noise = Math.sin(t * Math.PI * 3) * 10;
|
|
x += Math.round(noise);
|
|
|
|
path.push({ x, y });
|
|
}
|
|
|
|
// Mark road tiles (3 tiles wide)
|
|
for (const point of path) {
|
|
for (let offsetX = -1; offsetX <= 1; offsetX++) {
|
|
for (let offsetY = -1; offsetY <= 1; offsetY++) {
|
|
const x = point.x + offsetX;
|
|
const y = point.y + offsetY;
|
|
|
|
if (x >= 0 && x < this.worldWidth && y >= 0 && y < this.worldHeight) {
|
|
// Don't place roads over water
|
|
if (!this.riverSystem.isRiver(x, y) && !this.lakeSystem.isLake(x, y)) {
|
|
this.roadMap[y][x] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.roads.push({
|
|
start: start.name || 'unknown',
|
|
end: end.name || 'unknown',
|
|
path
|
|
});
|
|
}
|
|
|
|
// Generate landmarks (unique structures)
|
|
generateLandmarks() {
|
|
console.log('🗿 Generating landmarks...');
|
|
|
|
for (const landmarkDef of this.landmarkTypes) {
|
|
for (let i = 0; i < landmarkDef.count; i++) {
|
|
const location = this.findBiomeLocation(landmarkDef.biome, 100, 100);
|
|
if (location) {
|
|
this.createLandmark(landmarkDef.type, location.x, location.y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a single landmark
|
|
createLandmark(type, x, y) {
|
|
const size = 15; // Landmarks are large (15x15)
|
|
|
|
// Mark area as occupied
|
|
for (let dx = -size; dx <= size; dx++) {
|
|
for (let dy = -size; dy <= size; dy++) {
|
|
const tx = x + dx;
|
|
const ty = y + dy;
|
|
if (tx >= 0 && tx < this.worldWidth && ty >= 0 && ty < this.worldHeight) {
|
|
this.structureMap[ty][tx] = { type: 'landmark', landmarkType: type };
|
|
}
|
|
}
|
|
}
|
|
|
|
this.landmarks.push({
|
|
type,
|
|
x,
|
|
y,
|
|
size,
|
|
discovered: false
|
|
});
|
|
}
|
|
|
|
// Generate regular structures
|
|
generateStructures() {
|
|
console.log('🏠 Generating structures...');
|
|
|
|
const structureCount = 80; // 80 structures across the world
|
|
let placed = 0;
|
|
let attempts = 0;
|
|
const maxAttempts = structureCount * 10;
|
|
|
|
while (placed < structureCount && attempts < maxAttempts) {
|
|
attempts++;
|
|
|
|
// Random location
|
|
const x = Math.floor(Math.random() * this.worldWidth);
|
|
const y = Math.floor(Math.random() * this.worldHeight);
|
|
|
|
// Check if location is valid
|
|
if (this.canPlaceStructure(x, y, 20)) {
|
|
const biome = this.biomeSystem.getBiome(x, y);
|
|
const types = this.structureTypes[biome];
|
|
|
|
if (types && types.length > 0) {
|
|
const type = types[Math.floor(Math.random() * types.length)];
|
|
this.createStructure(type, x, y, biome);
|
|
placed++;
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Placed ${placed} structures (${attempts} attempts)`);
|
|
}
|
|
|
|
// Check if structure can be placed at location
|
|
canPlaceStructure(x, y, minDistance) {
|
|
// Check if on water
|
|
if (this.riverSystem.isRiver(x, y) || this.lakeSystem.isLake(x, y)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if too close to other structures
|
|
for (const structure of this.structures) {
|
|
const dist = Math.sqrt((structure.x - x) ** 2 + (structure.y - y) ** 2);
|
|
if (dist < minDistance) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if too close to landmarks
|
|
for (const landmark of this.landmarks) {
|
|
const dist = Math.sqrt((landmark.x - x) ** 2 + (landmark.y - y) ** 2);
|
|
if (dist < 50) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Create a single structure
|
|
createStructure(type, x, y, biome) {
|
|
const size = this.getStructureSize(type);
|
|
|
|
// Mark area as occupied
|
|
for (let dx = -size; dx <= size; dx++) {
|
|
for (let dy = -size; dy <= size; dy++) {
|
|
const tx = x + dx;
|
|
const ty = y + dy;
|
|
if (tx >= 0 && tx < this.worldWidth && ty >= 0 && ty < this.worldHeight) {
|
|
this.structureMap[ty][tx] = { type: 'structure', structureType: type };
|
|
}
|
|
}
|
|
}
|
|
|
|
this.structures.push({
|
|
type,
|
|
x,
|
|
y,
|
|
size,
|
|
biome,
|
|
explored: false
|
|
});
|
|
}
|
|
|
|
// Get structure size
|
|
getStructureSize(type) {
|
|
const sizes = {
|
|
// Small structures
|
|
'well': 2,
|
|
'camp': 3,
|
|
'totem': 2,
|
|
'pillar': 2,
|
|
|
|
// Medium structures
|
|
'house': 4,
|
|
'cabin': 4,
|
|
'hut': 3,
|
|
'shrine': 4,
|
|
'altar': 4,
|
|
'tomb': 5,
|
|
|
|
// Large structures
|
|
'farm': 7,
|
|
'barn': 6,
|
|
'windmill': 5,
|
|
'ruins': 6,
|
|
'mine': 5,
|
|
'tower': 4,
|
|
'pyramid': 8,
|
|
'cave': 5,
|
|
'treehouse': 4,
|
|
'oasis_camp': 5,
|
|
'bog_shrine': 4,
|
|
'abandoned_dock': 5
|
|
};
|
|
|
|
return sizes[type] || 4;
|
|
}
|
|
|
|
// Find a location in specific biome
|
|
findBiomeLocation(biomeName, minDistance, maxAttempts) {
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const x = Math.floor(Math.random() * this.worldWidth);
|
|
const y = Math.floor(Math.random() * this.worldHeight);
|
|
|
|
const biome = this.biomeSystem.getBiome(x, y);
|
|
|
|
if (biome === biomeName && this.canPlaceStructure(x, y, minDistance)) {
|
|
return { x, y };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check if tile is a road
|
|
isRoad(x, y) {
|
|
if (x < 0 || x >= this.worldWidth || y < 0 || y >= this.worldHeight) {
|
|
return false;
|
|
}
|
|
return this.roadMap[y][x];
|
|
}
|
|
|
|
// Get structure at tile
|
|
getStructure(x, y) {
|
|
if (x < 0 || x >= this.worldWidth || y < 0 || y >= this.worldHeight) {
|
|
return null;
|
|
}
|
|
return this.structureMap[y][x];
|
|
}
|
|
|
|
// Get statistics
|
|
getStats() {
|
|
return {
|
|
structures: this.structures.length,
|
|
landmarks: this.landmarks.length,
|
|
roads: this.roads.length,
|
|
roadTiles: this.roadMap.flat().filter(r => r).length
|
|
};
|
|
}
|
|
|
|
// Export data for saving
|
|
exportData() {
|
|
return {
|
|
structures: this.structures,
|
|
landmarks: this.landmarks,
|
|
roads: this.roads,
|
|
structureMap: this.structureMap,
|
|
roadMap: this.roadMap
|
|
};
|
|
}
|
|
|
|
// Import data from save
|
|
importData(data) {
|
|
this.structures = data.structures || [];
|
|
this.landmarks = data.landmarks || [];
|
|
this.roads = data.roads || [];
|
|
this.structureMap = data.structureMap || this.structureMap;
|
|
this.roadMap = data.roadMap || this.roadMap;
|
|
}
|
|
}
|