Files
blackroad-metaverse/deploy-temp/pangea-creatures.js
Your Name 5e3404b1cd 🚀 EPIC METAVERSE UPGRADE: All Features Complete
 DESIGN COHESION (40% → 95%)
- Applied official BlackRoad brand colors across ALL HTML files
- Implemented golden ratio spacing system (φ = 1.618)
- Updated CSS variables: --sunrise-orange, --hot-pink, --vivid-purple, --cyber-blue
- Fixed 3D agent colors: Alice (0x0066FF), Aria (0xFF0066), Lucidia (0x7700FF)

📦 NEW PRODUCTION MODULES
- audio-system.js: Procedural music, biome sounds, weather effects
- api-client.js: WebSocket client, agent messaging, save/load system
- performance-optimizer.js: LOD system, object pooling, FPS monitoring

🎯 FILES UPDATED
- universe.html, index.html, pangea.html, ultimate.html

🛠 DEPLOYMENT TOOLS
- deploy-quick.sh: Automated Cloudflare Pages deployment

📚 DOCUMENTATION
- Complete feature documentation and deployment records

🌐 LIVE: https://2bb3d69b.blackroad-metaverse.pages.dev

This commit represents a complete metaverse transformation! 🔥
2026-01-30 15:39:26 -06:00

921 lines
27 KiB
JavaScript

