MASTER SYSTEMS + Town & Privacy
This commit is contained in:
454
src/systems/MasterGameSystemsManager.js
Normal file
454
src/systems/MasterGameSystemsManager.js
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
/**
|
||||||
|
* MASTER GAME SYSTEMS MANAGER
|
||||||
|
* Centralized coordinator for all game systems
|
||||||
|
* Created: January 4, 2026
|
||||||
|
*
|
||||||
|
* Integrates:
|
||||||
|
* - Sleep System
|
||||||
|
* - Crafting Tables System
|
||||||
|
* - Bakery Shop System
|
||||||
|
* - Barber Shop System
|
||||||
|
* - Lawyer Office System
|
||||||
|
* - Zombie Miner Automation System
|
||||||
|
* - Town Growth System
|
||||||
|
* - NPC Privacy System
|
||||||
|
* - Existing Mining System
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SleepSystem from './SleepSystem.js';
|
||||||
|
import CraftingTablesSystem from './CraftingTablesSystem.js';
|
||||||
|
import BakeryShopSystem from './BakeryShopSystem.js';
|
||||||
|
import BarberShopSystem from './BarberShopSystem.js';
|
||||||
|
import LawyerOfficeSystem from './LawyerOfficeSystem.js';
|
||||||
|
import ZombieMinerAutomationSystem from './ZombieMinerAutomationSystem.js';
|
||||||
|
import TownGrowthSystem from './TownGrowthSystem.js';
|
||||||
|
import NPCPrivacySystem from './NPCPrivacySystem.js';
|
||||||
|
|
||||||
|
export class MasterGameSystemsManager {
|
||||||
|
constructor(game) {
|
||||||
|
this.game = game;
|
||||||
|
this.scene = game.scene;
|
||||||
|
|
||||||
|
console.log('🎮 Initializing Master Game Systems Manager...');
|
||||||
|
|
||||||
|
// Initialize all systems
|
||||||
|
this.initializeSystems();
|
||||||
|
|
||||||
|
// Set up cross-system event listeners
|
||||||
|
this.setupEventListeners();
|
||||||
|
|
||||||
|
console.log('✅ Master Game Systems Manager initialized!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all game systems
|
||||||
|
*/
|
||||||
|
initializeSystems() {
|
||||||
|
// HOME & SLEEP
|
||||||
|
this.sleepSystem = new SleepSystem(this.game);
|
||||||
|
console.log(' ✓ Sleep System');
|
||||||
|
|
||||||
|
// CRAFTING
|
||||||
|
this.craftingSystem = new CraftingTablesSystem(this.game);
|
||||||
|
console.log(' ✓ Crafting Tables System');
|
||||||
|
|
||||||
|
// TOWN BUILDINGS
|
||||||
|
this.bakerySystem = new BakeryShopSystem(this.game);
|
||||||
|
console.log(' ✓ Bakery Shop System');
|
||||||
|
|
||||||
|
this.barberSystem = new BarberShopSystem(this.game);
|
||||||
|
console.log(' ✓ Barber Shop System');
|
||||||
|
|
||||||
|
this.lawyerSystem = new LawyerOfficeSystem(this.game);
|
||||||
|
console.log(' ✓ Lawyer Office System');
|
||||||
|
|
||||||
|
// MINING & AUTOMATION
|
||||||
|
this.zombieMinerSystem = new ZombieMinerAutomationSystem(this.game);
|
||||||
|
console.log(' ✓ Zombie Miner Automation System');
|
||||||
|
|
||||||
|
// TOWN GROWTH
|
||||||
|
this.townGrowthSystem = new TownGrowthSystem(this.game);
|
||||||
|
console.log(' ✓ Town Growth System');
|
||||||
|
|
||||||
|
// NPC SYSTEMS
|
||||||
|
this.npcPrivacySystem = new NPCPrivacySystem(this.game);
|
||||||
|
console.log(' ✓ NPC Privacy System');
|
||||||
|
|
||||||
|
// Register all systems globally
|
||||||
|
this.registerSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register systems to game registry
|
||||||
|
*/
|
||||||
|
registerSystems() {
|
||||||
|
this.game.registry.set('sleepSystem', this.sleepSystem);
|
||||||
|
this.game.registry.set('craftingSystem', this.craftingSystem);
|
||||||
|
this.game.registry.set('bakerySystem', this.bakerySystem);
|
||||||
|
this.game.registry.set('barberSystem', this.barberSystem);
|
||||||
|
this.game.registry.set('lawyerSystem', this.lawyerSystem);
|
||||||
|
this.game.registry.set('zombieMinerSystem', this.zombieMinerSystem);
|
||||||
|
this.game.registry.set('townGrowthSystem', this.townGrowthSystem);
|
||||||
|
this.game.registry.set('npcPrivacySystem', this.npcPrivacySystem);
|
||||||
|
|
||||||
|
console.log(' ✓ All systems registered to game registry');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up cross-system event listeners
|
||||||
|
*/
|
||||||
|
setupEventListeners() {
|
||||||
|
// TOWN GROWTH → SERVICES
|
||||||
|
this.game.events.on('serviceUnlocked', (data) => {
|
||||||
|
this.onServiceUnlocked(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// MARRIAGE → LAWYER AUTO-UNLOCK
|
||||||
|
this.game.events.on('marriageComplete', () => {
|
||||||
|
this.lawyerSystem.checkAutoUnlock();
|
||||||
|
});
|
||||||
|
|
||||||
|
// RELATIONSHIP CHANGE → LAWYER AUTO-UNLOCK
|
||||||
|
this.game.events.on('relationshipChanged', (data) => {
|
||||||
|
if (data.hearts <= 3) {
|
||||||
|
this.lawyerSystem.checkAutoUnlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// BUILDING UNLOCKED → TOWN GROWTH
|
||||||
|
this.game.events.on('buildingUnlocked', (data) => {
|
||||||
|
this.townGrowthSystem.checkPopulationUnlocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ZOMBIE HIRED → TOWN GROWTH CHECK
|
||||||
|
this.game.events.on('zombieWorkerHired', () => {
|
||||||
|
this.townGrowthSystem.checkPopulationUnlocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
// SLEEP → ZOMBIE LOYALTY DECAY PAUSE
|
||||||
|
this.game.events.on('sleepStarted', () => {
|
||||||
|
this.pauseZombieLoyaltyDecay = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.events.on('wakeUp', () => {
|
||||||
|
this.pauseZombieLoyaltyDecay = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(' ✓ Cross-system event listeners configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle service unlock
|
||||||
|
*/
|
||||||
|
onServiceUnlocked(data) {
|
||||||
|
const { serviceId, population } = data;
|
||||||
|
|
||||||
|
console.log(`🏛️ Service unlocked: ${serviceId} at pop ${population}`);
|
||||||
|
|
||||||
|
// Trigger service-specific initialization
|
||||||
|
switch (serviceId) {
|
||||||
|
case 'market':
|
||||||
|
this.initializeMarket();
|
||||||
|
break;
|
||||||
|
case 'hospital':
|
||||||
|
this.initializeHospital();
|
||||||
|
break;
|
||||||
|
case 'school':
|
||||||
|
this.initializeSchool();
|
||||||
|
break;
|
||||||
|
case 'bank':
|
||||||
|
this.initializeBank();
|
||||||
|
break;
|
||||||
|
case 'museum':
|
||||||
|
this.initializeMuseum();
|
||||||
|
break;
|
||||||
|
case 'theater':
|
||||||
|
this.initializeTheater();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service initializers (placeholders for future implementation)
|
||||||
|
*/
|
||||||
|
initializeMarket() {
|
||||||
|
console.log(' → Market initialized');
|
||||||
|
// TODO: Implement market system
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeHospital() {
|
||||||
|
console.log(' → Hospital initialized');
|
||||||
|
// TODO: Implement hospital system
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeSchool() {
|
||||||
|
console.log(' → School initialized');
|
||||||
|
// TODO: Implement school system
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeBank() {
|
||||||
|
console.log(' → Bank initialized');
|
||||||
|
// TODO: Implement bank system
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeMuseum() {
|
||||||
|
console.log(' → Museum initialized');
|
||||||
|
// TODO: Implement museum system
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeTheater() {
|
||||||
|
console.log(' → Theater initialized');
|
||||||
|
// TODO: Implement theater system
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all systems (called every frame)
|
||||||
|
*/
|
||||||
|
update(time, delta) {
|
||||||
|
const deltaSeconds = delta / 1000;
|
||||||
|
|
||||||
|
// Update systems that need per-frame updates
|
||||||
|
this.sleepSystem.update(deltaSeconds);
|
||||||
|
this.craftingSystem.update(deltaSeconds);
|
||||||
|
|
||||||
|
// Update zombie miners (if not paused by sleep)
|
||||||
|
if (!this.pauseZombieLoyaltyDecay) {
|
||||||
|
this.zombieMinerSystem.update(deltaSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hourly update (called when game hour changes)
|
||||||
|
*/
|
||||||
|
onHourChange(hour) {
|
||||||
|
// Update time-sensitive systems
|
||||||
|
this.bakerySystem.update();
|
||||||
|
this.townGrowthSystem.update();
|
||||||
|
|
||||||
|
// Check for automation collection reminders
|
||||||
|
if (hour % 4 === 0) { // Every 4 hours
|
||||||
|
this.checkAutomationReminders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check automation reminders
|
||||||
|
*/
|
||||||
|
checkAutomationReminders() {
|
||||||
|
// Zombie miner automation
|
||||||
|
if (this.zombieMinerSystem.automationActive) {
|
||||||
|
const hours = this.zombieMinerSystem.getHoursSinceLastCollection();
|
||||||
|
|
||||||
|
if (hours >= 8) {
|
||||||
|
this.game.showNotification({
|
||||||
|
title: '⛏️ Automation Ready',
|
||||||
|
text: `${hours.toFixed(0)}h of mining ready to collect!`,
|
||||||
|
icon: '⛏️'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Daily update (called at midnight)
|
||||||
|
*/
|
||||||
|
onDayChange(day) {
|
||||||
|
console.log(`📅 Day ${day} - Running daily system updates...`);
|
||||||
|
|
||||||
|
// Town growth check
|
||||||
|
this.townGrowthSystem.update();
|
||||||
|
|
||||||
|
// Restock shops
|
||||||
|
this.bakerySystem.restockInventory();
|
||||||
|
|
||||||
|
// Birthday cake deliveries
|
||||||
|
this.bakerySystem.checkBirthdayCakeDeliveries();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save all systems state
|
||||||
|
*/
|
||||||
|
saveAllSystems() {
|
||||||
|
return {
|
||||||
|
version: '1.0',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
systems: {
|
||||||
|
sleep: {
|
||||||
|
playerBed: this.sleepSystem.playerBed,
|
||||||
|
unlockedBeds: Object.entries(this.sleepSystem.bedTypes)
|
||||||
|
.filter(([_, bed]) => bed.unlocked)
|
||||||
|
.map(([key, _]) => key)
|
||||||
|
},
|
||||||
|
crafting: {
|
||||||
|
currentTable: this.craftingSystem.currentTable.id,
|
||||||
|
unlockedRecipes: this.craftingSystem.unlockedRecipes,
|
||||||
|
largeTableUnlocked: this.craftingSystem.tables.LARGE.unlocked
|
||||||
|
},
|
||||||
|
bakery: {
|
||||||
|
isUnlocked: this.bakerySystem.isUnlocked,
|
||||||
|
inventory: this.bakerySystem.inventory,
|
||||||
|
birthdayOrders: this.bakerySystem.birthdayCakeOrders
|
||||||
|
},
|
||||||
|
barber: {
|
||||||
|
isUnlocked: this.barberSystem.isUnlocked,
|
||||||
|
playerAppearance: this.barberSystem.playerAppearance,
|
||||||
|
savedLooks: this.barberSystem.savedLooks,
|
||||||
|
visitCount: this.barberSystem.visitCount
|
||||||
|
},
|
||||||
|
lawyer: {
|
||||||
|
isUnlocked: this.lawyerSystem.isUnlocked,
|
||||||
|
hasPrenup: this.lawyerSystem.hasPrenup,
|
||||||
|
divorceHistory: this.lawyerSystem.divorceHistory,
|
||||||
|
counselingInProgress: this.lawyerSystem.counselingInProgress
|
||||||
|
},
|
||||||
|
zombieMiners: {
|
||||||
|
miners: this.zombieMinerSystem.zombieMiners,
|
||||||
|
equipment: this.zombieMinerSystem.zombieEquipment,
|
||||||
|
lastCollectionTime: this.zombieMinerSystem.lastCollectionTime
|
||||||
|
},
|
||||||
|
townGrowth: {
|
||||||
|
population: this.townGrowthSystem.population,
|
||||||
|
populationSlots: this.townGrowthSystem.populationSlots,
|
||||||
|
villages: this.townGrowthSystem.villages,
|
||||||
|
services: this.townGrowthSystem.services
|
||||||
|
},
|
||||||
|
npcPrivacy: {
|
||||||
|
npcHomes: this.npcPrivacySystem.npcHomes,
|
||||||
|
visitHistory: this.npcPrivacySystem.visitHistory,
|
||||||
|
lastVisitTimes: this.npcPrivacySystem.lastVisitTimes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all systems state
|
||||||
|
*/
|
||||||
|
loadAllSystems(saveData) {
|
||||||
|
if (!saveData || !saveData.systems) {
|
||||||
|
console.warn('No save data to load');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const systems = saveData.systems;
|
||||||
|
|
||||||
|
// Load sleep system
|
||||||
|
if (systems.sleep) {
|
||||||
|
systems.sleep.unlockedBeds.forEach(bedKey => {
|
||||||
|
this.sleepSystem.bedTypes[bedKey].unlocked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load crafting system
|
||||||
|
if (systems.crafting) {
|
||||||
|
this.craftingSystem.tables.LARGE.unlocked = systems.crafting.largeTableUnlocked;
|
||||||
|
this.craftingSystem.unlockedRecipes = systems.crafting.unlockedRecipes || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bakery
|
||||||
|
if (systems.bakery) {
|
||||||
|
this.bakerySystem.isUnlocked = systems.bakery.isUnlocked;
|
||||||
|
this.bakerySystem.birthdayCakeOrders = systems.bakery.birthdayOrders || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load barber
|
||||||
|
if (systems.barber) {
|
||||||
|
this.barberSystem.isUnlocked = systems.barber.isUnlocked;
|
||||||
|
this.barberSystem.playerAppearance = systems.barber.playerAppearance;
|
||||||
|
this.barberSystem.savedLooks = systems.barber.savedLooks || [];
|
||||||
|
this.barberSystem.visitCount = systems.barber.visitCount || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load lawyer
|
||||||
|
if (systems.lawyer) {
|
||||||
|
this.lawyerSystem.isUnlocked = systems.lawyer.isUnlocked;
|
||||||
|
this.lawyerSystem.hasPrenup = systems.lawyer.hasPrenup || false;
|
||||||
|
this.lawyerSystem.divorceHistory = systems.lawyer.divorceHistory || [];
|
||||||
|
this.lawyerSystem.counselingInProgress = systems.lawyer.counselingInProgress || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load zombie miners
|
||||||
|
if (systems.zombieMiners) {
|
||||||
|
this.zombieMinerSystem.zombieMiners = systems.zombieMiners.miners || [];
|
||||||
|
this.zombieMinerSystem.zombieEquipment = systems.zombieMiners.equipment || {};
|
||||||
|
this.zombieMinerSystem.lastCollectionTime = systems.zombieMiners.lastCollectionTime;
|
||||||
|
this.zombieMinerSystem.updateAutomationYield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load town growth
|
||||||
|
if (systems.townGrowth) {
|
||||||
|
this.townGrowthSystem.population = systems.townGrowth.population;
|
||||||
|
this.townGrowthSystem.populationSlots = systems.townGrowth.populationSlots;
|
||||||
|
this.townGrowthSystem.villages = systems.townGrowth.villages || [];
|
||||||
|
this.townGrowthSystem.services = systems.townGrowth.services || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load NPC privacy
|
||||||
|
if (systems.npcPrivacy) {
|
||||||
|
this.npcPrivacySystem.npcHomes = systems.npcPrivacy.npcHomes || {};
|
||||||
|
this.npcPrivacySystem.visitHistory = systems.npcPrivacy.visitHistory || {};
|
||||||
|
this.npcPrivacySystem.lastVisitTimes = systems.npcPrivacy.lastVisitTimes || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All systems loaded from save data');
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error loading systems:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all systems status (for debug/admin panel)
|
||||||
|
*/
|
||||||
|
getAllSystemsStatus() {
|
||||||
|
return {
|
||||||
|
sleep: {
|
||||||
|
active: true,
|
||||||
|
currentBed: this.sleepSystem.playerBed.name,
|
||||||
|
isSleeping: this.sleepSystem.isSleeping
|
||||||
|
},
|
||||||
|
crafting: {
|
||||||
|
active: true,
|
||||||
|
currentTable: this.craftingSystem.currentTable.name,
|
||||||
|
isCrafting: this.craftingSystem.isCrafting,
|
||||||
|
queueLength: this.craftingSystem.craftingQueue.length
|
||||||
|
},
|
||||||
|
bakery: {
|
||||||
|
active: this.bakerySystem.isUnlocked,
|
||||||
|
isOpen: this.bakerySystem.isOpen,
|
||||||
|
stockLevel: Object.values(this.bakerySystem.inventory)
|
||||||
|
.reduce((sum, item) => sum + item.stock, 0)
|
||||||
|
},
|
||||||
|
barber: {
|
||||||
|
active: this.barberSystem.isUnlocked,
|
||||||
|
isOpen: this.barberSystem.isOpen,
|
||||||
|
currentStyle: this.barberSystem.playerAppearance.hairstyle
|
||||||
|
},
|
||||||
|
lawyer: {
|
||||||
|
active: this.lawyerSystem.isUnlocked,
|
||||||
|
counselingActive: this.lawyerSystem.counselingInProgress,
|
||||||
|
hasPrenup: this.lawyerSystem.hasPrenup
|
||||||
|
},
|
||||||
|
zombieMiners: {
|
||||||
|
active: this.zombieMinerSystem.automationActive,
|
||||||
|
minerCount: this.zombieMinerSystem.zombieMiners.length,
|
||||||
|
yieldPerHour: this.zombieMinerSystem.totalYieldPerHour
|
||||||
|
},
|
||||||
|
townGrowth: {
|
||||||
|
active: true,
|
||||||
|
population: this.townGrowthSystem.population,
|
||||||
|
maxPopulation: this.townGrowthSystem.maxPopulation,
|
||||||
|
status: this.townGrowthSystem.getTownStatus()
|
||||||
|
},
|
||||||
|
npcPrivacy: {
|
||||||
|
active: true,
|
||||||
|
homesGenerated: Object.keys(this.npcPrivacySystem.npcHomes).length
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MasterGameSystemsManager;
|
||||||
454
src/systems/NPCPrivacySystem.js
Normal file
454
src/systems/NPCPrivacySystem.js
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
/**
|
||||||
|
* NPC PRIVACY & HOME DECORATION SYSTEM
|
||||||
|
* Part of: Game Systems Expansion
|
||||||
|
* Created: January 4, 2026
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Hobby-based automatic interior generation
|
||||||
|
* - Door lock system (heart-based access)
|
||||||
|
* - NPC home visit mechanics
|
||||||
|
* - Relationship effects from visits
|
||||||
|
* - Privacy violations & consequences
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class NPCPrivacySystem {
|
||||||
|
constructor(game) {
|
||||||
|
this.game = game;
|
||||||
|
this.player = game.player;
|
||||||
|
|
||||||
|
// Privacy settings
|
||||||
|
this.privacyLevels = {
|
||||||
|
PUBLIC: 0, // Anyone can enter (living room, kitchen)
|
||||||
|
FRIENDLY: 3, // 3+ hearts required
|
||||||
|
PRIVATE: 5, // 5+ hearts required (bedroom)
|
||||||
|
INTIMATE: 8, // 8+ hearts required (special rooms)
|
||||||
|
LOCKED: 10 // 10 hearts or marriage required
|
||||||
|
};
|
||||||
|
|
||||||
|
// Visit tracking
|
||||||
|
this.visitHistory = {};
|
||||||
|
this.lastVisitTimes = {};
|
||||||
|
|
||||||
|
// NPC home decorations (generated based on hobby)
|
||||||
|
this.npcHomes = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate NPC home interior based on hobby
|
||||||
|
*/
|
||||||
|
generateNPCHome(npcId) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
if (!npc) return null;
|
||||||
|
|
||||||
|
const hobby = npc.hobby;
|
||||||
|
|
||||||
|
// Base home structure
|
||||||
|
const home = {
|
||||||
|
npcId: npcId,
|
||||||
|
rooms: {
|
||||||
|
living_room: {
|
||||||
|
name: 'Living Room',
|
||||||
|
privacyLevel: this.privacyLevels.PUBLIC,
|
||||||
|
objects: this.generateLivingRoomObjects(hobby)
|
||||||
|
},
|
||||||
|
kitchen: {
|
||||||
|
name: 'Kitchen',
|
||||||
|
privacyLevel: this.privacyLevels.PUBLIC,
|
||||||
|
objects: this.generateKitchenObjects(hobby)
|
||||||
|
},
|
||||||
|
bedroom: {
|
||||||
|
name: 'Bedroom',
|
||||||
|
privacyLevel: this.privacyLevels.PRIVATE,
|
||||||
|
objects: this.generateBedroomObjects(hobby)
|
||||||
|
},
|
||||||
|
hobby_room: {
|
||||||
|
name: this.getHobbyRoomName(hobby),
|
||||||
|
privacyLevel: this.privacyLevels.FRIENDLY,
|
||||||
|
objects: this.generateHobbyRoomObjects(hobby)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visitCount: 0,
|
||||||
|
lastVisit: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store home
|
||||||
|
this.npcHomes[npcId] = home;
|
||||||
|
|
||||||
|
return home;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate living room objects
|
||||||
|
*/
|
||||||
|
generateLivingRoomObjects(hobby) {
|
||||||
|
const base = [
|
||||||
|
'interior_table_small',
|
||||||
|
'interior_bookshelf',
|
||||||
|
'interior_gothic_lantern'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Hobby-specific additions
|
||||||
|
const hobbyAdditions = {
|
||||||
|
fishing: ['mounted_fish', 'fishing_net'],
|
||||||
|
farming: ['grain_sack', 'tool_rack'],
|
||||||
|
cooking: ['recipe_shelf'],
|
||||||
|
reading: ['bookshelf', 'reading_chair'],
|
||||||
|
music: ['guitar_stand', 'music_sheets'],
|
||||||
|
crafting: ['crafting_workshop']
|
||||||
|
};
|
||||||
|
|
||||||
|
return [...base, ...(hobbyAdditions[hobby] || [])];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate kitchen objects
|
||||||
|
*/
|
||||||
|
generateKitchenObjects(hobby) {
|
||||||
|
const base = [
|
||||||
|
'interior_kitchen_stove',
|
||||||
|
'interior_kitchen_counter'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (hobby === 'cooking') {
|
||||||
|
base.push(
|
||||||
|
'interior_kitchen_fridge',
|
||||||
|
'interior_kitchen_sink',
|
||||||
|
'interior_recipe_shelf'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate bedroom objects
|
||||||
|
*/
|
||||||
|
generateBedroomObjects(hobby) {
|
||||||
|
const base = [
|
||||||
|
'interior_bed_wooden',
|
||||||
|
'interior_wardrobe',
|
||||||
|
'interior_chest_locked'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Personal items based on hobby
|
||||||
|
const hobbyItems = {
|
||||||
|
zombie_worker: ['brain_jar', 'work_uniform'],
|
||||||
|
baker: ['flour_workspace', 'dough_tools'],
|
||||||
|
barber: ['dreadlock_kit', 'styling_tools'],
|
||||||
|
fisherman: ['tackle_box', 'fish_collection']
|
||||||
|
};
|
||||||
|
|
||||||
|
return [...base, ...(hobbyItems[hobby] || [])];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate hobby room objects
|
||||||
|
*/
|
||||||
|
generateHobbyRoomObjects(hobby) {
|
||||||
|
const hobbyRooms = {
|
||||||
|
fishing: [
|
||||||
|
'fishing_rod_rack',
|
||||||
|
'bait_storage',
|
||||||
|
'fish_tank',
|
||||||
|
'mounted_trophy_fish'
|
||||||
|
],
|
||||||
|
zombie_worker: [
|
||||||
|
'brain_jar',
|
||||||
|
'work_bench',
|
||||||
|
'tool_storage',
|
||||||
|
'miner_equipment'
|
||||||
|
],
|
||||||
|
baker: [
|
||||||
|
'flour_workspace',
|
||||||
|
'mixing_bowls',
|
||||||
|
'bread_storage',
|
||||||
|
'recipe_collection'
|
||||||
|
],
|
||||||
|
alchemy: [
|
||||||
|
'interior_alchemy_bottle',
|
||||||
|
'interior_brewing_cauldron',
|
||||||
|
'interior_chemistry_set',
|
||||||
|
'interior_potion_shelf'
|
||||||
|
],
|
||||||
|
crafting: [
|
||||||
|
'interior_crafting_workshop',
|
||||||
|
'interior_tool_rack',
|
||||||
|
'material_storage',
|
||||||
|
'blueprint_table'
|
||||||
|
],
|
||||||
|
reading: [
|
||||||
|
'interior_bookshelf',
|
||||||
|
'reading_chair',
|
||||||
|
'ancient_manuscripts',
|
||||||
|
'writing_desk'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return hobbyRooms[hobby] || ['generic_workspace'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hobby room name
|
||||||
|
*/
|
||||||
|
getHobbyRoomName(hobby) {
|
||||||
|
const names = {
|
||||||
|
fishing: 'Fishing Den',
|
||||||
|
zombie_worker: 'Worker\'s Quarters',
|
||||||
|
baker: 'Baking Workshop',
|
||||||
|
alchemy: 'Alchemy Lab',
|
||||||
|
crafting: 'Craft Room',
|
||||||
|
reading: 'Library',
|
||||||
|
music: 'Music Studio',
|
||||||
|
farming: 'Storage Shed'
|
||||||
|
};
|
||||||
|
|
||||||
|
return names[hobby] || 'Hobby Room';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to enter NPC room
|
||||||
|
*/
|
||||||
|
attemptEntry(npcId, roomId) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
if (!npc) {
|
||||||
|
return { success: false, message: 'NPC not found!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate home if doesn't exist
|
||||||
|
if (!this.npcHomes[npcId]) {
|
||||||
|
this.generateNPCHome(npcId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const home = this.npcHomes[npcId];
|
||||||
|
const room = home.rooms[roomId];
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
return { success: false, message: 'Room not found!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check privacy level
|
||||||
|
const playerHearts = this.player.getRelationshipHearts(npcId);
|
||||||
|
const requiredHearts = room.privacyLevel;
|
||||||
|
|
||||||
|
if (playerHearts < requiredHearts) {
|
||||||
|
// Privacy violation!
|
||||||
|
return this.handlePrivacyViolation(npcId, roomId, requiredHearts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed entry
|
||||||
|
return this.handleSuccessfulEntry(npcId, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle privacy violation
|
||||||
|
*/
|
||||||
|
handlePrivacyViolation(npcId, roomId, requiredHearts) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
|
||||||
|
// Relationship penalty
|
||||||
|
const penalty = (requiredHearts - this.player.getRelationshipHearts(npcId)) * 20;
|
||||||
|
npc.addRelationshipPoints(-penalty);
|
||||||
|
|
||||||
|
// NPC reaction
|
||||||
|
const reactions = [
|
||||||
|
"Hey! That's private!",
|
||||||
|
"What are you doing in my room?!",
|
||||||
|
"Get out! This is MY space!",
|
||||||
|
"I can't believe you just walked in here...",
|
||||||
|
"Privacy, please!"
|
||||||
|
];
|
||||||
|
|
||||||
|
const reaction = Phaser.Utils.Array.GetRandom(reactions);
|
||||||
|
|
||||||
|
this.game.showDialogue(npc.name, reaction, {
|
||||||
|
mood: 'angry',
|
||||||
|
animation: 'shocked'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.showMessage(
|
||||||
|
`${npc.name} is upset! -${penalty} relationship points`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Player gets kicked out
|
||||||
|
this.game.player.teleportToLocation('outside_' + npcId + '_home');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
privacyViolation: true,
|
||||||
|
penalty: penalty,
|
||||||
|
requiredHearts: requiredHearts,
|
||||||
|
currentHearts: this.player.getRelationshipHearts(npcId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle successful entry
|
||||||
|
*/
|
||||||
|
handleSuccessfulEntry(npcId, roomId) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
const home = this.npcHomes[npcId];
|
||||||
|
|
||||||
|
// Track visit
|
||||||
|
if (!this.visitHistory[npcId]) {
|
||||||
|
this.visitHistory[npcId] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const visit = {
|
||||||
|
roomId: roomId,
|
||||||
|
timestamp: this.game.time.currentTime,
|
||||||
|
timeOfDay: this.game.time.getTimeOfDay()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.visitHistory[npcId].push(visit);
|
||||||
|
this.lastVisitTimes[npcId] = this.game.time.currentTime;
|
||||||
|
|
||||||
|
home.visitCount++;
|
||||||
|
home.lastVisit = this.game.time.currentTime;
|
||||||
|
|
||||||
|
// Relationship effects based on visit
|
||||||
|
this.applyVisitEffects(npcId, roomId, visit);
|
||||||
|
|
||||||
|
// Enter room scene
|
||||||
|
this.game.scene.start('NPCRoomScene', {
|
||||||
|
npcId: npcId,
|
||||||
|
roomId: roomId,
|
||||||
|
room: home.rooms[roomId]
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
room: home.rooms[roomId],
|
||||||
|
npc: npc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply relationship effects from visit
|
||||||
|
*/
|
||||||
|
applyVisitEffects(npcId, roomId, visit) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
const timeOfDay = visit.timeOfDay;
|
||||||
|
|
||||||
|
// Visit frequency check
|
||||||
|
const recentVisits = this.visitHistory[npcId].filter(v => {
|
||||||
|
const hoursSince = (this.game.time.currentTime - v.timestamp) / 3600;
|
||||||
|
return hoursSince < 24; // Last 24 hours
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recentVisits.length > 3) {
|
||||||
|
// Visiting TOO much = annoying
|
||||||
|
npc.addRelationshipPoints(-10);
|
||||||
|
this.game.showMessage(
|
||||||
|
`${npc.name} seems a bit annoyed by frequent visits...`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time of day effects
|
||||||
|
if (timeOfDay === 'night' && roomId === 'bedroom') {
|
||||||
|
// Visiting bedroom at night = awkward
|
||||||
|
npc.addRelationshipPoints(-5);
|
||||||
|
this.game.showDialogue(
|
||||||
|
npc.name,
|
||||||
|
"It's quite late... Is everything okay?",
|
||||||
|
{ mood: 'concerned' }
|
||||||
|
);
|
||||||
|
} else if (timeOfDay === 'morning' && roomId === 'kitchen') {
|
||||||
|
// Morning kitchen visit = breakfast together!
|
||||||
|
npc.addRelationshipPoints(5);
|
||||||
|
this.game.showDialogue(
|
||||||
|
npc.name,
|
||||||
|
"Good morning! Care to join me for breakfast?",
|
||||||
|
{ mood: 'happy' }
|
||||||
|
);
|
||||||
|
} else if (roomId === 'hobby_room') {
|
||||||
|
// Showing interest in hobby = bonus points!
|
||||||
|
npc.addRelationshipPoints(10);
|
||||||
|
this.game.showDialogue(
|
||||||
|
npc.name,
|
||||||
|
"I'm glad you're interested in my hobby!",
|
||||||
|
{ mood: 'excited' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get visit statistics for NPC
|
||||||
|
*/
|
||||||
|
getVisitStats(npcId) {
|
||||||
|
const visits = this.visitHistory[npcId] || [];
|
||||||
|
|
||||||
|
// Count visits per room
|
||||||
|
const roomCounts = {};
|
||||||
|
visits.forEach(visit => {
|
||||||
|
roomCounts[visit.roomId] = (roomCounts[visit.roomId] || 0) + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Average visits per day
|
||||||
|
const daysSinceFirstVisit = visits.length > 0
|
||||||
|
? (this.game.time.currentTime - visits[0].timestamp) / 86400
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const avgVisitsPerDay = daysSinceFirstVisit > 0
|
||||||
|
? visits.length / daysSinceFirstVisit
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalVisits: visits.length,
|
||||||
|
roomCounts: roomCounts,
|
||||||
|
avgVisitsPerDay: avgVisitsPerDay,
|
||||||
|
lastVisit: this.lastVisitTimes[npcId],
|
||||||
|
favoriteRoom: Object.keys(roomCounts).reduce((a, b) =>
|
||||||
|
roomCounts[a] > roomCounts[b] ? a : b, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if player can access special room
|
||||||
|
*/
|
||||||
|
canAccessSpecialRoom(npcId, roomId) {
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
if (!npc) return false;
|
||||||
|
|
||||||
|
const home = this.npcHomes[npcId];
|
||||||
|
if (!home) return false;
|
||||||
|
|
||||||
|
const room = home.rooms[roomId];
|
||||||
|
if (!room) return false;
|
||||||
|
|
||||||
|
const playerHearts = this.player.getRelationshipHearts(npcId);
|
||||||
|
|
||||||
|
// Special case: marriage allows LOCKED access
|
||||||
|
if (room.privacyLevel === this.privacyLevels.LOCKED) {
|
||||||
|
return this.player.spouse === npcId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerHearts >= room.privacyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get NPC home UI data
|
||||||
|
*/
|
||||||
|
getNPCHomeUIData(npcId) {
|
||||||
|
if (!this.npcHomes[npcId]) {
|
||||||
|
this.generateNPCHome(npcId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const home = this.npcHomes[npcId];
|
||||||
|
const npc = this.game.npcs.get(npcId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
npc: npc,
|
||||||
|
home: home,
|
||||||
|
accessibleRooms: Object.keys(home.rooms).filter(roomId =>
|
||||||
|
this.canAccessSpecialRoom(npcId, roomId)
|
||||||
|
),
|
||||||
|
lockedRooms: Object.keys(home.rooms).filter(roomId =>
|
||||||
|
!this.canAccessSpecialRoom(npcId, roomId)
|
||||||
|
),
|
||||||
|
visitStats: this.getVisitStats(npcId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NPCPrivacySystem;
|
||||||
460
src/systems/TownGrowthSystem.js
Normal file
460
src/systems/TownGrowthSystem.js
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
/**
|
||||||
|
* TOWN GROWTH SYSTEM - Population & Expansion
|
||||||
|
* Part of: Game Systems Expansion
|
||||||
|
* Created: January 4, 2026
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Dynamic town population (4 → 20 NPCs)
|
||||||
|
* - Town sign with live stats
|
||||||
|
* - Small villages (5-10 scattered across map)
|
||||||
|
* - Population unlock requirements
|
||||||
|
* - Town Services unlock based on population
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class TownGrowthSystem {
|
||||||
|
constructor(game) {
|
||||||
|
this.game = game;
|
||||||
|
this.player = game.player;
|
||||||
|
|
||||||
|
// Town stats
|
||||||
|
this.townName = 'Dolina Smrti';
|
||||||
|
this.population = 4; // Starting NPCs: Kai, Ana, Gronk, Baker
|
||||||
|
this.maxPopulation = 20;
|
||||||
|
this.populationSlots = [
|
||||||
|
{ index: 1, unlocked: true, npc: 'kai' },
|
||||||
|
{ index: 2, unlocked: true, npc: 'ana' },
|
||||||
|
{ index: 3, unlocked: true, npc: 'gronk' },
|
||||||
|
{ index: 4, unlocked: true, npc: 'baker' },
|
||||||
|
{ index: 5, unlocked: false, npc: null, requirement: { farmLevel: 2 } },
|
||||||
|
{ index: 6, unlocked: false, npc: null, requirement: { money: 10000 } },
|
||||||
|
{ index: 7, unlocked: false, npc: null, requirement: { quest: 'expand_town_1' } },
|
||||||
|
{ index: 8, unlocked: false, npc: null, requirement: { population: 6 } },
|
||||||
|
{ index: 9, unlocked: false, npc: null, requirement: { building: 'bakery' } },
|
||||||
|
{ index: 10, unlocked: false, npc: null, requirement: { building: 'barbershop' } },
|
||||||
|
{ index: 11, unlocked: false, npc: null, requirement: { hearts: 5, npcId: 'any' } },
|
||||||
|
{ index: 12, unlocked: false, npc: null, requirement: { quest: 'expand_town_2' } },
|
||||||
|
{ index: 13, unlocked: false, npc: null, requirement: { zombieWorkers: 5 } },
|
||||||
|
{ index: 14, unlocked: false, npc: null, requirement: { building: 'mine' } },
|
||||||
|
{ index: 15, unlocked: false, npc: null, requirement: { money: 50000 } },
|
||||||
|
{ index: 16, unlocked: false, npc: null, requirement: { quest: 'expand_town_3' } },
|
||||||
|
{ index: 17, unlocked: false, npc: null, requirement: { marriage: true } },
|
||||||
|
{ index: 18, unlocked: false, npc: null, requirement: { population: 15 } },
|
||||||
|
{ index: 19, unlocked: false, npc: null, requirement: { allBuildings: true } },
|
||||||
|
{ index: 20, unlocked: false, npc: null, requirement: { quest: 'town_master' } }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Town sign
|
||||||
|
this.townSign = {
|
||||||
|
location: { x: 400, y: 300 },
|
||||||
|
visible: true,
|
||||||
|
displayMode: 'population' // 'population', 'status', 'full'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Small villages
|
||||||
|
this.villages = this.initializeVillages();
|
||||||
|
|
||||||
|
// Town services (unlock based on population)
|
||||||
|
this.services = {
|
||||||
|
market: { unlocked: false, requiredPopulation: 6 },
|
||||||
|
hospital: { unlocked: false, requiredPopulation: 8 },
|
||||||
|
school: { unlocked: false, requiredPopulation: 10 },
|
||||||
|
bank: { unlocked: false, requiredPopulation: 12 },
|
||||||
|
museum: { unlocked: false, requiredPopulation: 15 },
|
||||||
|
theater: { unlocked: false, requiredPopulation: 18 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize small villages
|
||||||
|
*/
|
||||||
|
initializeVillages() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'village_north',
|
||||||
|
name: 'Severna Vas',
|
||||||
|
position: { x: 3000, y: 500 },
|
||||||
|
population: 7,
|
||||||
|
discovered: false,
|
||||||
|
npcs: [
|
||||||
|
{ id: 'fisherman', name: 'Old Fisher', hobby: 'fishing' },
|
||||||
|
{ id: 'hunter', name: 'Hunter Dane', hobby: 'hunting' },
|
||||||
|
{ id: 'hermit', name: 'Wise Hermit', hobby: 'meditation' }
|
||||||
|
],
|
||||||
|
specialItems: ['ancient_fishing_rod', 'hunter_bow', 'meditation_mat']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'village_east',
|
||||||
|
name: 'Vzhodna Stran',
|
||||||
|
position: { x: 5000, y: 2000 },
|
||||||
|
population: 5,
|
||||||
|
discovered: false,
|
||||||
|
npcs: [
|
||||||
|
{ id: 'blacksmith', name: 'Master Smith', hobby: 'forging' },
|
||||||
|
{ id: 'alchemist', name: 'Mysterious Alchemist', hobby: 'alchemy' }
|
||||||
|
],
|
||||||
|
specialItems: ['master_anvil', 'legendary_hammer', 'philosopher_stone']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'village_south',
|
||||||
|
name: 'Južno Naselje',
|
||||||
|
position: { x: 2500, y: 4500 },
|
||||||
|
population: 6,
|
||||||
|
discovered: false,
|
||||||
|
npcs: [
|
||||||
|
{ id: 'trader', name: 'Traveling Trader', hobby: 'collecting' },
|
||||||
|
{ id: 'musician', name: 'Bard Luka', hobby: 'music' }
|
||||||
|
],
|
||||||
|
specialItems: ['exotic_seeds', 'rare_instruments', 'ancient_map']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'village_west',
|
||||||
|
name: 'Zahodna Dolina',
|
||||||
|
position: { x: 500, y: 3000 },
|
||||||
|
population: 8,
|
||||||
|
discovered: false,
|
||||||
|
npcs: [
|
||||||
|
{ id: 'chef', name: 'Chef Antonio', hobby: 'cooking' },
|
||||||
|
{ id: 'librarian', name: 'Keeper of Books', hobby: 'reading' },
|
||||||
|
{ id: 'artist', name: 'Painter Ana', hobby: 'painting' }
|
||||||
|
],
|
||||||
|
specialItems: ['master_cookbook', 'ancient_tome', 'rare_pigments']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'village_mysterious',
|
||||||
|
name: '??? Mystery Village',
|
||||||
|
position: { x: 4000, y: 4000 },
|
||||||
|
population: 10,
|
||||||
|
discovered: false,
|
||||||
|
hidden: true, // Only visible after completing special quest
|
||||||
|
npcs: [
|
||||||
|
{ id: 'time_keeper', name: 'Keeper of Time', hobby: 'timekeeping' },
|
||||||
|
{ id: 'oracle', name: 'Oracle of Dolina', hobby: 'prophecy' }
|
||||||
|
],
|
||||||
|
specialItems: ['time_crystal', 'prophecy_scroll', 'reality_gem']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and unlock new population slot
|
||||||
|
*/
|
||||||
|
checkPopulationUnlocks() {
|
||||||
|
let newUnlocks = 0;
|
||||||
|
|
||||||
|
this.populationSlots.forEach(slot => {
|
||||||
|
if (!slot.unlocked && slot.requirement) {
|
||||||
|
if (this.meetsRequirement(slot.requirement)) {
|
||||||
|
slot.unlocked = true;
|
||||||
|
newUnlocks++;
|
||||||
|
|
||||||
|
this.game.showMessage(
|
||||||
|
`New population slot unlocked! (${this.population}/${this.maxPopulation})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newUnlocks > 0) {
|
||||||
|
this.updateTownServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUnlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if requirement is met
|
||||||
|
*/
|
||||||
|
meetsRequirement(requirement) {
|
||||||
|
// Farm level
|
||||||
|
if (requirement.farmLevel) {
|
||||||
|
if (this.player.farmLevel < requirement.farmLevel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Money
|
||||||
|
if (requirement.money) {
|
||||||
|
if (this.player.money < requirement.money) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quest
|
||||||
|
if (requirement.quest) {
|
||||||
|
if (!this.player.hasCompletedQuest(requirement.quest)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Building
|
||||||
|
if (requirement.building) {
|
||||||
|
if (!this.player.hasBuilding(requirement.building)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current population
|
||||||
|
if (requirement.population) {
|
||||||
|
if (this.population < requirement.population) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zombie workers
|
||||||
|
if (requirement.zombieWorkers) {
|
||||||
|
const zombieCount = this.game.zombieWorkers?.getWorkerCount() || 0;
|
||||||
|
if (zombieCount < requirement.zombieWorkers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marriage
|
||||||
|
if (requirement.marriage) {
|
||||||
|
if (!this.player.isMarried) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hearts with any NPC
|
||||||
|
if (requirement.hearts && requirement.npcId === 'any') {
|
||||||
|
const hasHighRelationship = this.game.npcs.getAllNPCs()
|
||||||
|
.some(npc => npc.relationshipHearts >= requirement.hearts);
|
||||||
|
if (!hasHighRelationship) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All buildings
|
||||||
|
if (requirement.allBuildings) {
|
||||||
|
const requiredBuildings = ['bakery', 'barbershop', 'lawyer', 'mine', 'hospital'];
|
||||||
|
if (!requiredBuildings.every(b => this.player.hasBuilding(b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite new NPC to town
|
||||||
|
*/
|
||||||
|
inviteNPC(npcId) {
|
||||||
|
// Find available slot
|
||||||
|
const availableSlot = this.populationSlots.find(
|
||||||
|
slot => slot.unlocked && slot.npc === null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!availableSlot) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'No available population slots!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if NPC exists
|
||||||
|
const npcData = this.game.npcs.getNPCData(npcId);
|
||||||
|
if (!npcData) {
|
||||||
|
return { success: false, message: 'NPC not found!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign NPC to slot
|
||||||
|
availableSlot.npc = npcId;
|
||||||
|
|
||||||
|
// Spawn NPC in town
|
||||||
|
this.game.npcs.spawn(npcId, {
|
||||||
|
homeLocation: 'town',
|
||||||
|
moveInDate: this.game.time.currentDate
|
||||||
|
});
|
||||||
|
|
||||||
|
// Increase population
|
||||||
|
this.population++;
|
||||||
|
|
||||||
|
// Update town sign
|
||||||
|
this.updateTownSign();
|
||||||
|
|
||||||
|
// Check for service unlocks
|
||||||
|
this.updateTownServices();
|
||||||
|
|
||||||
|
this.game.showMessage(
|
||||||
|
`${npcData.name} moved to town! Population: ${this.population}/${this.maxPopulation}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return { success: true, npc: npcData };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update town services based on population
|
||||||
|
*/
|
||||||
|
updateTownServices() {
|
||||||
|
let newServices = [];
|
||||||
|
|
||||||
|
Object.entries(this.services).forEach(([serviceId, service]) => {
|
||||||
|
if (!service.unlocked && this.population >= service.requiredPopulation) {
|
||||||
|
service.unlocked = true;
|
||||||
|
newServices.push(serviceId);
|
||||||
|
|
||||||
|
this.game.showMessage(
|
||||||
|
`🏛️ New service unlocked: ${serviceId}! (Pop: ${this.population})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Trigger service built event
|
||||||
|
this.game.emit('serviceUnlocked', {
|
||||||
|
serviceId: serviceId,
|
||||||
|
population: this.population
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update town sign display
|
||||||
|
*/
|
||||||
|
updateTownSign() {
|
||||||
|
const signData = {
|
||||||
|
townName: this.townName,
|
||||||
|
population: this.population,
|
||||||
|
maxPopulation: this.maxPopulation,
|
||||||
|
status: this.getTownStatus(),
|
||||||
|
services: Object.keys(this.services).filter(s => this.services[s].unlocked).length
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit event to update sign sprite
|
||||||
|
this.game.emit('townSignUpdate', signData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get town status description
|
||||||
|
*/
|
||||||
|
getTownStatus() {
|
||||||
|
if (this.population >= 18) {
|
||||||
|
return 'Thriving City';
|
||||||
|
}
|
||||||
|
if (this.population >= 15) {
|
||||||
|
return 'Prosperous Town';
|
||||||
|
}
|
||||||
|
if (this.population >= 10) {
|
||||||
|
return 'Growing Town';
|
||||||
|
}
|
||||||
|
if (this.population >= 6) {
|
||||||
|
return 'Small Town';
|
||||||
|
}
|
||||||
|
return 'Village';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover village
|
||||||
|
*/
|
||||||
|
discoverVillage(villageId) {
|
||||||
|
const village = this.villages.find(v => v.id === villageId);
|
||||||
|
|
||||||
|
if (!village) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (village.discovered) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Village already discovered!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if hidden village requires quest
|
||||||
|
if (village.hidden && !this.player.hasCompletedQuest('find_mystery_village')) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'This village remains hidden...'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover village
|
||||||
|
village.discovered = true;
|
||||||
|
|
||||||
|
// Spawn village NPCs
|
||||||
|
village.npcs.forEach(npcData => {
|
||||||
|
this.game.npcs.spawn(npcData.id, {
|
||||||
|
homeLocation: villageId,
|
||||||
|
position: village.position,
|
||||||
|
hobby: npcData.hobby
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark special items as available
|
||||||
|
village.specialItems.forEach(itemId => {
|
||||||
|
this.game.items.markAsDiscovered(itemId, villageId);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.showMessage(
|
||||||
|
`Discovered ${village.name}! Population: ${village.population} NPCs`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Achievement
|
||||||
|
const discoveredCount = this.villages.filter(v => v.discovered).length;
|
||||||
|
if (discoveredCount === this.villages.length) {
|
||||||
|
this.game.achievements.unlock('village_explorer');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, village: village };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get travel distance to village
|
||||||
|
*/
|
||||||
|
getTravelDistance(villageId) {
|
||||||
|
const village = this.villages.find(v => v.id === villageId);
|
||||||
|
if (!village) return null;
|
||||||
|
|
||||||
|
const playerPos = this.player.getPosition();
|
||||||
|
const dx = village.position.x - playerPos.x;
|
||||||
|
const dy = village.position.y - playerPos.y;
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get village trade options
|
||||||
|
*/
|
||||||
|
getVillageTradeOptions(villageId) {
|
||||||
|
const village = this.villages.find(v => v.id === villageId);
|
||||||
|
if (!village || !village.discovered) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
villageName: village.name,
|
||||||
|
npcs: village.npcs,
|
||||||
|
specialItems: village.specialItems,
|
||||||
|
population: village.population
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get town growth UI data
|
||||||
|
*/
|
||||||
|
getTownGrowthUIData() {
|
||||||
|
return {
|
||||||
|
townName: this.townName,
|
||||||
|
population: this.population,
|
||||||
|
maxPopulation: this.maxPopulation,
|
||||||
|
status: this.getTownStatus(),
|
||||||
|
populationSlots: this.populationSlots,
|
||||||
|
availableSlots: this.populationSlots.filter(s => s.unlocked && !s.npc).length,
|
||||||
|
services: this.services,
|
||||||
|
villages: this.villages.filter(v => !v.hidden || v.discovered),
|
||||||
|
discoveredVillages: this.villages.filter(v => v.discovered).length,
|
||||||
|
totalVillages: this.villages.filter(v => !v.hidden).length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update (check for new unlocks)
|
||||||
|
*/
|
||||||
|
update() {
|
||||||
|
// Check for new population slot unlocks
|
||||||
|
this.checkPopulationUnlocks();
|
||||||
|
|
||||||
|
// Update town sign
|
||||||
|
this.updateTownSign();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TownGrowthSystem;
|
||||||
Reference in New Issue
Block a user