/** * 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 */ 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(); } }