MASTER SYSTEMS + Town & Privacy

This commit is contained in:
2026-01-04 12:45:29 +01:00
parent 2478589c71
commit c751254648
3 changed files with 1368 additions and 0 deletions

View 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;

View 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;

View 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;