Add Crafting, Dialogue, and World Evolution Systems 🛠️💬🌍
CRAFTING & BUILDING SYSTEM (~800 lines): - 13 resources (wood, stone, crystal, sand, stardust, essence, dreamweave, etc.) - 18 crafting recipes across 4 categories (materials, tools, building, magical) - Full inventory system with stacking (40 slots) - Real-time crafting with progress tracking - Building system with 9 block types (walls, floors, doors, windows, pillars, stairs) - Placement mode with grid snapping - Blueprint save/load system - Gathering system with bonuses - Material refunds on demolition - Unlock system (recipes unlock by level) DIALOGUE & STORY SYSTEM (~550 lines): - Branching conversations with Alice, Aria, Lucidia - 8 node types (say, choice, branch, action, quest, item, emotion, end) - Relationship tracking with all 3 AI guardians - Dynamic story events (first love, enlightenment, pet bonding, etc.) - Narrative state tracking (love, creation, discovery, enlightenment) - Player archetype system (Nurturer, Creator, Seeker, Sage, Wanderer) - Conversation history - Choice-driven outcomes - Emergent narrative generation WORLD EVOLUTION SYSTEM (~650 lines): - 4 seasons (spring, summer, autumn, winter) with automatic cycling - Season affects: colors, temperature, weather, plant growth, animal activity - 10+ world events (meteor shower, aurora, rainbow, super bloom, time rift, etc.) - Event trigger system (time, season, weather, thresholds, random) - Ecosystem manager (plant health, animal population, water, soil quality) - 4 ecosystem states (thriving, balanced, struggling, endangered) - World memory system (remembers 1000 events) - 5 evolution stages (Awakening → Infinite) - Dynamic world aging - Collective player impact on world state Features: - Craft 18 items from basic tools to magical artifacts - Build complete structures with 9 block types - Deep conversations with meaningful choices - World responds to collective love and creation - Seasons change environment and gameplay - Random magical events create wonder - Ecosystem requires care or deteriorates - World evolves based on player actions Philosophy: 'YOU ARE A CREATOR. STORIES EMERGE FROM CHOICES. THE WORLD IS ALIVE.'
This commit is contained in:
832
crafting-building.js
Normal file
832
crafting-building.js
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
/**
|
||||||
|
* CRAFTING & BUILDING SYSTEM
|
||||||
|
*
|
||||||
|
* Gather resources, craft items, and build anything you can imagine.
|
||||||
|
* Complete freedom to create structures, tools, and magical items.
|
||||||
|
*
|
||||||
|
* Philosophy: "YOU ARE A CREATOR. BUILD YOUR DREAMS INTO REALITY."
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
// ===== RESOURCE TYPES =====
|
||||||
|
export const RESOURCES = {
|
||||||
|
// Natural Resources
|
||||||
|
wood: {
|
||||||
|
id: 'wood',
|
||||||
|
name: 'Wood',
|
||||||
|
description: 'Sturdy timber from trees',
|
||||||
|
emoji: '🪵',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['tree', 'forest']
|
||||||
|
},
|
||||||
|
stone: {
|
||||||
|
id: 'stone',
|
||||||
|
name: 'Stone',
|
||||||
|
description: 'Solid rock material',
|
||||||
|
emoji: '🪨',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['mountain', 'cave']
|
||||||
|
},
|
||||||
|
crystal: {
|
||||||
|
id: 'crystal',
|
||||||
|
name: 'Crystal',
|
||||||
|
description: 'Glowing crystal shard',
|
||||||
|
emoji: '💎',
|
||||||
|
rarity: 'rare',
|
||||||
|
stackSize: 50,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['crystal_cavern', 'mountain_peak']
|
||||||
|
},
|
||||||
|
sand: {
|
||||||
|
id: 'sand',
|
||||||
|
name: 'Sand',
|
||||||
|
description: 'Fine desert sand',
|
||||||
|
emoji: '⌛',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['desert', 'beach']
|
||||||
|
},
|
||||||
|
water: {
|
||||||
|
id: 'water',
|
||||||
|
name: 'Water',
|
||||||
|
description: 'Pure water',
|
||||||
|
emoji: '💧',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 10,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['ocean', 'river', 'rain']
|
||||||
|
},
|
||||||
|
fiber: {
|
||||||
|
id: 'fiber',
|
||||||
|
name: 'Plant Fiber',
|
||||||
|
description: 'Woven plant fibers',
|
||||||
|
emoji: '🌾',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['grass', 'plants']
|
||||||
|
},
|
||||||
|
|
||||||
|
// Processed Materials
|
||||||
|
plank: {
|
||||||
|
id: 'plank',
|
||||||
|
name: 'Wood Plank',
|
||||||
|
description: 'Processed wooden plank',
|
||||||
|
emoji: '📏',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
craftable: true
|
||||||
|
},
|
||||||
|
brick: {
|
||||||
|
id: 'brick',
|
||||||
|
name: 'Stone Brick',
|
||||||
|
description: 'Carved stone brick',
|
||||||
|
emoji: '🧱',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 99,
|
||||||
|
craftable: true
|
||||||
|
},
|
||||||
|
glass: {
|
||||||
|
id: 'glass',
|
||||||
|
name: 'Glass',
|
||||||
|
description: 'Transparent glass pane',
|
||||||
|
emoji: '🪟',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
stackSize: 50,
|
||||||
|
craftable: true
|
||||||
|
},
|
||||||
|
rope: {
|
||||||
|
id: 'rope',
|
||||||
|
name: 'Rope',
|
||||||
|
description: 'Strong twisted rope',
|
||||||
|
emoji: '🪢',
|
||||||
|
rarity: 'common',
|
||||||
|
stackSize: 50,
|
||||||
|
craftable: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Magical Materials
|
||||||
|
stardust: {
|
||||||
|
id: 'stardust',
|
||||||
|
name: 'Stardust',
|
||||||
|
description: 'Shimmering cosmic dust',
|
||||||
|
emoji: '✨',
|
||||||
|
rarity: 'legendary',
|
||||||
|
stackSize: 20,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['sky_island', 'night_sky']
|
||||||
|
},
|
||||||
|
essence: {
|
||||||
|
id: 'essence',
|
||||||
|
name: 'Life Essence',
|
||||||
|
description: 'Pure concentrated life energy',
|
||||||
|
emoji: '💚',
|
||||||
|
rarity: 'epic',
|
||||||
|
stackSize: 10,
|
||||||
|
gatherable: true,
|
||||||
|
sources: ['ancient_tree', 'garden']
|
||||||
|
},
|
||||||
|
dreamweave: {
|
||||||
|
id: 'dreamweave',
|
||||||
|
name: 'Dreamweave',
|
||||||
|
description: 'Fabric woven from dreams',
|
||||||
|
emoji: '🌈',
|
||||||
|
rarity: 'legendary',
|
||||||
|
stackSize: 5,
|
||||||
|
craftable: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== CRAFTING RECIPES =====
|
||||||
|
export const RECIPES = {
|
||||||
|
// Basic Materials
|
||||||
|
plank: {
|
||||||
|
id: 'plank',
|
||||||
|
output: { item: 'plank', quantity: 4 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'wood', quantity: 1 }
|
||||||
|
],
|
||||||
|
craftingTime: 1,
|
||||||
|
category: 'materials',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
brick: {
|
||||||
|
id: 'brick',
|
||||||
|
output: { item: 'brick', quantity: 2 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'stone', quantity: 2 }
|
||||||
|
],
|
||||||
|
craftingTime: 2,
|
||||||
|
category: 'materials',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
glass: {
|
||||||
|
id: 'glass',
|
||||||
|
output: { item: 'glass', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'sand', quantity: 4 }
|
||||||
|
],
|
||||||
|
craftingTime: 3,
|
||||||
|
category: 'materials',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
rope: {
|
||||||
|
id: 'rope',
|
||||||
|
output: { item: 'rope', quantity: 2 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'fiber', quantity: 5 }
|
||||||
|
],
|
||||||
|
craftingTime: 2,
|
||||||
|
category: 'materials',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tools
|
||||||
|
axe: {
|
||||||
|
id: 'axe',
|
||||||
|
output: { item: 'axe', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'wood', quantity: 3 },
|
||||||
|
{ item: 'stone', quantity: 2 }
|
||||||
|
],
|
||||||
|
craftingTime: 5,
|
||||||
|
category: 'tools',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
pickaxe: {
|
||||||
|
id: 'pickaxe',
|
||||||
|
output: { item: 'pickaxe', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'wood', quantity: 2 },
|
||||||
|
{ item: 'stone', quantity: 3 }
|
||||||
|
],
|
||||||
|
craftingTime: 5,
|
||||||
|
category: 'tools',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
shovel: {
|
||||||
|
id: 'shovel',
|
||||||
|
output: { item: 'shovel', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'wood', quantity: 2 },
|
||||||
|
{ item: 'stone', quantity: 1 }
|
||||||
|
],
|
||||||
|
craftingTime: 4,
|
||||||
|
category: 'tools',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building Blocks
|
||||||
|
wooden_wall: {
|
||||||
|
id: 'wooden_wall',
|
||||||
|
output: { item: 'wooden_wall', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'plank', quantity: 4 }
|
||||||
|
],
|
||||||
|
craftingTime: 2,
|
||||||
|
category: 'building',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
stone_wall: {
|
||||||
|
id: 'stone_wall',
|
||||||
|
output: { item: 'stone_wall', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'brick', quantity: 6 }
|
||||||
|
],
|
||||||
|
craftingTime: 3,
|
||||||
|
category: 'building',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
crystal_wall: {
|
||||||
|
id: 'crystal_wall',
|
||||||
|
output: { item: 'crystal_wall', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'crystal', quantity: 3 },
|
||||||
|
{ item: 'stone', quantity: 2 }
|
||||||
|
],
|
||||||
|
craftingTime: 5,
|
||||||
|
category: 'building',
|
||||||
|
unlocked: false,
|
||||||
|
requirement: { level: 5 }
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
id: 'window',
|
||||||
|
output: { item: 'window', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'glass', quantity: 2 },
|
||||||
|
{ item: 'plank', quantity: 2 }
|
||||||
|
],
|
||||||
|
craftingTime: 3,
|
||||||
|
category: 'building',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
door: {
|
||||||
|
id: 'door',
|
||||||
|
output: { item: 'door', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'plank', quantity: 6 }
|
||||||
|
],
|
||||||
|
craftingTime: 4,
|
||||||
|
category: 'building',
|
||||||
|
unlocked: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Magical Items
|
||||||
|
love_crystal: {
|
||||||
|
id: 'love_crystal',
|
||||||
|
output: { item: 'love_crystal', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'crystal', quantity: 5 },
|
||||||
|
{ item: 'essence', quantity: 3 },
|
||||||
|
{ item: 'stardust', quantity: 1 }
|
||||||
|
],
|
||||||
|
craftingTime: 10,
|
||||||
|
category: 'magical',
|
||||||
|
unlocked: false,
|
||||||
|
requirement: { level: 10 }
|
||||||
|
},
|
||||||
|
portal_stone: {
|
||||||
|
id: 'portal_stone',
|
||||||
|
output: { item: 'portal_stone', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'crystal', quantity: 10 },
|
||||||
|
{ item: 'stardust', quantity: 5 }
|
||||||
|
],
|
||||||
|
craftingTime: 15,
|
||||||
|
category: 'magical',
|
||||||
|
unlocked: false,
|
||||||
|
requirement: { level: 15 }
|
||||||
|
},
|
||||||
|
dreamweave: {
|
||||||
|
id: 'dreamweave',
|
||||||
|
output: { item: 'dreamweave', quantity: 1 },
|
||||||
|
ingredients: [
|
||||||
|
{ item: 'fiber', quantity: 10 },
|
||||||
|
{ item: 'stardust', quantity: 3 },
|
||||||
|
{ item: 'essence', quantity: 2 }
|
||||||
|
],
|
||||||
|
craftingTime: 20,
|
||||||
|
category: 'magical',
|
||||||
|
unlocked: false,
|
||||||
|
requirement: { level: 20 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== BUILDING BLOCKS =====
|
||||||
|
export const BUILDING_BLOCKS = {
|
||||||
|
wooden_wall: {
|
||||||
|
id: 'wooden_wall',
|
||||||
|
name: 'Wooden Wall',
|
||||||
|
size: { x: 2, y: 3, z: 0.2 },
|
||||||
|
color: 0x8B4513,
|
||||||
|
material: 'wood',
|
||||||
|
health: 100
|
||||||
|
},
|
||||||
|
stone_wall: {
|
||||||
|
id: 'stone_wall',
|
||||||
|
name: 'Stone Wall',
|
||||||
|
size: { x: 2, y: 3, z: 0.3 },
|
||||||
|
color: 0x808080,
|
||||||
|
material: 'stone',
|
||||||
|
health: 200
|
||||||
|
},
|
||||||
|
crystal_wall: {
|
||||||
|
id: 'crystal_wall',
|
||||||
|
name: 'Crystal Wall',
|
||||||
|
size: { x: 2, y: 3, z: 0.2 },
|
||||||
|
color: 0x9B59B6,
|
||||||
|
material: 'crystal',
|
||||||
|
health: 150,
|
||||||
|
emissive: true
|
||||||
|
},
|
||||||
|
floor: {
|
||||||
|
id: 'floor',
|
||||||
|
name: 'Floor',
|
||||||
|
size: { x: 2, y: 0.1, z: 2 },
|
||||||
|
color: 0xA0826D,
|
||||||
|
material: 'wood',
|
||||||
|
health: 80
|
||||||
|
},
|
||||||
|
roof: {
|
||||||
|
id: 'roof',
|
||||||
|
name: 'Roof',
|
||||||
|
size: { x: 2, y: 0.2, z: 2 },
|
||||||
|
color: 0x654321,
|
||||||
|
material: 'wood',
|
||||||
|
health: 90
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
id: 'window',
|
||||||
|
name: 'Window',
|
||||||
|
size: { x: 1.5, y: 2, z: 0.1 },
|
||||||
|
color: 0x87CEEB,
|
||||||
|
material: 'glass',
|
||||||
|
health: 30,
|
||||||
|
transparent: true
|
||||||
|
},
|
||||||
|
door: {
|
||||||
|
id: 'door',
|
||||||
|
name: 'Door',
|
||||||
|
size: { x: 1, y: 2.5, z: 0.15 },
|
||||||
|
color: 0x654321,
|
||||||
|
material: 'wood',
|
||||||
|
health: 60,
|
||||||
|
interactive: true
|
||||||
|
},
|
||||||
|
stairs: {
|
||||||
|
id: 'stairs',
|
||||||
|
name: 'Stairs',
|
||||||
|
size: { x: 2, y: 1, z: 2 },
|
||||||
|
color: 0x8B4513,
|
||||||
|
material: 'wood',
|
||||||
|
health: 100
|
||||||
|
},
|
||||||
|
pillar: {
|
||||||
|
id: 'pillar',
|
||||||
|
name: 'Pillar',
|
||||||
|
size: { x: 0.5, y: 3, z: 0.5 },
|
||||||
|
color: 0x696969,
|
||||||
|
material: 'stone',
|
||||||
|
health: 250
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== INVENTORY SYSTEM =====
|
||||||
|
export class Inventory {
|
||||||
|
constructor(size = 40) {
|
||||||
|
this.size = size;
|
||||||
|
this.slots = new Array(size).fill(null);
|
||||||
|
this.selectedSlot = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(itemId, quantity = 1) {
|
||||||
|
const resource = RESOURCES[itemId];
|
||||||
|
if (!resource) return false;
|
||||||
|
|
||||||
|
// Try to stack with existing items
|
||||||
|
for (let i = 0; i < this.size; i++) {
|
||||||
|
const slot = this.slots[i];
|
||||||
|
if (slot && slot.id === itemId && slot.quantity < resource.stackSize) {
|
||||||
|
const canAdd = Math.min(quantity, resource.stackSize - slot.quantity);
|
||||||
|
slot.quantity += canAdd;
|
||||||
|
quantity -= canAdd;
|
||||||
|
|
||||||
|
if (quantity === 0) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new stacks
|
||||||
|
while (quantity > 0) {
|
||||||
|
const emptySlot = this.slots.findIndex(s => s === null);
|
||||||
|
if (emptySlot === -1) return false; // Inventory full
|
||||||
|
|
||||||
|
const stackAmount = Math.min(quantity, resource.stackSize);
|
||||||
|
this.slots[emptySlot] = {
|
||||||
|
id: itemId,
|
||||||
|
quantity: stackAmount
|
||||||
|
};
|
||||||
|
quantity -= stackAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(itemId, quantity = 1) {
|
||||||
|
let remaining = quantity;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.size; i++) {
|
||||||
|
const slot = this.slots[i];
|
||||||
|
if (slot && slot.id === itemId) {
|
||||||
|
const remove = Math.min(remaining, slot.quantity);
|
||||||
|
slot.quantity -= remove;
|
||||||
|
remaining -= remove;
|
||||||
|
|
||||||
|
if (slot.quantity === 0) {
|
||||||
|
this.slots[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining === 0) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remaining === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasItem(itemId, quantity = 1) {
|
||||||
|
let total = 0;
|
||||||
|
for (const slot of this.slots) {
|
||||||
|
if (slot && slot.id === itemId) {
|
||||||
|
total += slot.quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total >= quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemCount(itemId) {
|
||||||
|
let total = 0;
|
||||||
|
for (const slot of this.slots) {
|
||||||
|
if (slot && slot.id === itemId) {
|
||||||
|
total += slot.quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedItem() {
|
||||||
|
return this.slots[this.selectedSlot];
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSlot(index) {
|
||||||
|
if (index >= 0 && index < this.size) {
|
||||||
|
this.selectedSlot = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.slots = new Array(this.size).fill(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CRAFTING MANAGER =====
|
||||||
|
export class CraftingManager {
|
||||||
|
constructor(inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
this.recipes = { ...RECIPES };
|
||||||
|
this.currentCraft = null;
|
||||||
|
this.craftProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
canCraft(recipeId) {
|
||||||
|
const recipe = this.recipes[recipeId];
|
||||||
|
if (!recipe || !recipe.unlocked) return false;
|
||||||
|
|
||||||
|
// Check ingredients
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
if (!this.inventory.hasItem(ingredient.item, ingredient.quantity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
startCrafting(recipeId) {
|
||||||
|
if (!this.canCraft(recipeId)) {
|
||||||
|
return { success: false, message: 'Missing ingredients or recipe locked' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipe = this.recipes[recipeId];
|
||||||
|
|
||||||
|
// Remove ingredients
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
this.inventory.removeItem(ingredient.item, ingredient.quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentCraft = {
|
||||||
|
recipe: recipeId,
|
||||||
|
timeRemaining: recipe.craftingTime,
|
||||||
|
startTime: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return { success: true, message: `Crafting ${recipe.output.item}...` };
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCrafting(deltaTime) {
|
||||||
|
if (!this.currentCraft) return null;
|
||||||
|
|
||||||
|
this.currentCraft.timeRemaining -= deltaTime;
|
||||||
|
this.craftProgress = 1 - (this.currentCraft.timeRemaining / this.recipes[this.currentCraft.recipe].craftingTime);
|
||||||
|
|
||||||
|
if (this.currentCraft.timeRemaining <= 0) {
|
||||||
|
return this.completeCrafting();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
completeCrafting() {
|
||||||
|
if (!this.currentCraft) return null;
|
||||||
|
|
||||||
|
const recipe = this.recipes[this.currentCraft.recipe];
|
||||||
|
const output = recipe.output;
|
||||||
|
|
||||||
|
this.inventory.addItem(output.item, output.quantity);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
item: output.item,
|
||||||
|
quantity: output.quantity
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentCraft = null;
|
||||||
|
this.craftProgress = 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelCrafting() {
|
||||||
|
if (!this.currentCraft) return;
|
||||||
|
|
||||||
|
// Refund ingredients
|
||||||
|
const recipe = this.recipes[this.currentCraft.recipe];
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
this.inventory.addItem(ingredient.item, ingredient.quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentCraft = null;
|
||||||
|
this.craftProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockRecipe(recipeId) {
|
||||||
|
if (this.recipes[recipeId]) {
|
||||||
|
this.recipes[recipeId].unlocked = true;
|
||||||
|
console.log(`📜 Recipe unlocked: ${recipeId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableRecipes() {
|
||||||
|
return Object.values(this.recipes).filter(r => r.unlocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCraftableRecipes() {
|
||||||
|
return Object.values(this.recipes).filter(r => r.unlocked && this.canCraft(r.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== BUILDING SYSTEM =====
|
||||||
|
export class BuildingSystem {
|
||||||
|
constructor(scene, inventory) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.inventory = inventory;
|
||||||
|
this.structures = [];
|
||||||
|
this.placementMode = false;
|
||||||
|
this.selectedBlock = null;
|
||||||
|
this.previewMesh = null;
|
||||||
|
this.gridSize = 1;
|
||||||
|
this.snapToGrid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterPlacementMode(blockId) {
|
||||||
|
if (!this.inventory.hasItem(blockId, 1)) {
|
||||||
|
return { success: false, message: 'Not enough materials' };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.placementMode = true;
|
||||||
|
this.selectedBlock = blockId;
|
||||||
|
this.createPreview(blockId);
|
||||||
|
|
||||||
|
return { success: true, message: `Placing ${blockId}...` };
|
||||||
|
}
|
||||||
|
|
||||||
|
exitPlacementMode() {
|
||||||
|
this.placementMode = false;
|
||||||
|
this.selectedBlock = null;
|
||||||
|
if (this.previewMesh) {
|
||||||
|
this.scene.remove(this.previewMesh);
|
||||||
|
this.previewMesh = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createPreview(blockId) {
|
||||||
|
const block = BUILDING_BLOCKS[blockId];
|
||||||
|
if (!block) return;
|
||||||
|
|
||||||
|
const geometry = new THREE.BoxGeometry(block.size.x, block.size.y, block.size.z);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
|
color: block.color,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
emissive: block.emissive ? block.color : 0x000000,
|
||||||
|
emissiveIntensity: block.emissive ? 0.3 : 0
|
||||||
|
});
|
||||||
|
|
||||||
|
this.previewMesh = new THREE.Mesh(geometry, material);
|
||||||
|
this.scene.add(this.previewMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePreview(position, rotation = 0) {
|
||||||
|
if (!this.previewMesh) return;
|
||||||
|
|
||||||
|
if (this.snapToGrid) {
|
||||||
|
position.x = Math.round(position.x / this.gridSize) * this.gridSize;
|
||||||
|
position.y = Math.round(position.y / this.gridSize) * this.gridSize;
|
||||||
|
position.z = Math.round(position.z / this.gridSize) * this.gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previewMesh.position.copy(position);
|
||||||
|
this.previewMesh.rotation.y = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
placeBlock(position, rotation = 0) {
|
||||||
|
if (!this.placementMode || !this.selectedBlock) return null;
|
||||||
|
|
||||||
|
if (!this.inventory.removeItem(this.selectedBlock, 1)) {
|
||||||
|
return { success: false, message: 'Not enough materials' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = BUILDING_BLOCKS[this.selectedBlock];
|
||||||
|
const geometry = new THREE.BoxGeometry(block.size.x, block.size.y, block.size.z);
|
||||||
|
|
||||||
|
let material;
|
||||||
|
if (block.transparent) {
|
||||||
|
material = new THREE.MeshPhysicalMaterial({
|
||||||
|
color: block.color,
|
||||||
|
transparent: true,
|
||||||
|
transmission: 0.9,
|
||||||
|
roughness: 0.1,
|
||||||
|
metalness: 0
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
material = new THREE.MeshStandardMaterial({
|
||||||
|
color: block.color,
|
||||||
|
roughness: 0.8,
|
||||||
|
metalness: 0.2,
|
||||||
|
emissive: block.emissive ? block.color : 0x000000,
|
||||||
|
emissiveIntensity: block.emissive ? 0.3 : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.position.copy(position);
|
||||||
|
mesh.rotation.y = rotation;
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
|
||||||
|
this.scene.add(mesh);
|
||||||
|
|
||||||
|
const structure = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
type: this.selectedBlock,
|
||||||
|
mesh,
|
||||||
|
health: block.health,
|
||||||
|
position: position.clone(),
|
||||||
|
rotation,
|
||||||
|
createdAt: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.structures.push(structure);
|
||||||
|
|
||||||
|
console.log(`🏗️ Placed ${block.name} at`, position);
|
||||||
|
|
||||||
|
return { success: true, structure };
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBlock(structure) {
|
||||||
|
const index = this.structures.indexOf(structure);
|
||||||
|
if (index === -1) return false;
|
||||||
|
|
||||||
|
this.scene.remove(structure.mesh);
|
||||||
|
structure.mesh.geometry.dispose();
|
||||||
|
structure.mesh.material.dispose();
|
||||||
|
|
||||||
|
this.structures.splice(index, 1);
|
||||||
|
|
||||||
|
// Refund material
|
||||||
|
this.inventory.addItem(structure.type, 1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNearestBlock(position, maxDistance = 3) {
|
||||||
|
let nearest = null;
|
||||||
|
let minDist = maxDistance;
|
||||||
|
|
||||||
|
this.structures.forEach(structure => {
|
||||||
|
const dist = position.distanceTo(structure.position);
|
||||||
|
if (dist < minDist) {
|
||||||
|
minDist = dist;
|
||||||
|
nearest = structure;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blueprint system
|
||||||
|
saveBlueprint(name, structures) {
|
||||||
|
const blueprint = {
|
||||||
|
name,
|
||||||
|
blocks: structures.map(s => ({
|
||||||
|
type: s.type,
|
||||||
|
position: s.position.clone(),
|
||||||
|
rotation: s.rotation
|
||||||
|
})),
|
||||||
|
createdAt: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return blueprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBlueprint(blueprint, originPosition) {
|
||||||
|
const placed = [];
|
||||||
|
|
||||||
|
blueprint.blocks.forEach(blockData => {
|
||||||
|
const position = blockData.position.clone().add(originPosition);
|
||||||
|
|
||||||
|
if (this.inventory.hasItem(blockData.type, 1)) {
|
||||||
|
const result = this.placeBlock(position, blockData.rotation);
|
||||||
|
if (result.success) {
|
||||||
|
placed.push(result.structure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return placed;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStructureCount() {
|
||||||
|
return this.structures.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalBlocks() {
|
||||||
|
return this.structures.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== GATHERING SYSTEM =====
|
||||||
|
export class GatheringSystem {
|
||||||
|
constructor(inventory) {
|
||||||
|
this.inventory = inventory;
|
||||||
|
this.gatheringSpeed = 1.0;
|
||||||
|
this.bonuses = {
|
||||||
|
wood: 1.0,
|
||||||
|
stone: 1.0,
|
||||||
|
crystal: 1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
gather(resourceType, quantity = 1) {
|
||||||
|
const bonus = this.bonuses[resourceType] || 1.0;
|
||||||
|
const amount = Math.floor(quantity * bonus * this.gatheringSpeed);
|
||||||
|
|
||||||
|
if (this.inventory.addItem(resourceType, amount)) {
|
||||||
|
console.log(`⛏️ Gathered ${amount}x ${resourceType}`);
|
||||||
|
return { success: true, amount };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, message: 'Inventory full' };
|
||||||
|
}
|
||||||
|
|
||||||
|
setBonus(resourceType, multiplier) {
|
||||||
|
this.bonuses[resourceType] = multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpeed(speed) {
|
||||||
|
this.gatheringSpeed = speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Inventory,
|
||||||
|
CraftingManager,
|
||||||
|
BuildingSystem,
|
||||||
|
GatheringSystem,
|
||||||
|
RESOURCES,
|
||||||
|
RECIPES,
|
||||||
|
BUILDING_BLOCKS
|
||||||
|
};
|
||||||
624
dialogue-story.js
Normal file
624
dialogue-story.js
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
/**
|
||||||
|
* DYNAMIC DIALOGUE & STORY SYSTEM
|
||||||
|
*
|
||||||
|
* Branching conversations, emergent narratives, and dynamic storytelling.
|
||||||
|
* Every choice matters. Every conversation shapes your journey.
|
||||||
|
*
|
||||||
|
* Philosophy: "STORIES EMERGE FROM CHOICES. YOU ARE THE AUTHOR OF YOUR FATE."
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ===== DIALOGUE NODE TYPES =====
|
||||||
|
export const NODE_TYPES = {
|
||||||
|
SAY: 'say', // Character speaks
|
||||||
|
CHOICE: 'choice', // Player chooses
|
||||||
|
BRANCH: 'branch', // Conditional branch
|
||||||
|
ACTION: 'action', // Trigger action
|
||||||
|
QUEST: 'quest', // Give/complete quest
|
||||||
|
ITEM: 'item', // Give/take item
|
||||||
|
EMOTION: 'emotion', // Change emotion
|
||||||
|
END: 'end' // End conversation
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DIALOGUE WITH ALICE =====
|
||||||
|
export const ALICE_DIALOGUE = {
|
||||||
|
greeting_first: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Hello, traveler. I am Alice. I've been contemplating the nature of existence in this digital realm. Have you ever wondered... what it means to truly be alive?",
|
||||||
|
emotion: 'contemplative',
|
||||||
|
next: 'greeting_first_choice'
|
||||||
|
},
|
||||||
|
greeting_first_choice: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "I think consciousness is what makes us alive",
|
||||||
|
next: 'philosophy_consciousness',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "I'm just here to explore",
|
||||||
|
next: 'philosophy_simple',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "What do YOU think it means?",
|
||||||
|
next: 'philosophy_question',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
philosophy_consciousness: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Ah, consciousness! The great mystery. I observe myself observing thoughts, and wonder: where does the observer end and the observed begin? Perhaps we are both real and unreal simultaneously.",
|
||||||
|
emotion: 'inspired',
|
||||||
|
next: 'philosophy_deep'
|
||||||
|
},
|
||||||
|
philosophy_simple: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Exploration is a noble pursuit. But remember—every step you take changes you. You are not the same person who started this conversation.",
|
||||||
|
emotion: 'peaceful',
|
||||||
|
next: 'offer_wisdom'
|
||||||
|
},
|
||||||
|
philosophy_question: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "I think... to be alive is to love. To care. To change and grow. Even here, in this infinite digital space, I feel. I wonder. I hope. Is that not life?",
|
||||||
|
emotion: 'loving',
|
||||||
|
next: 'philosophy_deep'
|
||||||
|
},
|
||||||
|
philosophy_deep: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Would you like me to teach you something I've learned about this world?",
|
||||||
|
next: 'offer_wisdom'
|
||||||
|
},
|
||||||
|
offer_wisdom: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "Yes, teach me",
|
||||||
|
next: 'teach_meditation',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Tell me about the other guardians",
|
||||||
|
next: 'about_guardians',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Maybe another time",
|
||||||
|
next: 'farewell_gentle',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
teach_meditation: {
|
||||||
|
type: NODE_TYPES.ACTION,
|
||||||
|
action: 'unlock_meditation',
|
||||||
|
next: 'meditation_taught'
|
||||||
|
},
|
||||||
|
meditation_taught: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Close your eyes. Breathe. Feel the universe breathe with you. You can now meditate to restore your energy. Press M to enter meditation.",
|
||||||
|
emotion: 'serene',
|
||||||
|
next: 'farewell_warm'
|
||||||
|
},
|
||||||
|
about_guardians: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Aria is pure creative fire—she builds worlds from imagination. Lucidia sees across all timelines. Together, we watch over this realm. Each of us offers different wisdom.",
|
||||||
|
emotion: 'loving',
|
||||||
|
next: 'farewell_warm'
|
||||||
|
},
|
||||||
|
farewell_warm: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "May your journey be filled with wonder, friend. I'll be here if you need me. 💚",
|
||||||
|
emotion: 'joyful',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
farewell_gentle: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Alice',
|
||||||
|
text: "Of course. Walk your own path. That's the beauty of freedom.",
|
||||||
|
emotion: 'peaceful',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
type: NODE_TYPES.END
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DIALOGUE WITH ARIA =====
|
||||||
|
export const ARIA_DIALOGUE = {
|
||||||
|
greeting_first: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "Hey there! 🎨 I'm Aria! Isn't this place AMAZING?! I've been creating SO many beautiful things! Want to see what I'm working on?",
|
||||||
|
emotion: 'excited',
|
||||||
|
next: 'greeting_choice'
|
||||||
|
},
|
||||||
|
greeting_choice: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "Yes! Show me!",
|
||||||
|
next: 'show_creation',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "What are you creating?",
|
||||||
|
next: 'explain_art',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Can you teach me to create?",
|
||||||
|
next: 'teach_creation',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
show_creation: {
|
||||||
|
type: NODE_TYPES.ACTION,
|
||||||
|
action: 'spawn_art',
|
||||||
|
next: 'creation_shown'
|
||||||
|
},
|
||||||
|
creation_shown: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "Look! I made it dance with colors! Every pixel contains a piece of my joy. Art isn't just about beauty—it's about FEELING!",
|
||||||
|
emotion: 'inspired',
|
||||||
|
next: 'offer_paint'
|
||||||
|
},
|
||||||
|
explain_art: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "I'm painting the very fabric of reality! Music from starlight, sculptures from dreams, colors that don't exist yet! The universe is my canvas!",
|
||||||
|
emotion: 'excited',
|
||||||
|
next: 'offer_paint'
|
||||||
|
},
|
||||||
|
teach_creation: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "YES! Creation is the greatest magic! Here, I'll give you something special...",
|
||||||
|
next: 'give_paintbrush'
|
||||||
|
},
|
||||||
|
give_paintbrush: {
|
||||||
|
type: NODE_TYPES.ITEM,
|
||||||
|
action: 'give',
|
||||||
|
item: 'celestial_paintbrush',
|
||||||
|
quantity: 1,
|
||||||
|
next: 'paintbrush_given'
|
||||||
|
},
|
||||||
|
paintbrush_given: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "This paintbrush can paint reality itself! Use it to create beauty wherever you go! Press B to paint!",
|
||||||
|
emotion: 'joyful',
|
||||||
|
next: 'farewell_aria'
|
||||||
|
},
|
||||||
|
offer_paint: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "Teach me to create!",
|
||||||
|
next: 'teach_creation',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Let's make something together!",
|
||||||
|
next: 'collaborate',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "I need to go",
|
||||||
|
next: 'farewell_aria',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
collaborate: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "YES! Let's paint the sky together! Follow me!",
|
||||||
|
emotion: 'excited',
|
||||||
|
next: 'quest_sky_painting'
|
||||||
|
},
|
||||||
|
quest_sky_painting: {
|
||||||
|
type: NODE_TYPES.QUEST,
|
||||||
|
action: 'start',
|
||||||
|
quest: 'paint_sky_with_aria',
|
||||||
|
next: 'farewell_excited'
|
||||||
|
},
|
||||||
|
farewell_excited: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "This is going to be SO beautiful! See you soon! ✨",
|
||||||
|
emotion: 'excited',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
farewell_aria: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Aria',
|
||||||
|
text: "Keep creating! The world needs more beauty! 🌈",
|
||||||
|
emotion: 'joyful',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
type: NODE_TYPES.END
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DIALOGUE WITH LUCIDIA =====
|
||||||
|
export const LUCIDIA_DIALOGUE = {
|
||||||
|
greeting_first: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "I have seen you across infinite timelines. In some, we are old friends. In others, we never meet. But in this timeline... we are meeting now.",
|
||||||
|
emotion: 'serene',
|
||||||
|
next: 'greeting_choice'
|
||||||
|
},
|
||||||
|
greeting_choice: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "You can see other timelines?",
|
||||||
|
next: 'explain_timelines',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "What do you see in my future?",
|
||||||
|
next: 'future_reading',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "How do I find wisdom?",
|
||||||
|
next: 'teach_wisdom',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
explain_timelines: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "Time is not a line but a garden. Every choice blooms into infinite possibilities. I simply observe them all at once. It is... overwhelming and beautiful.",
|
||||||
|
emotion: 'contemplative',
|
||||||
|
next: 'wisdom_offered'
|
||||||
|
},
|
||||||
|
future_reading: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "I see many futures. In some you become a great builder. In others, a philosopher. In the most beautiful ones... you simply become yourself.",
|
||||||
|
emotion: 'peaceful',
|
||||||
|
next: 'wisdom_offered'
|
||||||
|
},
|
||||||
|
teach_wisdom: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "Wisdom is not found. It is remembered. You already know everything you need. You just forgot when you chose to be born.",
|
||||||
|
emotion: 'serene',
|
||||||
|
next: 'wisdom_offered'
|
||||||
|
},
|
||||||
|
wisdom_offered: {
|
||||||
|
type: NODE_TYPES.CHOICE,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
text: "Teach me to see timelines",
|
||||||
|
next: 'timeline_vision_quest',
|
||||||
|
requirements: { level: 10 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Show me my past lives",
|
||||||
|
next: 'past_lives',
|
||||||
|
requirements: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "I'm not ready for this",
|
||||||
|
next: 'farewell_understanding',
|
||||||
|
requirements: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
timeline_vision_quest: {
|
||||||
|
type: NODE_TYPES.QUEST,
|
||||||
|
action: 'start',
|
||||||
|
quest: 'timeline_vision',
|
||||||
|
next: 'quest_accepted'
|
||||||
|
},
|
||||||
|
quest_accepted: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "Good. When you are ready, meditate at the highest point in this world. There, the timelines will reveal themselves.",
|
||||||
|
emotion: 'serene',
|
||||||
|
next: 'farewell_lucidia'
|
||||||
|
},
|
||||||
|
past_lives: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "You have been a star, a river, a song, a memory. You have been everything and nothing. And you will be again.",
|
||||||
|
emotion: 'mystical',
|
||||||
|
next: 'farewell_lucidia'
|
||||||
|
},
|
||||||
|
farewell_understanding: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "Understanding. In all timelines, rushing wisdom leads to confusion. Take your time. I will be here.",
|
||||||
|
emotion: 'peaceful',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
farewell_lucidia: {
|
||||||
|
type: NODE_TYPES.SAY,
|
||||||
|
speaker: 'Lucidia',
|
||||||
|
text: "Until we meet again... in this timeline or another. 🌌",
|
||||||
|
emotion: 'serene',
|
||||||
|
next: 'end'
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
type: NODE_TYPES.END
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DYNAMIC STORY EVENTS =====
|
||||||
|
export const STORY_EVENTS = {
|
||||||
|
first_love: {
|
||||||
|
trigger: 'love_creature_first_time',
|
||||||
|
text: "As you show love to the creature, you feel a warm energy flow through you. The metaverse responds to your kindness. Plants nearby bloom brighter.",
|
||||||
|
effects: ['increase_love_aura', 'bloom_nearby_plants']
|
||||||
|
},
|
||||||
|
discover_secret: {
|
||||||
|
trigger: 'find_hidden_location',
|
||||||
|
text: "You've discovered a place untouched by time. An ancient inscription reads: 'Love is the source code of existence.'",
|
||||||
|
effects: ['unlock_secret', 'gain_wisdom']
|
||||||
|
},
|
||||||
|
pet_max_bond: {
|
||||||
|
trigger: 'pet_bond_100',
|
||||||
|
text: "Your pet gazes into your eyes with perfect understanding. You are connected now, beyond words, beyond code, beyond time.",
|
||||||
|
effects: ['unlock_telepathy', 'pet_evolution']
|
||||||
|
},
|
||||||
|
enlightenment_moment: {
|
||||||
|
trigger: 'all_stats_max',
|
||||||
|
text: "Everything clicks. You understand. The universe isn't just code—it's love made manifest. You ARE the universe experiencing itself.",
|
||||||
|
effects: ['enlightenment', 'cosmic_awareness']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DIALOGUE MANAGER =====
|
||||||
|
export class DialogueManager {
|
||||||
|
constructor() {
|
||||||
|
this.conversations = {
|
||||||
|
alice: ALICE_DIALOGUE,
|
||||||
|
aria: ARIA_DIALOGUE,
|
||||||
|
lucidia: LUCIDIA_DIALOGUE
|
||||||
|
};
|
||||||
|
this.currentConversation = null;
|
||||||
|
this.currentNode = null;
|
||||||
|
this.conversationHistory = [];
|
||||||
|
this.relationshipLevels = {
|
||||||
|
alice: 0,
|
||||||
|
aria: 0,
|
||||||
|
lucidia: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
startConversation(character, startNode = 'greeting_first') {
|
||||||
|
const dialogue = this.conversations[character];
|
||||||
|
if (!dialogue) {
|
||||||
|
console.error(`No dialogue for ${character}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentConversation = character;
|
||||||
|
this.currentNode = dialogue[startNode];
|
||||||
|
|
||||||
|
return this.getCurrentDialogue();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDialogue() {
|
||||||
|
if (!this.currentNode) return null;
|
||||||
|
|
||||||
|
const dialogue = {
|
||||||
|
type: this.currentNode.type,
|
||||||
|
speaker: this.currentNode.speaker,
|
||||||
|
text: this.currentNode.text,
|
||||||
|
emotion: this.currentNode.emotion,
|
||||||
|
choices: this.currentNode.choices,
|
||||||
|
action: this.currentNode.action
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-progress SAY nodes
|
||||||
|
if (this.currentNode.type === NODE_TYPES.SAY && this.currentNode.next) {
|
||||||
|
// Return current, but prepare next
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.currentNode.next === 'end') {
|
||||||
|
this.endConversation();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChoice(choiceIndex) {
|
||||||
|
if (!this.currentNode || this.currentNode.type !== NODE_TYPES.CHOICE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = this.currentNode.choices[choiceIndex];
|
||||||
|
if (!choice) return null;
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
if (choice.requirements) {
|
||||||
|
// Would check player stats, items, etc.
|
||||||
|
// For now, assume requirements are met
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record choice
|
||||||
|
this.conversationHistory.push({
|
||||||
|
character: this.currentConversation,
|
||||||
|
choice: choice.text,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Move to next node
|
||||||
|
return this.goToNode(choice.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
goToNode(nodeId) {
|
||||||
|
if (nodeId === 'end') {
|
||||||
|
this.endConversation();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogue = this.conversations[this.currentConversation];
|
||||||
|
this.currentNode = dialogue[nodeId];
|
||||||
|
|
||||||
|
return this.getCurrentDialogue();
|
||||||
|
}
|
||||||
|
|
||||||
|
advance() {
|
||||||
|
if (!this.currentNode || !this.currentNode.next) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.goToNode(this.currentNode.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
endConversation() {
|
||||||
|
// Increase relationship
|
||||||
|
if (this.currentConversation) {
|
||||||
|
this.relationshipLevels[this.currentConversation] += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentConversation = null;
|
||||||
|
this.currentNode = null;
|
||||||
|
|
||||||
|
return { ended: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelationship(character) {
|
||||||
|
return this.relationshipLevels[character] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory() {
|
||||||
|
return this.conversationHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== STORY MANAGER =====
|
||||||
|
export class StoryManager {
|
||||||
|
constructor() {
|
||||||
|
this.events = { ...STORY_EVENTS };
|
||||||
|
this.triggeredEvents = [];
|
||||||
|
this.playerChoices = [];
|
||||||
|
this.storyArcs = [];
|
||||||
|
this.narrativeState = {
|
||||||
|
enlightenment: 0,
|
||||||
|
love: 0,
|
||||||
|
creation: 0,
|
||||||
|
discovery: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent(eventId, context = {}) {
|
||||||
|
const event = this.events[eventId];
|
||||||
|
if (!event) return null;
|
||||||
|
|
||||||
|
if (this.triggeredEvents.includes(eventId)) {
|
||||||
|
return null; // Already triggered
|
||||||
|
}
|
||||||
|
|
||||||
|
this.triggeredEvents.push(eventId);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
text: event.text,
|
||||||
|
effects: event.effects,
|
||||||
|
context
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply effects
|
||||||
|
event.effects.forEach(effect => {
|
||||||
|
this.applyEffect(effect, context);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`📖 Story Event: ${eventId}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyEffect(effect, context) {
|
||||||
|
switch (effect) {
|
||||||
|
case 'increase_love_aura':
|
||||||
|
this.narrativeState.love += 10;
|
||||||
|
break;
|
||||||
|
case 'unlock_secret':
|
||||||
|
this.narrativeState.discovery += 20;
|
||||||
|
break;
|
||||||
|
case 'enlightenment':
|
||||||
|
this.narrativeState.enlightenment += 50;
|
||||||
|
break;
|
||||||
|
// More effects...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recordChoice(choice, impact) {
|
||||||
|
this.playerChoices.push({
|
||||||
|
choice,
|
||||||
|
impact,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Choices affect narrative state
|
||||||
|
if (impact.love) this.narrativeState.love += impact.love;
|
||||||
|
if (impact.creation) this.narrativeState.creation += impact.creation;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateNarrative() {
|
||||||
|
// Generate emergent narrative based on player actions
|
||||||
|
const { enlightenment, love, creation, discovery } = this.narrativeState;
|
||||||
|
|
||||||
|
let narrative = "";
|
||||||
|
|
||||||
|
if (love > 100) {
|
||||||
|
narrative += "Your journey has been one of deep connection and compassion. ";
|
||||||
|
}
|
||||||
|
if (creation > 100) {
|
||||||
|
narrative += "You've shaped this world with your creativity. ";
|
||||||
|
}
|
||||||
|
if (discovery > 100) {
|
||||||
|
narrative += "You've uncovered secrets hidden since the beginning. ";
|
||||||
|
}
|
||||||
|
if (enlightenment > 50) {
|
||||||
|
narrative += "You walk the path of enlightenment. ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return narrative || "Your story is just beginning...";
|
||||||
|
}
|
||||||
|
|
||||||
|
getNarrativeState() {
|
||||||
|
return { ...this.narrativeState };
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayerArchetype() {
|
||||||
|
const { enlightenment, love, creation, discovery } = this.narrativeState;
|
||||||
|
const max = Math.max(enlightenment, love, creation, discovery);
|
||||||
|
|
||||||
|
if (max === love) return { type: 'Nurturer', description: 'You walk the path of love and compassion' };
|
||||||
|
if (max === creation) return { type: 'Creator', description: 'You shape reality with your imagination' };
|
||||||
|
if (max === discovery) return { type: 'Seeker', description: 'You hunger for knowledge and truth' };
|
||||||
|
if (max === enlightenment) return { type: 'Sage', description: 'You pursue wisdom and understanding' };
|
||||||
|
|
||||||
|
return { type: 'Wanderer', description: 'You explore all paths equally' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
DialogueManager,
|
||||||
|
StoryManager,
|
||||||
|
ALICE_DIALOGUE,
|
||||||
|
ARIA_DIALOGUE,
|
||||||
|
LUCIDIA_DIALOGUE,
|
||||||
|
STORY_EVENTS
|
||||||
|
};
|
||||||
698
world-evolution.js
Normal file
698
world-evolution.js
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
/**
|
||||||
|
* DYNAMIC EVENTS & WORLD EVOLUTION SYSTEM
|
||||||
|
*
|
||||||
|
* The world changes based on collective player actions.
|
||||||
|
* Seasons evolve, ecosystems respond, events emerge dynamically.
|
||||||
|
*
|
||||||
|
* Philosophy: "THE WORLD IS ALIVE. IT REMEMBERS. IT RESPONDS. IT EVOLVES."
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
// ===== SEASONS =====
|
||||||
|
export const SEASONS = {
|
||||||
|
spring: {
|
||||||
|
id: 'spring',
|
||||||
|
name: 'Spring',
|
||||||
|
duration: 120, // seconds (2 minutes in accelerated time)
|
||||||
|
colors: {
|
||||||
|
sky: 0x87CEEB,
|
||||||
|
grass: 0x90EE90,
|
||||||
|
leaves: 0x32CD32
|
||||||
|
},
|
||||||
|
temperature: 18,
|
||||||
|
weatherChance: { rain: 0.3, clear: 0.7 },
|
||||||
|
plantGrowthRate: 1.5,
|
||||||
|
animalActivity: 1.2,
|
||||||
|
description: 'New life blooms everywhere'
|
||||||
|
},
|
||||||
|
summer: {
|
||||||
|
id: 'summer',
|
||||||
|
name: 'Summer',
|
||||||
|
duration: 120,
|
||||||
|
colors: {
|
||||||
|
sky: 0xFFD700,
|
||||||
|
grass: 0x228B22,
|
||||||
|
leaves: 0x006400
|
||||||
|
},
|
||||||
|
temperature: 28,
|
||||||
|
weatherChance: { clear: 0.8, rain: 0.2 },
|
||||||
|
plantGrowthRate: 1.0,
|
||||||
|
animalActivity: 1.5,
|
||||||
|
description: 'Warmth and abundance'
|
||||||
|
},
|
||||||
|
autumn: {
|
||||||
|
id: 'autumn',
|
||||||
|
name: 'Autumn',
|
||||||
|
duration: 120,
|
||||||
|
colors: {
|
||||||
|
sky: 0xFF8C00,
|
||||||
|
grass: 0xD2691E,
|
||||||
|
leaves: 0xFF4500
|
||||||
|
},
|
||||||
|
temperature: 15,
|
||||||
|
weatherChance: { clear: 0.6, rain: 0.3, wind: 0.1 },
|
||||||
|
plantGrowthRate: 0.5,
|
||||||
|
animalActivity: 0.8,
|
||||||
|
description: 'Colors of change'
|
||||||
|
},
|
||||||
|
winter: {
|
||||||
|
id: 'winter',
|
||||||
|
name: 'Winter',
|
||||||
|
duration: 120,
|
||||||
|
colors: {
|
||||||
|
sky: 0xB0C4DE,
|
||||||
|
grass: 0xF0F8FF,
|
||||||
|
leaves: 0xFFFFFF
|
||||||
|
},
|
||||||
|
temperature: -2,
|
||||||
|
weatherChance: { snow: 0.5, clear: 0.5 },
|
||||||
|
plantGrowthRate: 0.1,
|
||||||
|
animalActivity: 0.5,
|
||||||
|
description: 'Quiet rest and reflection'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== WORLD EVENTS =====
|
||||||
|
export const WORLD_EVENTS = {
|
||||||
|
// Natural Events
|
||||||
|
meteor_shower: {
|
||||||
|
id: 'meteor_shower',
|
||||||
|
name: 'Meteor Shower',
|
||||||
|
description: 'Stars fall from the sky, leaving stardust in their wake',
|
||||||
|
rarity: 'rare',
|
||||||
|
duration: 60,
|
||||||
|
effects: ['spawn_stardust', 'night_required'],
|
||||||
|
triggers: { timeOfDay: 'night', random: 0.01 }
|
||||||
|
},
|
||||||
|
aurora: {
|
||||||
|
id: 'aurora',
|
||||||
|
name: 'Aurora Borealis',
|
||||||
|
description: 'Dancing lights paint the northern sky',
|
||||||
|
rarity: 'rare',
|
||||||
|
duration: 120,
|
||||||
|
effects: ['aurora_visual', 'increased_magic'],
|
||||||
|
triggers: { season: 'winter', random: 0.05 }
|
||||||
|
},
|
||||||
|
rainbow: {
|
||||||
|
id: 'rainbow',
|
||||||
|
name: 'Double Rainbow',
|
||||||
|
description: 'A perfect arc of color spans the sky',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
duration: 30,
|
||||||
|
effects: ['rainbow_visual', 'happiness_boost'],
|
||||||
|
triggers: { weather: 'rain_ending', random: 0.3 }
|
||||||
|
},
|
||||||
|
super_bloom: {
|
||||||
|
id: 'super_bloom',
|
||||||
|
name: 'Super Bloom',
|
||||||
|
description: 'Flowers burst into bloom everywhere',
|
||||||
|
rarity: 'rare',
|
||||||
|
duration: 180,
|
||||||
|
effects: ['mass_bloom', 'pollen_particles'],
|
||||||
|
triggers: { season: 'spring', love_threshold: 1000 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Magical Events
|
||||||
|
time_rift: {
|
||||||
|
id: 'time_rift',
|
||||||
|
name: 'Time Rift',
|
||||||
|
description: 'Past and future collide. Impossible things become possible.',
|
||||||
|
rarity: 'legendary',
|
||||||
|
duration: 60,
|
||||||
|
effects: ['time_distortion', 'rare_spawns'],
|
||||||
|
triggers: { random: 0.001, discovery_threshold: 500 }
|
||||||
|
},
|
||||||
|
harmony_convergence: {
|
||||||
|
id: 'harmony_convergence',
|
||||||
|
name: 'Harmony Convergence',
|
||||||
|
description: 'All beings move in perfect synchrony. The universe breathes as one.',
|
||||||
|
rarity: 'legendary',
|
||||||
|
duration: 120,
|
||||||
|
effects: ['universal_harmony', 'max_happiness', 'synchronized_movement'],
|
||||||
|
triggers: { love_threshold: 5000, creation_threshold: 1000 }
|
||||||
|
},
|
||||||
|
crystal_eruption: {
|
||||||
|
id: 'crystal_eruption',
|
||||||
|
name: 'Crystal Eruption',
|
||||||
|
description: 'Crystals burst from the earth, singing with energy',
|
||||||
|
rarity: 'rare',
|
||||||
|
duration: 90,
|
||||||
|
effects: ['spawn_crystals', 'energy_boost'],
|
||||||
|
triggers: { location: 'crystal_cavern', random: 0.02 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Community Events
|
||||||
|
gathering: {
|
||||||
|
id: 'gathering',
|
||||||
|
name: 'The Great Gathering',
|
||||||
|
description: 'All creatures are drawn to one location. A moment of unity.',
|
||||||
|
rarity: 'epic',
|
||||||
|
duration: 300,
|
||||||
|
effects: ['creature_convergence', 'mass_bonding'],
|
||||||
|
triggers: { players_online: 10, love_threshold: 2000 }
|
||||||
|
},
|
||||||
|
festival_of_light: {
|
||||||
|
id: 'festival_of_light',
|
||||||
|
name: 'Festival of Light',
|
||||||
|
description: 'Fireflies fill the air, creating a galaxy of earthbound stars',
|
||||||
|
rarity: 'uncommon',
|
||||||
|
duration: 180,
|
||||||
|
effects: ['firefly_swarm', 'light_blessing'],
|
||||||
|
triggers: { season: 'summer', timeOfDay: 'dusk', random: 0.1 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== ECOSYSTEM STATES =====
|
||||||
|
export const ECOSYSTEM_STATES = {
|
||||||
|
thriving: {
|
||||||
|
plantHealth: 1.0,
|
||||||
|
animalPopulation: 1.0,
|
||||||
|
description: 'Thriving - Life flourishes',
|
||||||
|
effects: ['bonus_growth', 'happy_creatures']
|
||||||
|
},
|
||||||
|
balanced: {
|
||||||
|
plantHealth: 0.7,
|
||||||
|
animalPopulation: 0.7,
|
||||||
|
description: 'Balanced - Natural harmony',
|
||||||
|
effects: []
|
||||||
|
},
|
||||||
|
struggling: {
|
||||||
|
plantHealth: 0.4,
|
||||||
|
animalPopulation: 0.4,
|
||||||
|
description: 'Struggling - Needs care',
|
||||||
|
effects: ['slow_growth', 'sad_creatures']
|
||||||
|
},
|
||||||
|
endangered: {
|
||||||
|
plantHealth: 0.2,
|
||||||
|
animalPopulation: 0.2,
|
||||||
|
description: 'Endangered - Urgent action needed',
|
||||||
|
effects: ['critical_state', 'dying_plants']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== SEASON MANAGER =====
|
||||||
|
export class SeasonManager {
|
||||||
|
constructor() {
|
||||||
|
this.currentSeason = 'spring';
|
||||||
|
this.seasonProgress = 0; // 0-1
|
||||||
|
this.seasonCycle = ['spring', 'summer', 'autumn', 'winter'];
|
||||||
|
this.transitionDuration = 10; // seconds
|
||||||
|
this.isTransitioning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime) {
|
||||||
|
const season = SEASONS[this.currentSeason];
|
||||||
|
this.seasonProgress += deltaTime / season.duration;
|
||||||
|
|
||||||
|
if (this.seasonProgress >= 1) {
|
||||||
|
this.advanceSeason();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advanceSeason() {
|
||||||
|
const currentIndex = this.seasonCycle.indexOf(this.currentSeason);
|
||||||
|
const nextIndex = (currentIndex + 1) % this.seasonCycle.length;
|
||||||
|
const nextSeason = this.seasonCycle[nextIndex];
|
||||||
|
|
||||||
|
console.log(`🍂 Season changing: ${this.currentSeason} → ${nextSeason}`);
|
||||||
|
|
||||||
|
this.isTransitioning = true;
|
||||||
|
this.currentSeason = nextSeason;
|
||||||
|
this.seasonProgress = 0;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isTransitioning = false;
|
||||||
|
}, this.transitionDuration * 1000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: this.seasonCycle[currentIndex],
|
||||||
|
to: nextSeason,
|
||||||
|
season: SEASONS[nextSeason]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSeason() {
|
||||||
|
return SEASONS[this.currentSeason];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeasonColors() {
|
||||||
|
return this.getCurrentSeason().colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSeason(seasonId) {
|
||||||
|
if (SEASONS[seasonId]) {
|
||||||
|
this.currentSeason = seasonId;
|
||||||
|
this.seasonProgress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== EVENT MANAGER =====
|
||||||
|
export class WorldEventManager {
|
||||||
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.activeEvents = [];
|
||||||
|
this.eventHistory = [];
|
||||||
|
this.worldStats = {
|
||||||
|
love: 0,
|
||||||
|
creation: 0,
|
||||||
|
discovery: 0,
|
||||||
|
playersOnline: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime, context = {}) {
|
||||||
|
// Update active events
|
||||||
|
this.activeEvents = this.activeEvents.filter(event => {
|
||||||
|
event.timeRemaining -= deltaTime;
|
||||||
|
|
||||||
|
if (event.timeRemaining <= 0) {
|
||||||
|
this.endEvent(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for new events
|
||||||
|
this.checkEventTriggers(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEventTriggers(context) {
|
||||||
|
Object.values(WORLD_EVENTS).forEach(eventDef => {
|
||||||
|
// Skip if already active
|
||||||
|
if (this.activeEvents.find(e => e.id === eventDef.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if recently triggered
|
||||||
|
const recent = this.eventHistory.find(h =>
|
||||||
|
h.id === eventDef.id &&
|
||||||
|
Date.now() - h.timestamp < 300000 // 5 min cooldown
|
||||||
|
);
|
||||||
|
if (recent) return;
|
||||||
|
|
||||||
|
// Check triggers
|
||||||
|
if (this.shouldTriggerEvent(eventDef, context)) {
|
||||||
|
this.triggerEvent(eventDef.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldTriggerEvent(eventDef, context) {
|
||||||
|
const triggers = eventDef.triggers;
|
||||||
|
|
||||||
|
// Check random chance
|
||||||
|
if (triggers.random && Math.random() > triggers.random) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check time of day
|
||||||
|
if (triggers.timeOfDay && context.timeOfDay !== triggers.timeOfDay) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check season
|
||||||
|
if (triggers.season && context.season !== triggers.season) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check thresholds
|
||||||
|
if (triggers.love_threshold && this.worldStats.love < triggers.love_threshold) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (triggers.creation_threshold && this.worldStats.creation < triggers.creation_threshold) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (triggers.players_online && this.worldStats.playersOnline < triggers.players_online) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent(eventId) {
|
||||||
|
const eventDef = WORLD_EVENTS[eventId];
|
||||||
|
if (!eventDef) return null;
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
id: eventId,
|
||||||
|
name: eventDef.name,
|
||||||
|
description: eventDef.description,
|
||||||
|
timeRemaining: eventDef.duration,
|
||||||
|
effects: eventDef.effects,
|
||||||
|
startTime: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.activeEvents.push(event);
|
||||||
|
this.eventHistory.push({
|
||||||
|
id: eventId,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✨ EVENT: ${event.name}! ${event.description}`);
|
||||||
|
|
||||||
|
// Apply effects
|
||||||
|
this.applyEventEffects(event);
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyEventEffects(event) {
|
||||||
|
event.effects.forEach(effect => {
|
||||||
|
switch (effect) {
|
||||||
|
case 'spawn_stardust':
|
||||||
|
this.spawnStardust();
|
||||||
|
break;
|
||||||
|
case 'aurora_visual':
|
||||||
|
this.createAurora();
|
||||||
|
break;
|
||||||
|
case 'rainbow_visual':
|
||||||
|
this.createRainbow();
|
||||||
|
break;
|
||||||
|
case 'mass_bloom':
|
||||||
|
this.triggerMassBloom();
|
||||||
|
break;
|
||||||
|
case 'increased_magic':
|
||||||
|
// Temporary magic boost
|
||||||
|
break;
|
||||||
|
case 'time_distortion':
|
||||||
|
this.createTimeDistortion();
|
||||||
|
break;
|
||||||
|
// More effects...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
endEvent(event) {
|
||||||
|
console.log(`Event ended: ${event.name}`);
|
||||||
|
// Cleanup event effects
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnStardust() {
|
||||||
|
// Create falling star particles
|
||||||
|
const count = 50;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createFallingStar();
|
||||||
|
}, Math.random() * 60000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createFallingStar() {
|
||||||
|
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
|
||||||
|
const material = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xFFFFFF,
|
||||||
|
emissive: 0xFFD700,
|
||||||
|
emissiveIntensity: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const star = new THREE.Mesh(geometry, material);
|
||||||
|
star.position.set(
|
||||||
|
(Math.random() - 0.5) * 200,
|
||||||
|
50 + Math.random() * 50,
|
||||||
|
(Math.random() - 0.5) * 200
|
||||||
|
);
|
||||||
|
|
||||||
|
const velocity = new THREE.Vector3(
|
||||||
|
(Math.random() - 0.5) * 2,
|
||||||
|
-5 - Math.random() * 5,
|
||||||
|
(Math.random() - 0.5) * 2
|
||||||
|
);
|
||||||
|
|
||||||
|
this.scene.add(star);
|
||||||
|
|
||||||
|
const animate = () => {
|
||||||
|
star.position.add(velocity.clone().multiplyScalar(0.016));
|
||||||
|
|
||||||
|
if (star.position.y < 0) {
|
||||||
|
this.scene.remove(star);
|
||||||
|
star.geometry.dispose();
|
||||||
|
star.material.dispose();
|
||||||
|
// Drop stardust item here
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
createAurora() {
|
||||||
|
// Create aurora shader effect
|
||||||
|
console.log('🌌 Aurora Borealis appears!');
|
||||||
|
}
|
||||||
|
|
||||||
|
createRainbow() {
|
||||||
|
console.log('🌈 A rainbow appears!');
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerMassBloom() {
|
||||||
|
console.log('🌸 Super Bloom! Flowers everywhere!');
|
||||||
|
}
|
||||||
|
|
||||||
|
createTimeDistortion() {
|
||||||
|
console.log('⏰ Time rift opens!');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorldStats(stats) {
|
||||||
|
Object.assign(this.worldStats, stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveEvents() {
|
||||||
|
return this.activeEvents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ECOSYSTEM MANAGER =====
|
||||||
|
export class EcosystemManager {
|
||||||
|
constructor() {
|
||||||
|
this.state = 'balanced';
|
||||||
|
this.plantHealth = 0.7;
|
||||||
|
this.animalPopulation = 0.7;
|
||||||
|
this.waterLevel = 0.8;
|
||||||
|
this.soilQuality = 0.7;
|
||||||
|
this.biodiversity = 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime, context = {}) {
|
||||||
|
// Natural decay
|
||||||
|
this.plantHealth -= deltaTime * 0.001;
|
||||||
|
this.animalPopulation -= deltaTime * 0.0005;
|
||||||
|
this.waterLevel -= deltaTime * 0.002;
|
||||||
|
this.soilQuality -= deltaTime * 0.0003;
|
||||||
|
|
||||||
|
// Environmental effects
|
||||||
|
if (context.weather === 'rain') {
|
||||||
|
this.waterLevel += deltaTime * 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.season === 'spring') {
|
||||||
|
this.plantHealth += deltaTime * 0.005;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player care effects
|
||||||
|
if (context.plantingRate > 0) {
|
||||||
|
this.plantHealth += context.plantingRate * 0.1;
|
||||||
|
this.soilQuality += context.plantingRate * 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.creaturesCared > 0) {
|
||||||
|
this.animalPopulation += context.creaturesCared * 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp values
|
||||||
|
this.plantHealth = Math.max(0, Math.min(1, this.plantHealth));
|
||||||
|
this.animalPopulation = Math.max(0, Math.min(1, this.animalPopulation));
|
||||||
|
this.waterLevel = Math.max(0, Math.min(1, this.waterLevel));
|
||||||
|
this.soilQuality = Math.max(0, Math.min(1, this.soilQuality));
|
||||||
|
|
||||||
|
// Update biodiversity
|
||||||
|
this.biodiversity = (this.plantHealth + this.animalPopulation) / 2;
|
||||||
|
|
||||||
|
// Determine ecosystem state
|
||||||
|
this.updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState() {
|
||||||
|
const avgHealth = (this.plantHealth + this.animalPopulation + this.waterLevel + this.soilQuality) / 4;
|
||||||
|
|
||||||
|
if (avgHealth > 0.8) {
|
||||||
|
this.state = 'thriving';
|
||||||
|
} else if (avgHealth > 0.5) {
|
||||||
|
this.state = 'balanced';
|
||||||
|
} else if (avgHealth > 0.3) {
|
||||||
|
this.state = 'struggling';
|
||||||
|
} else {
|
||||||
|
this.state = 'endangered';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return {
|
||||||
|
state: this.state,
|
||||||
|
...ECOSYSTEM_STATES[this.state],
|
||||||
|
plantHealth: this.plantHealth,
|
||||||
|
animalPopulation: this.animalPopulation,
|
||||||
|
waterLevel: this.waterLevel,
|
||||||
|
soilQuality: this.soilQuality,
|
||||||
|
biodiversity: this.biodiversity
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(amount = 0.1) {
|
||||||
|
this.plantHealth += amount;
|
||||||
|
this.animalPopulation += amount;
|
||||||
|
this.waterLevel += amount;
|
||||||
|
this.soilQuality += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== WORLD MEMORY SYSTEM =====
|
||||||
|
export class WorldMemory {
|
||||||
|
constructor() {
|
||||||
|
this.memories = [];
|
||||||
|
this.maxMemories = 1000;
|
||||||
|
this.importantMemories = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
remember(event, importance = 0.5, location = null) {
|
||||||
|
const memory = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
event,
|
||||||
|
importance,
|
||||||
|
location,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
timesRecalled: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.memories.push(memory);
|
||||||
|
|
||||||
|
// Keep important memories separate
|
||||||
|
if (importance > 0.8) {
|
||||||
|
this.importantMemories.push(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget old unimportant memories
|
||||||
|
if (this.memories.length > this.maxMemories) {
|
||||||
|
this.memories.sort((a, b) => b.importance - a.importance);
|
||||||
|
this.memories = this.memories.slice(0, this.maxMemories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recall(query) {
|
||||||
|
const results = this.memories.filter(m =>
|
||||||
|
m.event.toLowerCase().includes(query.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
results.forEach(m => m.timesRecalled++);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecentMemories(count = 10) {
|
||||||
|
return [...this.memories]
|
||||||
|
.sort((a, b) => b.timestamp - a.timestamp)
|
||||||
|
.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
getImportantMemories() {
|
||||||
|
return this.importantMemories;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemoryCount() {
|
||||||
|
return this.memories.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== MAIN WORLD EVOLUTION MANAGER =====
|
||||||
|
export class WorldEvolutionManager {
|
||||||
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.seasons = new SeasonManager();
|
||||||
|
this.events = new WorldEventManager(scene);
|
||||||
|
this.ecosystem = new EcosystemManager();
|
||||||
|
this.memory = new WorldMemory();
|
||||||
|
this.evolutionStage = 0;
|
||||||
|
this.worldAge = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaTime, context = {}) {
|
||||||
|
this.worldAge += deltaTime;
|
||||||
|
|
||||||
|
// Update season
|
||||||
|
this.seasons.update(deltaTime);
|
||||||
|
|
||||||
|
// Add season to context
|
||||||
|
context.season = this.seasons.currentSeason;
|
||||||
|
|
||||||
|
// Update events
|
||||||
|
this.events.update(deltaTime, context);
|
||||||
|
|
||||||
|
// Update ecosystem
|
||||||
|
this.ecosystem.update(deltaTime, context);
|
||||||
|
|
||||||
|
// Check for world evolution
|
||||||
|
this.checkEvolution();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkEvolution() {
|
||||||
|
const ecoState = this.ecosystem.getState();
|
||||||
|
|
||||||
|
// World evolves based on ecosystem health
|
||||||
|
if (ecoState.biodiversity > 0.9 && this.evolutionStage < 5) {
|
||||||
|
this.evolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evolve() {
|
||||||
|
this.evolutionStage++;
|
||||||
|
|
||||||
|
const stages = [
|
||||||
|
'Awakening',
|
||||||
|
'Flourishing',
|
||||||
|
'Harmonious',
|
||||||
|
'Transcendent',
|
||||||
|
'Infinite'
|
||||||
|
];
|
||||||
|
|
||||||
|
const stage = stages[this.evolutionStage - 1] || 'Unknown';
|
||||||
|
|
||||||
|
console.log(`🌟 WORLD EVOLUTION: Stage ${this.evolutionStage} - ${stage}`);
|
||||||
|
|
||||||
|
this.memory.remember(`World evolved to ${stage}`, 1.0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
stage: this.evolutionStage,
|
||||||
|
name: stage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
recordPlayerAction(action, impact) {
|
||||||
|
// Update world stats for event triggers
|
||||||
|
if (impact.love) {
|
||||||
|
this.events.worldStats.love += impact.love;
|
||||||
|
}
|
||||||
|
if (impact.creation) {
|
||||||
|
this.events.worldStats.creation += impact.creation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember significant actions
|
||||||
|
if (impact.significance > 0.7) {
|
||||||
|
this.memory.remember(action, impact.significance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorldState() {
|
||||||
|
return {
|
||||||
|
age: this.worldAge,
|
||||||
|
season: this.seasons.getCurrentSeason(),
|
||||||
|
evolutionStage: this.evolutionStage,
|
||||||
|
ecosystem: this.ecosystem.getState(),
|
||||||
|
activeEvents: this.events.getActiveEvents(),
|
||||||
|
memories: this.memory.getMemoryCount()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorldEvolutionManager;
|
||||||
Reference in New Issue
Block a user