/**
* PANGEA LIVING CREATURES
*
* Animated, AI-driven prehistoric animals with realistic behaviors
* - Autonomous movement and pathfinding
* - Hunting, grazing, swimming behaviors
* - Flocking/herding for social species
* - Day/night activity cycles
* - Territorial behaviors
*/
import * as THREE from 'three';
/**
* CREATURE AI BEHAVIORS
*/
export const BEHAVIORS = {
IDLE: 'idle',
WANDER: 'wander',
GRAZE: 'graze',
HUNT: 'hunt',
FLEE: 'flee',
SWIM: 'swim',
FLY: 'fly',
SLEEP: 'sleep',
DRINK: 'drink',
SOCIALIZE: 'socialize',
TERRITORIAL: 'territorial'
};
/**
* CREATURE TYPES WITH AI PROFILES
*/
export const CREATURE_TYPES = {
LYSTROSAURUS: {
name: 'Lystrosaurus',
type: 'herbivore',
size: 'medium',
speed: 3,
behaviors: [BEHAVIORS.GRAZE, BEHAVIORS.WANDER, BEHAVIORS.SOCIALIZE, BEHAVIORS.FLEE],
social: true,
herdSize: [3, 8],
activeTime: 'day',
diet: ['ferns', 'low_plants'],
soundFrequency: 0.1,
aggression: 0.1,
stamina: 0.7
},
DIMETRODON: {
name: 'Dimetrodon',
type: 'carnivore',
size: 'large',
speed: 4,
behaviors: [BEHAVIORS.HUNT, BEHAVIORS.TERRITORIAL, BEHAVIORS.WANDER, BEHAVIORS.IDLE],
social: false,
herdSize: [1, 1],
activeTime: 'day',
diet: ['small_animals', 'fish'],
soundFrequency: 0.05,
aggression: 0.8,
stamina: 0.6,
huntRange: 30
},
COELOPHYSIS: {
name: 'Coelophysis',
type: 'carnivore',
size: 'small',
speed: 8,
behaviors: [BEHAVIORS.HUNT, BEHAVIORS.WANDER, BEHAVIORS.SOCIALIZE],
social: true,
herdSize: [2, 5],
activeTime: 'day',
diet: ['insects', 'small_animals'],
soundFrequency: 0.2,
aggression: 0.6,
stamina: 0.9
},
CYNOGNATHUS: {
name: 'Cynognathus',
type: 'carnivore',
size: 'medium',
speed: 6,
behaviors: [BEHAVIORS.HUNT, BEHAVIORS.WANDER, BEHAVIORS.TERRITORIAL],
social: false,
herdSize: [1, 2],
activeTime: 'both',
diet: ['small_animals', 'carrion'],
soundFrequency: 0.15,
aggression: 0.7,
stamina: 0.75
},
TEMNOSPONDYL: {
name: 'Temnospondyl',
type: 'carnivore',
size: 'large',
speed: 2,
behaviors: [BEHAVIORS.SWIM, BEHAVIORS.HUNT, BEHAVIORS.IDLE],
social: false,
herdSize: [1, 1],
activeTime: 'both',
diet: ['fish', 'small_animals'],
soundFrequency: 0.08,
aggression: 0.5,
stamina: 0.4,
aquatic: true
},
PTEROSAUR: {
name: 'Pterosaur',
type: 'carnivore',
size: 'medium',
speed: 12,
behaviors: [BEHAVIORS.FLY, BEHAVIORS.HUNT, BEHAVIORS.WANDER],
social: true,
herdSize: [3, 10],
activeTime: 'day',
diet: ['fish', 'insects'],
soundFrequency: 0.3,
aggression: 0.3,
stamina: 0.8,
flying: true
},
PLESIOSAUR: {
name: 'Plesiosaur',
type: 'carnivore',
size: 'large',
speed: 5,
behaviors: [BEHAVIORS.SWIM, BEHAVIORS.HUNT],
social: false,
herdSize: [1, 2],
activeTime: 'both',
diet: ['fish', 'cephalopods'],
soundFrequency: 0.05,
aggression: 0.6,
stamina: 0.7,
aquatic: true,
deepWater: true
},
ICHTHYOSAUR: {
name: 'Ichthyosaur',
type: 'carnivore',
size: 'large',
speed: 10,
behaviors: [BEHAVIORS.SWIM, BEHAVIORS.HUNT],
social: true,
herdSize: [2, 6],
activeTime: 'both',
diet: ['fish', 'squid'],
soundFrequency: 0.1,
aggression: 0.5,
stamina: 0.9,
aquatic: true
}
};
/**
* LIVING CREATURE CLASS
* Individual creature with AI and animation
*/
export class LivingCreature {
constructor(type, position, scene, terrain) {
this.type = CREATURE_TYPES[type];
this.scene = scene;
this.terrain = terrain;
// State
this.position = position.clone();
this.velocity = new THREE.Vector3();
this.rotation = Math.random() * Math.PI * 2;
this.health = 1.0;
this.energy = 1.0;
this.hunger = 0.0;
// AI
this.behavior = BEHAVIORS.WANDER;
this.target = null;
this.targetPosition = null;
this.behaviorTimer = 0;
this.soundTimer = 0;
// Animation
this.animationTime = Math.random() * Math.PI * 2;
this.animationSpeed = 1 + Math.random();
// Create 3D model
this.mesh = this.createMesh(type);
this.mesh.position.copy(position);
this.mesh.rotation.y = this.rotation;
scene.add(this.mesh);
}
createMesh(type) {
const group = new THREE.Group();
switch(type) {
case 'LYSTROSAURUS':
this.createLystrosaurusMesh(group);
break;
case 'DIMETRODON':
this.createDimetrodonMesh(group);
break;
case 'COELOPHYSIS':
this.createCoelophysisMesh(group);
break;
case 'PTEROSAUR':
this.createPterosaurMesh(group);
break;
case 'PLESIOSAUR':
this.createPlesiosaurMesh(group);
break;
case 'ICHTHYOSAUR':
this.createIchthyosaurMesh(group);
break;
default:
this.createGenericMesh(group);
}
return group;
}
createLystrosaurusMesh(group) {
// Body (stocky, low)
const body = new THREE.Mesh(
new THREE.BoxGeometry(1.2, 0.6, 2),
new THREE.MeshStandardMaterial({
color: 0x6b5d4f,
roughness: 0.9
})
);
body.position.y = 0.5;
body.castShadow = true;
group.add(body);
// Head with tusks
const head = new THREE.Mesh(
new THREE.BoxGeometry(0.6, 0.5, 0.7),
new THREE.MeshStandardMaterial({ color: 0x7a6a5a })
);
head.position.set(0, 0.5, 1.2);
head.castShadow = true;
group.add(head);
// Tusks
for (let side of [-1, 1]) {
const tusk = new THREE.Mesh(
new THREE.CylinderGeometry(0.03, 0.03, 0.3, 6),
new THREE.MeshStandardMaterial({ color: 0xf5f5dc })
);
tusk.position.set(side * 0.2, 0.4, 1.4);
tusk.rotation.x = Math.PI / 6;
group.add(tusk);
}
// Legs (animated)
this.legs = [];
for (let i = 0; i < 4; i++) {
const leg = new THREE.Mesh(
new THREE.CylinderGeometry(0.1, 0.08, 0.5, 6),
new THREE.MeshStandardMaterial({ color: 0x6b5d4f })
);
const x = (i % 2 === 0) ? 0.4 : -0.4;
const z = (i < 2) ? 0.7 : -0.7;
leg.position.set(x, 0.25, z);
leg.castShadow = true;
group.add(leg);
this.legs.push(leg);
}
this.bodyParts = { body, head };
}
createDimetrodonMesh(group) {
// Body
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.5, 2.5),
new THREE.MeshStandardMaterial({ color: 0x5a4a3a })
);
body.position.y = 0.6;
body.castShadow = true;
group.add(body);
// Iconic sail
const sail = new THREE.Mesh(
new THREE.PlaneGeometry(3, 1.5),
new THREE.MeshStandardMaterial({
color: 0x8b4513,
side: THREE.DoubleSide,
emissive: 0x331100,
emissiveIntensity: 0.2
})
);
sail.position.y = 1.3;
sail.rotation.x = Math.PI / 2;
sail.castShadow = true;
group.add(sail);
// Head
const head = new THREE.Mesh(
new THREE.ConeGeometry(0.3, 0.8, 8),
new THREE.MeshStandardMaterial({ color: 0x6a5a4a })
);
head.position.set(0, 0.6, 1.6);
head.rotation.z = -Math.PI / 2;
head.castShadow = true;
group.add(head);
// Legs
this.legs = [];
for (let i = 0; i < 4; i++) {
const leg = new THREE.Mesh(
new THREE.CylinderGeometry(0.1, 0.08, 0.6, 6),
new THREE.MeshStandardMaterial({ color: 0x5a4a3a })
);
const x = (i % 2 === 0) ? 0.3 : -0.3;
const z = (i < 2) ? 1 : -0.8;
leg.position.set(x, 0.3, z);
leg.castShadow = true;
group.add(leg);
this.legs.push(leg);
}
this.bodyParts = { body, head, sail };
}
createCoelophysisMesh(group) {
// Body (bipedal)
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.4, 0.5, 1.5),
new THREE.MeshStandardMaterial({ color: 0x6b8e23 })
);
body.position.set(0, 1.2, -0.2);
body.rotation.x = Math.PI / 8;
body.castShadow = true;
group.add(body);
// Neck
const neck = new THREE.Mesh(
new THREE.CylinderGeometry(0.15, 0.2, 0.8, 8),
new THREE.MeshStandardMaterial({ color: 0x6b8e23 })
);
neck.position.set(0, 1.5, 0.5);
neck.rotation.x = -Math.PI / 4;
neck.castShadow = true;
group.add(neck);
// Head
const head = new THREE.Mesh(
new THREE.ConeGeometry(0.2, 0.5, 8),
new THREE.MeshStandardMaterial({ color: 0x7a9e33 })
);
head.position.set(0, 1.8, 1.0);
head.rotation.z = -Math.PI / 2;
head.castShadow = true;
group.add(head);
// Tail
const tail = new THREE.Mesh(
new THREE.CylinderGeometry(0.08, 0.15, 1.5, 8),
new THREE.MeshStandardMaterial({ color: 0x6b8e23 })
);
tail.position.set(0, 1, -1.2);
tail.rotation.x = -Math.PI / 3;
tail.castShadow = true;
group.add(tail);
// Hind legs
this.legs = [];
for (let side of [-1, 1]) {
const leg = new THREE.Mesh(
new THREE.CylinderGeometry(0.1, 0.08, 1.2, 6),
new THREE.MeshStandardMaterial({ color: 0x6b8e23 })
);
leg.position.set(side * 0.2, 0.6, -0.2);
leg.castShadow = true;
group.add(leg);
this.legs.push(leg);
}
// Small arms
for (let side of [-1, 1]) {
const arm = new THREE.Mesh(
new THREE.CylinderGeometry(0.05, 0.04, 0.4, 6),
new THREE.MeshStandardMaterial({ color: 0x6b8e23 })
);
arm.position.set(side * 0.25, 1.3, 0.4);
arm.rotation.x = Math.PI / 3;
group.add(arm);
}
this.bodyParts = { body, head, neck, tail };
}
createPterosaurMesh(group) {
// Body (small, light)
const body = new THREE.Mesh(
new THREE.BoxGeometry(0.3, 0.3, 0.6),
new THREE.MeshStandardMaterial({ color: 0x8b7355 })
);
body.position.y = 0.3;
body.castShadow = true;
group.add(body);
// Head with crest
const head = new THREE.Mesh(
new THREE.ConeGeometry(0.15, 0.4, 8),
new THREE.MeshStandardMaterial({ color: 0x9b8365 })
);
head.position.set(0, 0.3, 0.5);
head.rotation.z = -Math.PI / 2;
head.castShadow = true;
group.add(head);
// Crest
const crest = new THREE.Mesh(
new THREE.PlaneGeometry(0.3, 0.4),
new THREE.MeshStandardMaterial({
color: 0xff6b35,
side: THREE.DoubleSide
})
);
crest.position.set(0, 0.5, 0.5);
crest.rotation.y = Math.PI / 2;
group.add(crest);
// Wings
this.wings = [];
for (let side of [-1, 1]) {
const wing = new THREE.Mesh(
new THREE.PlaneGeometry(2, 1),
new THREE.MeshStandardMaterial({
color: 0x8b7355,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8
})
);
wing.position.set(side * 0.3, 0.3, 0);
wing.rotation.y = side * Math.PI / 6;
group.add(wing);
this.wings.push({ mesh: wing, side });
}
this.bodyParts = { body, head };
}
createPlesiosaurMesh(group) {
// Body
const body = new THREE.Mesh(
new THREE.BoxGeometry(1, 0.8, 2),
new THREE.MeshStandardMaterial({ color: 0x2d5a4a })
);
body.position.y = 0;
group.add(body);
// Long neck
const neckSegments = 5;
this.neckParts = [];
for (let i = 0; i < neckSegments; i++) {
const segment = new THREE.Mesh(
new THREE.CylinderGeometry(0.2, 0.25, 0.4, 8),
new THREE.MeshStandardMaterial({ color: 0x2d5a4a })
);
segment.position.set(0, 0.2, 1 + i * 0.3);
group.add(segment);
this.neckParts.push(segment);
}
// Head
const head = new THREE.Mesh(
new THREE.ConeGeometry(0.25, 0.6, 8),
new THREE.MeshStandardMaterial({ color: 0x3d6a5a })
);
head.position.set(0, 0.2, 2.5);
head.rotation.z = -Math.PI / 2;
group.add(head);
// Flippers
this.flippers = [];
for (let i = 0; i < 4; i++) {
const flipper = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.1, 0.3),
new THREE.MeshStandardMaterial({ color: 0x2d5a4a })
);
const x = (i % 2 === 0) ? 0.7 : -0.7;
const z = (i < 2) ? 0.8 : -0.8;
flipper.position.set(x, 0, z);
group.add(flipper);
this.flippers.push(flipper);
}
this.bodyParts = { body, head };
}
createIchthyosaurMesh(group) {
// Streamlined body
const body = new THREE.Mesh(
new THREE.CylinderGeometry(0.4, 0.2, 3, 12),
new THREE.MeshStandardMaterial({ color: 0x1a3d5a })
);
body.rotation.z = Math.PI / 2;
group.add(body);
// Head
const head = new THREE.Mesh(
new THREE.ConeGeometry(0.4, 0.8, 12),
new THREE.MeshStandardMaterial({ color: 0x2a4d6a })
);
head.position.x = 1.9;
head.rotation.z = -Math.PI / 2;
group.add(head);
// Dorsal fin
const dorsalFin = new THREE.Mesh(
new THREE.PlaneGeometry(0.8, 0.6),
new THREE.MeshStandardMaterial({
color: 0x1a3d5a,
side: THREE.DoubleSide
})
);
dorsalFin.position.set(0, 0.6, 0);
dorsalFin.rotation.x = Math.PI / 2;
group.add(dorsalFin);
// Tail
this.tail = new THREE.Mesh(
new THREE.PlaneGeometry(0.6, 1),
new THREE.MeshStandardMaterial({
color: 0x1a3d5a,
side: THREE.DoubleSide
})
);
this.tail.position.set(-1.7, 0, 0);
this.tail.rotation.y = Math.PI / 2;
group.add(this.tail);
// Flippers
this.flippers = [];
for (let side of [-1, 1]) {
const flipper = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.1, 0.3),
new THREE.MeshStandardMaterial({ color: 0x1a3d5a })
);
flipper.position.set(0.5, 0, side * 0.5);
group.add(flipper);
this.flippers.push(flipper);
}
this.bodyParts = { body, head };
}
createGenericMesh(group) {
const body = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 8, 8),
new THREE.MeshStandardMaterial({ color: 0x8b7355 })
);
body.position.y = 0.5;
body.castShadow = true;
group.add(body);
this.bodyParts = { body };
}
/**
* UPDATE AI AND ANIMATION
*/
update(delta, creatures) {
// Update timers
this.behaviorTimer -= delta;
this.soundTimer -= delta;
this.animationTime += delta * this.animationSpeed;
// Energy and hunger
this.energy = Math.max(0, this.energy - delta * 0.01);
this.hunger = Math.min(1, this.hunger + delta * 0.02);
// Decide behavior
if (this.behaviorTimer <= 0) {
this.decideBehavior(creatures);
this.behaviorTimer = 3 + Math.random() * 7; // 3-10 seconds
}
// Execute behavior
this.executeBehavior(delta);
// Animate
this.animate(delta);
// Update position
this.mesh.position.copy(this.position);
this.mesh.rotation.y = this.rotation;
}
decideBehavior(creatures) {
const behaviors = this.type.behaviors;
// Low energy = rest
if (this.energy < 0.2) {
this.behavior = BEHAVIORS.IDLE;
return;
}
// High hunger = find food
if (this.hunger > 0.7) {
if (this.type.type === 'herbivore') {
this.behavior = BEHAVIORS.GRAZE;
} else {
this.behavior = BEHAVIORS.HUNT;
}
return;
}
// Random behavior from available
this.behavior = behaviors[Math.floor(Math.random() * behaviors.length)];
}
executeBehavior(delta) {
switch(this.behavior) {
case BEHAVIORS.WANDER:
this.wander(delta);
break;
case BEHAVIORS.GRAZE:
this.graze(delta);
break;
case BEHAVIORS.HUNT:
this.hunt(delta);
break;
case BEHAVIORS.SWIM:
this.swim(delta);
break;
case BEHAVIORS.FLY:
this.fly(delta);
break;
case BEHAVIORS.IDLE:
this.idle(delta);
break;
default:
this.wander(delta);
}
}
wander(delta) {
// Random walk
if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 2) {
this.targetPosition = new THREE.Vector3(
this.position.x + (Math.random() - 0.5) * 40,
this.position.y,
this.position.z + (Math.random() - 0.5) * 40
);
}
this.moveToward(this.targetPosition, delta);
}
graze(delta) {
// Move slowly, eating
if (Math.random() < 0.1) {
this.hunger = Math.max(0, this.hunger - 0.1);
}
if (Math.random() < 0.3) {
this.wander(delta);
}
}
hunt(delta) {
// Look for prey (simplified)
if (!this.targetPosition || Math.random() < 0.1) {
this.targetPosition = new THREE.Vector3(
this.position.x + (Math.random() - 0.5) * 60,
this.position.y,
this.position.z + (Math.random() - 0.5) * 60
);
}
this.moveToward(this.targetPosition, delta, this.type.speed * 1.2);
}
swim(delta) {
// Undulating movement in water
if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 3) {
this.targetPosition = new THREE.Vector3(
this.position.x + (Math.random() - 0.5) * 50,
-5 - Math.random() * 20, // Stay underwater
this.position.z + (Math.random() - 0.5) * 50
);
}
this.moveToward(this.targetPosition, delta, this.type.speed);
// Vertical undulation
this.position.y += Math.sin(this.animationTime * 2) * 0.3 * delta;
}
fly(delta) {
// Flying in air
if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 5) {
this.targetPosition = new THREE.Vector3(
this.position.x + (Math.random() - 0.5) * 80,
20 + Math.random() * 30, // High altitude
this.position.z + (Math.random() - 0.5) * 80
);
}
this.moveToward(this.targetPosition, delta, this.type.speed);
}
idle(delta) {
// Just chill
this.energy = Math.min(1, this.energy + delta * 0.05);
}
moveToward(target, delta, speedMultiplier = 1) {
const direction = new THREE.Vector3()
.subVectors(target, this.position)
.normalize();
const speed = this.type.speed * speedMultiplier * delta;
this.velocity.copy(direction).multiplyScalar(speed);
this.position.add(this.velocity);
// Face direction
this.rotation = Math.atan2(direction.x, direction.z);
// Constrain to terrain (if land creature)
if (!this.type.aquatic && !this.type.flying) {
const terrainHeight = this.terrain.getElevation(this.position.x, this.position.z);
if (terrainHeight > 0) {
this.position.y = terrainHeight;
}
}
}
animate(delta) {
// Leg animation (walking)
if (this.legs && this.velocity.length() > 0.1) {
this.legs.forEach((leg, i) => {
const phase = i * Math.PI + this.animationTime * 8;
leg.rotation.x = Math.sin(phase) * 0.5;
});
}
// Wing flapping
if (this.wings) {
this.wings.forEach(wing => {
const flap = Math.sin(this.animationTime * 10) * 0.8;
wing.mesh.rotation.z = wing.side * (Math.PI / 6 + flap);
});
}
// Tail swaying
if (this.tail) {
this.tail.rotation.y = Math.PI / 2 + Math.sin(this.animationTime * 5) * 0.4;
}
// Flipper swimming
if (this.flippers) {
this.flippers.forEach((flipper, i) => {
const phase = i * Math.PI + this.animationTime * 4;
flipper.rotation.x = Math.sin(phase) * 0.6;
});
}
// Neck undulation
if (this.neckParts) {
this.neckParts.forEach((part, i) => {
const wave = Math.sin(this.animationTime * 2 + i * 0.3) * 0.1;
part.position.y = 0.2 + wave;
});
}
// Body bobbing
if (this.bodyParts && this.bodyParts.body) {
this.bodyParts.body.position.y += Math.sin(this.animationTime * 3) * 0.02;
}
}
destroy() {
this.scene.remove(this.mesh);
}
}
/**
* CREATURE MANAGER
* Spawns and manages all creatures in the world
*/
export class CreatureManager {
constructor(scene, terrain) {
this.scene = scene;
this.terrain = terrain;
this.creatures = [];
this.maxCreatures = 50;
this.spawnRadius = 150;
}
spawnCreature(type, position) {
const creature = new LivingCreature(type, position, this.scene, this.terrain);
this.creatures.push(creature);
return creature;
}
spawnInBiome(biome, count = 1) {
// Spawn creatures appropriate for biome
const creatureTypes = this.getCreatureTypesForBiome(biome);
for (let i = 0; i < count; i++) {
if (this.creatures.length >= this.maxCreatures) break;
const type = creatureTypes[Math.floor(Math.random() * creatureTypes.length)];
const position = this.findSpawnPosition(biome);
if (position) {
this.spawnCreature(type, position);
}
}
}
getCreatureTypesForBiome(biome) {
// Match creatures to biomes
const biomeCreatures = {
TROPICAL_RAINFOREST: ['LYSTROSAURUS', 'DIMETRODON', 'COELOPHYSIS'],
ARID_INTERIOR: ['COELOPHYSIS', 'CYNOGNATHUS'],
COASTAL_WETLANDS: ['LYSTROSAURUS', 'TEMNOSPONDYL'],
PANTHALASSA_OCEAN: ['PLESIOSAUR', 'ICHTHYOSAUR'],
TETHYS_SEA: ['ICHTHYOSAUR'],
GONDWANA_POLAR_FOREST: ['LYSTROSAURUS'],
DEFAULT: ['LYSTROSAURUS', 'COELOPHYSIS']
};
return biomeCreatures[biome] || biomeCreatures.DEFAULT;
}
findSpawnPosition(biome) {
// Find valid spawn location
for (let attempts = 0; attempts < 10; attempts++) {
const x = (Math.random() - 0.5) * this.spawnRadius;
const z = (Math.random() - 0.5) * this.spawnRadius;
const y = this.terrain.getElevation(x, z);
// Check if valid for biome
if (biome.includes('OCEAN') || biome.includes('SEA')) {
if (y < 0) {
return new THREE.Vector3(x, y, z);
}
} else {
if (y > 0) {
return new THREE.Vector3(x, y, z);
}
}
}
return null;
}
update(delta) {
// Update all creatures
this.creatures.forEach(creature => {
creature.update(delta, this.creatures);
});
// Remove dead creatures
this.creatures = this.creatures.filter(creature => {
if (creature.health <= 0) {
creature.destroy();
return false;
}
return true;
});
// Spawn new creatures if below threshold
if (this.creatures.length < this.maxCreatures * 0.5) {
this.spawnRandomCreatures(5);
}
}
spawnRandomCreatures(count) {
const types = Object.keys(CREATURE_TYPES);
for (let i = 0; i < count; i++) {
const type = types[Math.floor(Math.random() * types.length)];
const typeData = CREATURE_TYPES[type];
let position;
if (typeData.aquatic) {
position = new THREE.Vector3(
(Math.random() - 0.5) * this.spawnRadius,
-10 - Math.random() * 20,
(Math.random() - 0.5) * this.spawnRadius
);
} else if (typeData.flying) {
position = new THREE.Vector3(
(Math.random() - 0.5) * this.spawnRadius,
20 + Math.random() * 20,
(Math.random() - 0.5) * this.spawnRadius
);
} else {
const x = (Math.random() - 0.5) * this.spawnRadius;
const z = (Math.random() - 0.5) * this.spawnRadius;
const y = this.terrain.getElevation(x, z);
if (y > 0) {
position = new THREE.Vector3(x, y, z);
}
}
if (position) {
this.spawnCreature(type, position);
}
}
}
clearAll() {
this.creatures.forEach(creature => creature.destroy());
this.creatures = [];
}
}
export default CreatureManager;