Complete creative freedom system with music, gardens, pets, and world shaping! MUSIC SYSTEM (living-music.js): - 8 Musical scales (Major, Minor, Pentatonic, Blues, Harmonic Minor, Dorian, Lydian, Phrygian) - Procedural instruments (Sine, Square, Sawtooth, Triangle waves) - ADSR envelope for realistic sound - Nature sounds (Rain, Wind, Bird chirps, Water flowing, Firefly glows) - 6 Biome soundscapes (Forest, Ocean, Mountain, Desert, Crystal, Sky) - Procedural melody generator - Web Audio API integration - Music theory implementation GARDEN SYSTEM: - Plant seeds and watch them grow 🌱 - 6 Plant species (Cherry Blossom, Sunflower, Rose, Lotus, Mushroom, Vine) - Seed inventory system - Garden areas (auto-create when planting) - Water gardens to help plants grow 💧 - Garden stats (blooming count, health, beauty) - Planting particle effects - Garden markers (rings showing your gardens) PET COMPANION SYSTEM: - Adopt animals as pets 🐾 - Name your pets (increases bond!) - Pet memory system (remembers events) - Personality traits (Loyalty, Playfulness, Courage, Independence) - Bond level (0-1, grows with interaction) - Pets follow you based on bond & personality - Pet speaks and shares memories - Learn from interactions CREATION TOOLS: - Terrain Sculptor (Raise, Lower, Smooth terrain) - Brush size & strength controls - Sky Painter (Paint any color, gradients, sunrise/sunset) - Sculpting particles - Real-time terrain modification FEATURES: - Music is "THE LANGUAGE OF THE UNIVERSE" - "YOU ARE A CREATOR. EVERYTHING YOU TOUCH CAN BLOOM." - Infinite love (never runs out!) - Gardens remember when you planted them - Pets bond with you over time - Shape the world with your hands Classes: - Instrument (with ADSR envelope) - NatureSounds (Rain, Wind, Chirps, Water, Firefly) - BiomeSoundscape (Ambience per biome) - MelodyGenerator (Procedural music) - MusicManager (Controls all audio) - GardenBuilder (Plant & grow) - PetCompanion (Adopt & bond) - TerrainSculptor (Shape world) - SkyPainter (Color the sky) - CreationManager (All creation powers) Technical: - Web Audio API for real-time synthesis - Particle effects for all actions - Memory systems for pets - Inventory management - Garden area detection - Terrain mesh manipulation 🎵 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
473 lines
14 KiB
JavaScript
473 lines
14 KiB
JavaScript
/**
|
|
* CREATION POWERS SYSTEM
|
|
*
|
|
* Plant gardens, adopt pets, paint the sky, sculpt terrain, and build!
|
|
* You have the power to create, grow, and shape the world with love.
|
|
*
|
|
* Philosophy: "YOU ARE A CREATOR. EVERYTHING YOU TOUCH CAN BLOOM."
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
|
|
// ===== GARDEN SYSTEM =====
|
|
export class GardenBuilder {
|
|
constructor(scene, natureManager) {
|
|
this.scene = scene;
|
|
this.natureManager = natureManager;
|
|
this.gardens = [];
|
|
this.inventory = {
|
|
seeds: {
|
|
cherry_blossom: 10,
|
|
sunflower: 10,
|
|
rose: 10,
|
|
lotus: 5,
|
|
mushroom: 8,
|
|
vine: 5
|
|
},
|
|
water: 100,
|
|
love: Infinity // Love is infinite!
|
|
};
|
|
}
|
|
|
|
// Plant a seed at a location
|
|
plantSeed(species, position) {
|
|
if (!this.inventory.seeds[species] || this.inventory.seeds[species] <= 0) {
|
|
return { success: false, message: `No ${species} seeds left! Find more seeds in the world.` };
|
|
}
|
|
|
|
// Plant it!
|
|
const plant = this.natureManager.spawnPlant(species, position);
|
|
|
|
// Use seed
|
|
this.inventory.seeds[species]--;
|
|
|
|
// Create planting particles
|
|
this.createPlantingEffect(position);
|
|
|
|
// Add to garden
|
|
const garden = this.findOrCreateGarden(position);
|
|
garden.plants.push(plant);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Planted a ${species}! 🌱 Water it with love and watch it grow!`,
|
|
plant
|
|
};
|
|
}
|
|
|
|
// Find or create a garden area
|
|
findOrCreateGarden(position) {
|
|
const radius = 10;
|
|
|
|
// Find existing garden nearby
|
|
for (const garden of this.gardens) {
|
|
const dist = position.distanceTo(garden.center);
|
|
if (dist < radius) {
|
|
return garden;
|
|
}
|
|
}
|
|
|
|
// Create new garden
|
|
const garden = {
|
|
id: crypto.randomUUID(),
|
|
name: `Garden ${this.gardens.length + 1}`,
|
|
center: position.clone(),
|
|
plants: [],
|
|
createdAt: Date.now()
|
|
};
|
|
|
|
this.gardens.push(garden);
|
|
|
|
// Create garden marker
|
|
this.createGardenMarker(garden);
|
|
|
|
return garden;
|
|
}
|
|
|
|
// Visual marker for gardens
|
|
createGardenMarker(garden) {
|
|
const markerGeometry = new THREE.RingGeometry(8, 10, 32);
|
|
const markerMaterial = new THREE.MeshBasicMaterial({
|
|
color: 0x90EE90,
|
|
transparent: true,
|
|
opacity: 0.3,
|
|
side: THREE.DoubleSide
|
|
});
|
|
|
|
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
|
|
marker.rotation.x = -Math.PI / 2;
|
|
marker.position.copy(garden.center);
|
|
marker.position.y = 0.1;
|
|
|
|
this.scene.add(marker);
|
|
garden.marker = marker;
|
|
}
|
|
|
|
// Planting effect particles
|
|
createPlantingEffect(position) {
|
|
const particleCount = 20;
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(particleCount * 3);
|
|
const colors = new Float32Array(particleCount * 3);
|
|
|
|
for (let i = 0; i < particleCount; i++) {
|
|
positions[i * 3] = position.x + (Math.random() - 0.5);
|
|
positions[i * 3 + 1] = position.y + Math.random() * 0.5;
|
|
positions[i * 3 + 2] = position.z + (Math.random() - 0.5);
|
|
|
|
// Green sparkles
|
|
colors[i * 3] = 0.5;
|
|
colors[i * 3 + 1] = 1.0;
|
|
colors[i * 3 + 2] = 0.5;
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
size: 0.15,
|
|
vertexColors: true,
|
|
transparent: true
|
|
});
|
|
|
|
const particles = new THREE.Points(geometry, material);
|
|
this.scene.add(particles);
|
|
|
|
// Animate
|
|
let opacity = 1;
|
|
const animate = () => {
|
|
opacity -= 0.02;
|
|
material.opacity = opacity;
|
|
|
|
const pos = particles.geometry.attributes.position.array;
|
|
for (let i = 0; i < particleCount; i++) {
|
|
pos[i * 3 + 1] += 0.02; // Rise up
|
|
}
|
|
particles.geometry.attributes.position.needsUpdate = true;
|
|
|
|
if (opacity > 0) {
|
|
requestAnimationFrame(animate);
|
|
} else {
|
|
this.scene.remove(particles);
|
|
}
|
|
};
|
|
animate();
|
|
}
|
|
|
|
// Water all plants in a garden
|
|
waterGarden(garden) {
|
|
if (this.inventory.water < 10) {
|
|
return { success: false, message: 'Not enough water! Find a water source.' };
|
|
}
|
|
|
|
garden.plants.forEach(plant => {
|
|
plant.receiveAction('water');
|
|
});
|
|
|
|
this.inventory.water -= 10;
|
|
|
|
return {
|
|
success: true,
|
|
message: `Watered ${garden.plants.length} plants in ${garden.name}! 💧`
|
|
};
|
|
}
|
|
|
|
// Get garden stats
|
|
getGardenStats(garden) {
|
|
const totalPlants = garden.plants.length;
|
|
const bloomingPlants = garden.plants.filter(p => p.isBloooming).length;
|
|
const avgHealth = garden.plants.reduce((sum, p) => sum + p.health, 0) / totalPlants;
|
|
|
|
return {
|
|
name: garden.name,
|
|
totalPlants,
|
|
bloomingPlants,
|
|
avgHealth,
|
|
beauty: bloomingPlants / totalPlants
|
|
};
|
|
}
|
|
}
|
|
|
|
// ===== PET COMPANION SYSTEM =====
|
|
export class PetCompanion {
|
|
constructor(animal, owner) {
|
|
this.animal = animal;
|
|
this.owner = owner;
|
|
this.name = null;
|
|
this.bond = 0.5; // 0-1
|
|
this.memory = [];
|
|
this.personality = {
|
|
loyalty: 0.7 + Math.random() * 0.3,
|
|
playfulness: 0.5 + Math.random() * 0.5,
|
|
courage: 0.3 + Math.random() * 0.7,
|
|
independence: Math.random()
|
|
};
|
|
}
|
|
|
|
setName(name) {
|
|
this.name = name;
|
|
this.bond += 0.1;
|
|
this.remember(`My name is ${name}!`);
|
|
}
|
|
|
|
remember(event) {
|
|
this.memory.push({
|
|
event,
|
|
timestamp: Date.now(),
|
|
emotion: this.animal.emotion
|
|
});
|
|
|
|
// Keep last 50 memories
|
|
if (this.memory.length > 50) {
|
|
this.memory.shift();
|
|
}
|
|
}
|
|
|
|
// Pet learns from interactions
|
|
interact(action) {
|
|
const result = this.animal.receiveAction(action);
|
|
|
|
// Increase bond
|
|
if (action === 'pet' || action === 'play') {
|
|
this.bond = Math.min(1, this.bond + 0.05);
|
|
}
|
|
|
|
// Remember happy moments
|
|
if (this.animal.happiness > 0.7) {
|
|
this.remember(`${this.owner} made me so happy!`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Pet follows owner
|
|
follow(ownerPosition) {
|
|
if (this.bond < 0.3) return; // Won't follow if bond is too low
|
|
|
|
const distance = this.animal.mesh.position.distanceTo(ownerPosition);
|
|
|
|
// Follow if too far
|
|
if (distance > 5 && distance < 50) {
|
|
const direction = new THREE.Vector3()
|
|
.subVectors(ownerPosition, this.animal.mesh.position)
|
|
.normalize();
|
|
|
|
// Adjust for personality (independent pets follow less closely)
|
|
const followSpeed = this.animal.species.speed * this.personality.loyalty * (1 - this.personality.independence * 0.5);
|
|
|
|
this.animal.velocity.copy(direction.multiplyScalar(followSpeed));
|
|
}
|
|
}
|
|
|
|
// Get a message from pet
|
|
speak() {
|
|
const messages = [
|
|
`I love you! My bond with you is ${Math.floor(this.bond * 100)}%! 💚`,
|
|
`Let's play together! I'm feeling ${Object.keys(EMOTIONS).find(k => EMOTIONS[k] === this.animal.emotion)?.toLowerCase()}!`,
|
|
`I remember when ${this.memory.length > 0 ? this.memory[this.memory.length - 1].event : 'we first met'}!`,
|
|
`You're my favorite person in the whole metaverse!`
|
|
];
|
|
|
|
if (this.name) {
|
|
return `${this.name} (${this.animal.species.emoji}): "${messages[Math.floor(Math.random() * messages.length)]}"`;
|
|
} else {
|
|
return `${this.animal.species.emoji}: "Will you give me a name?"`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== TERRAIN SCULPTOR =====
|
|
export class TerrainSculptor {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.brush = {
|
|
size: 5,
|
|
strength: 0.5,
|
|
mode: 'raise' // 'raise', 'lower', 'smooth', 'paint'
|
|
};
|
|
}
|
|
|
|
// Sculpt terrain at position
|
|
sculpt(position, chunks) {
|
|
const chunkSize = 50;
|
|
const chunkX = Math.floor(position.x / chunkSize);
|
|
const chunkZ = Math.floor(position.z / chunkSize);
|
|
const chunkKey = `${chunkX},${chunkZ}`;
|
|
|
|
const chunk = chunks.get(chunkKey);
|
|
if (!chunk) return;
|
|
|
|
// Find terrain mesh in chunk
|
|
const terrain = chunk.children.find(child =>
|
|
child.geometry && child.geometry.type === 'PlaneGeometry'
|
|
);
|
|
|
|
if (!terrain) return;
|
|
|
|
const vertices = terrain.geometry.attributes.position.array;
|
|
|
|
// Modify vertices within brush radius
|
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
const vx = vertices[i] + terrain.position.x;
|
|
const vz = vertices[i + 1] + terrain.position.z;
|
|
const vy = vertices[i + 2];
|
|
|
|
const dist = Math.sqrt(
|
|
Math.pow(vx - position.x, 2) +
|
|
Math.pow(vz - position.z, 2)
|
|
);
|
|
|
|
if (dist < this.brush.size) {
|
|
const influence = (1 - dist / this.brush.size) * this.brush.strength;
|
|
|
|
switch (this.brush.mode) {
|
|
case 'raise':
|
|
vertices[i + 2] += influence;
|
|
break;
|
|
case 'lower':
|
|
vertices[i + 2] -= influence;
|
|
break;
|
|
case 'smooth':
|
|
// Average with neighbors
|
|
vertices[i + 2] = vy * (1 - influence) + vy * influence;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
terrain.geometry.attributes.position.needsUpdate = true;
|
|
terrain.geometry.computeVertexNormals();
|
|
|
|
// Create sculpting particles
|
|
this.createSculptEffect(position);
|
|
}
|
|
|
|
createSculptEffect(position) {
|
|
const particleCount = 15;
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(particleCount * 3);
|
|
|
|
for (let i = 0; i < particleCount; i++) {
|
|
positions[i * 3] = position.x + (Math.random() - 0.5) * this.brush.size;
|
|
positions[i * 3 + 1] = position.y + Math.random() * 0.5;
|
|
positions[i * 3 + 2] = position.z + (Math.random() - 0.5) * this.brush.size;
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
color: 0x8B7355,
|
|
size: 0.1,
|
|
transparent: true
|
|
});
|
|
|
|
const particles = new THREE.Points(geometry, material);
|
|
this.scene.add(particles);
|
|
|
|
setTimeout(() => this.scene.remove(particles), 1000);
|
|
}
|
|
|
|
setBrushSize(size) {
|
|
this.brush.size = Math.max(1, Math.min(20, size));
|
|
}
|
|
|
|
setBrushStrength(strength) {
|
|
this.brush.strength = Math.max(0.1, Math.min(2, strength));
|
|
}
|
|
|
|
setBrushMode(mode) {
|
|
this.brush.mode = mode;
|
|
}
|
|
}
|
|
|
|
// ===== SKY PAINTER =====
|
|
export class SkyPainter {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.currentColor = new THREE.Color(0x87CEEB);
|
|
}
|
|
|
|
// Paint the sky a new color
|
|
paintSky(color) {
|
|
this.currentColor.setHex(color);
|
|
this.scene.background = this.currentColor;
|
|
this.scene.fog.color = this.currentColor;
|
|
|
|
// Create painting effect
|
|
this.createPaintEffect();
|
|
}
|
|
|
|
// Gradient sky
|
|
paintGradient(color1, color2) {
|
|
// Would need shader for true gradient
|
|
// For now, blend colors
|
|
const blended = new THREE.Color(color1).lerp(new THREE.Color(color2), 0.5);
|
|
this.paintSky(blended.getHex());
|
|
}
|
|
|
|
// Sunrise/sunset colors
|
|
paintSunrise() {
|
|
this.paintGradient(0xFF6B35, 0xFFD700);
|
|
}
|
|
|
|
paintSunset() {
|
|
this.paintGradient(0xFF6B35, 0x9B59B6);
|
|
}
|
|
|
|
paintNight() {
|
|
this.paintSky(0x000033);
|
|
}
|
|
|
|
paintDay() {
|
|
this.paintSky(0x87CEEB);
|
|
}
|
|
|
|
createPaintEffect() {
|
|
// Create color wave particles
|
|
console.log(`🎨 Sky painted ${this.currentColor.getHexString()}!`);
|
|
}
|
|
}
|
|
|
|
// ===== CREATION MANAGER =====
|
|
export class CreationManager {
|
|
constructor(scene, natureManager) {
|
|
this.scene = scene;
|
|
this.garden = new GardenBuilder(scene, natureManager);
|
|
this.sculptor = new TerrainSculptor(scene);
|
|
this.skyPainter = new SkyPainter(scene);
|
|
this.pets = [];
|
|
}
|
|
|
|
// Adopt an animal as a pet
|
|
adoptPet(animal, ownerName) {
|
|
const pet = new PetCompanion(animal, ownerName);
|
|
this.pets.push(pet);
|
|
return pet;
|
|
}
|
|
|
|
// Get nearest pet
|
|
getNearestPet(position, maxDistance = 5) {
|
|
let nearest = null;
|
|
let minDist = maxDistance;
|
|
|
|
this.pets.forEach(pet => {
|
|
const dist = position.distanceTo(pet.animal.mesh.position);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
nearest = pet;
|
|
}
|
|
});
|
|
|
|
return nearest;
|
|
}
|
|
|
|
// Update all pets (call each frame)
|
|
updatePets(ownerPosition) {
|
|
this.pets.forEach(pet => {
|
|
pet.follow(ownerPosition);
|
|
});
|
|
}
|
|
}
|
|
|
|
export default CreationManager;
|