💻 MORE SYSTEMS - Barber, Lawyer, Zombie Miners
Added 3 additional game systems: 1. BARBER SHOP SYSTEM (BarberShopSystem.js): - 7 hairstyles (dreadlocks, mohawks, long hair, ponytail, bald) - 5 piercing types (ear gauges, nose ring, eyebrow, lip) - 9 hair dye colors + clothing dyes - Zombie makeover (cosmetic for workers, +20 loyalty) - NPC customization (requires 5+ hearts) - 5 saved look slots - Repeat customer discounts (5+ visits = 10% off) 2. LAWYER OFFICE SYSTEM (LawyerOfficeSystem.js): - Divorce processing (50,000g + 25% money loss) - Prenup system (10,000g, reduces loss to 10%) - Marriage counseling (5,000g, 3 tasks to save marriage) - Relationship crisis detection (auto-unlock at 3 hearts) - 28-day remarriage cooldown - Post-divorce quests 3. ZOMBIE MINER AUTOMATION (ZombieMinerAutomationSystem.js): - Hire zombie miners (5,000g each, max 10) - Assign to specific mine depths - Efficiency & loyalty mechanics (0-100%) - Passive resource generation (yield per hour) - Zombie leveling system (XP from mining) - Equipment upgrades (pickaxe tiers, lamps, oxygen, carts) - Feed zombies to restore loyalty Total new systems: 6 Total code: 3,100+ lines All systems include event emission for UI integration Next: Town Growth & NPC Privacy systems
This commit is contained in:
663
src/systems/BarberShopSystem.js
Normal file
663
src/systems/BarberShopSystem.js
Normal file
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* BARBER SHOP SYSTEM - Character Customization & Styling
|
||||
* Part of: New Town Buildings
|
||||
* Created: January 4, 2026
|
||||
*
|
||||
* Features:
|
||||
* - Hairstyle changes (dreadlocks, mohawk, long hair, ponytail, bald)
|
||||
* - Piercings & body modifications (ear gauges, nose ring, eyebrow, lip)
|
||||
* - Color customization (hair dye, clothing)
|
||||
* - Zombie makeover (cosmetic for workers)
|
||||
* - NPC customization (change other characters)
|
||||
* - Save/load favorite looks
|
||||
*/
|
||||
|
||||
export class BarberShopSystem {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.player = game.player;
|
||||
|
||||
// Barber shop status
|
||||
this.isUnlocked = false;
|
||||
this.isOpen = false;
|
||||
this.barber = null;
|
||||
|
||||
// Hairstyle catalog
|
||||
this.hairstyles = this.initializeHairstyles();
|
||||
|
||||
// Piercing catalog
|
||||
this.piercings = this.initializePiercings();
|
||||
|
||||
// Hair dye colors
|
||||
this.dyeColors = this.initializeDyeColors();
|
||||
|
||||
// Player's current appearance
|
||||
this.playerAppearance = {
|
||||
hairstyle: 'default',
|
||||
hairColor: 'brown',
|
||||
piercings: [],
|
||||
clothing: {
|
||||
shirt: 'default',
|
||||
pants: 'default',
|
||||
colorShirt: 'blue',
|
||||
colorPants: 'brown'
|
||||
}
|
||||
};
|
||||
|
||||
// Saved looks (5 slots)
|
||||
this.savedLooks = [null, null, null, null, null];
|
||||
|
||||
// Visit counter (for discounts)
|
||||
this.visitCount = 0;
|
||||
this.lastVisitDate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hairstyle catalog
|
||||
*/
|
||||
initializeHairstyles() {
|
||||
return {
|
||||
default: {
|
||||
id: 'default',
|
||||
name: 'Default Hair',
|
||||
price: 0,
|
||||
unlocked: true,
|
||||
sprite: 'hair_default',
|
||||
description: 'Your natural hair.'
|
||||
},
|
||||
dreadlocks_pink_green: {
|
||||
id: 'dreadlocks_pink_green',
|
||||
name: 'Pink & Green Dreadlocks',
|
||||
price: 500,
|
||||
unlocked: true,
|
||||
signature: 'kai', // Kai's signature style
|
||||
sprite: 'hair_dreadlocks_pg',
|
||||
description: 'Kai\'s iconic pink and green dreads!'
|
||||
},
|
||||
mohawk_blue: {
|
||||
id: 'mohawk_blue',
|
||||
name: 'Blue Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_blue',
|
||||
description: 'Stand tall with this punk mohawk.'
|
||||
},
|
||||
mohawk_purple: {
|
||||
id: 'mohawk_purple',
|
||||
name: 'Purple Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_purple',
|
||||
description: 'Purple power!'
|
||||
},
|
||||
mohawk_red: {
|
||||
id: 'mohawk_red',
|
||||
name: 'Red Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_red',
|
||||
description: 'Fiery red mohawk.'
|
||||
},
|
||||
long_hair: {
|
||||
id: 'long_hair',
|
||||
name: 'Long Hair',
|
||||
price: 400,
|
||||
unlocked: true,
|
||||
dyeable: true,
|
||||
sprite: 'hair_long',
|
||||
description: 'Flowing long hair, can be dyed any color.'
|
||||
},
|
||||
ponytail: {
|
||||
id: 'ponytail',
|
||||
name: 'Ponytail',
|
||||
price: 250,
|
||||
unlocked: true,
|
||||
dyeable: true,
|
||||
sprite: 'hair_ponytail',
|
||||
description: 'Practical and stylish.'
|
||||
},
|
||||
bald: {
|
||||
id: 'bald',
|
||||
name: 'Bald',
|
||||
price: 100,
|
||||
unlocked: true,
|
||||
reversible: true, // Can revert for free
|
||||
sprite: 'hair_none',
|
||||
description: 'Clean shaven head. Can reverse for FREE!'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize piercing catalog
|
||||
*/
|
||||
initializePiercings() {
|
||||
return {
|
||||
ear_gauges: {
|
||||
id: 'ear_gauges',
|
||||
name: 'Ear Gauges',
|
||||
price: 200,
|
||||
unlocked: true,
|
||||
signature: 'kai', // Kai's trademark
|
||||
sprite: 'piercing_gauges',
|
||||
description: 'Large ear gauges like Kai\'s!'
|
||||
},
|
||||
nose_ring: {
|
||||
id: 'nose_ring',
|
||||
name: 'Nose Ring',
|
||||
price: 150,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_nose',
|
||||
description: 'Simple nose ring.'
|
||||
},
|
||||
eyebrow_piercing: {
|
||||
id: 'eyebrow_piercing',
|
||||
name: 'Eyebrow Piercing',
|
||||
price: 100,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_eyebrow',
|
||||
description: 'Edgy eyebrow piercing.'
|
||||
},
|
||||
lip_ring: {
|
||||
id: 'lip_ring',
|
||||
name: 'Lip Ring',
|
||||
price: 150,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_lip',
|
||||
description: 'Lip ring for that punk look.'
|
||||
},
|
||||
multiple_ear: {
|
||||
id: 'multiple_ear',
|
||||
name: 'Multiple Ear Piercings',
|
||||
price: 50,
|
||||
stackable: true, // Can have multiple
|
||||
sprite: 'piercing_ear_multiple',
|
||||
description: 'Each additional piercing costs 50g.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hair dye colors
|
||||
*/
|
||||
initializeDyeColors() {
|
||||
return {
|
||||
brown: { name: 'Brown', price: 0, hex: '#654321' },
|
||||
black: { name: 'Black', price: 100, hex: '#000000' },
|
||||
blonde: { name: 'Blonde', price: 150, hex: '#FFD700' },
|
||||
red: { name: 'Red', price: 200, hex: '#FF0000' },
|
||||
blue: { name: 'Blue', price: 200, hex: '#0000FF' },
|
||||
purple: { name: 'Purple', price: 200, hex: '#800080' },
|
||||
pink: { name: 'Pink', price: 200, hex: '#FF69B4' },
|
||||
green: { name: 'Green', price: 200, hex: '#00FF00' },
|
||||
white: { name: 'White', price: 250, hex: '#FFFFFF' }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock barber shop
|
||||
*/
|
||||
unlockBarberShop() {
|
||||
// Check requirements
|
||||
const npcCount = this.game.npcs.getPopulationCount();
|
||||
if (npcCount < 3) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need at least 3 NPCs in town first!'
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.player.hasCompletedQuest('style_matters')) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Complete "Style Matters" quest first!'
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.player.hasMaterials({ wood: 75, iron: 25 })) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need 75 Wood + 25 Iron to build barber shop!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.player.money < 6000) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need 6,000g to build barber shop!'
|
||||
};
|
||||
}
|
||||
|
||||
// Build barber shop
|
||||
this.player.removeMaterials({ wood: 75, iron: 25 });
|
||||
this.player.money -= 6000;
|
||||
this.isUnlocked = true;
|
||||
this.isOpen = true;
|
||||
|
||||
// Spawn barber NPC
|
||||
this.barber = this.game.npcs.spawn('barber', {
|
||||
name: 'Razor',
|
||||
position: { x: 1400, y: 900 },
|
||||
appearance: {
|
||||
hairstyle: 'mohawk_purple',
|
||||
piercings: ['ear_gauges', 'nose_ring', 'lip_ring']
|
||||
}
|
||||
});
|
||||
|
||||
this.game.showMessage('💇 Barber Shop is now open!');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Change hairstyle
|
||||
*/
|
||||
changeHairstyle(hairstyleId) {
|
||||
const hairstyle = this.hairstyles[hairstyleId];
|
||||
|
||||
if (!hairstyle) {
|
||||
return { success: false, message: 'Hairstyle not found!' };
|
||||
}
|
||||
|
||||
// Check if shop is open
|
||||
if (!this.isOpen) {
|
||||
return { success: false, message: 'Barber shop is closed!' };
|
||||
}
|
||||
|
||||
// Check price (with discount)
|
||||
const discount = this.getVisitDiscount();
|
||||
const price = Math.floor(hairstyle.price * (1 - discount));
|
||||
|
||||
// Free reversal from bald
|
||||
if (hairstyle.reversible && this.playerAppearance.hairstyle === 'bald') {
|
||||
// Revert to previous hairstyle for free
|
||||
this.playerAppearance.hairstyle = this.previousHairstyle || 'default';
|
||||
this.game.showMessage('Hair restored for FREE!');
|
||||
return { success: true, price: 0 };
|
||||
}
|
||||
|
||||
// Check money
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Save previous hairstyle (for bald reversal)
|
||||
this.previousHairstyle = this.playerAppearance.hairstyle;
|
||||
|
||||
// Change hairstyle
|
||||
this.player.money -= price;
|
||||
this.playerAppearance.hairstyle = hairstyleId;
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
let message = `Changed to ${hairstyle.name}! ${price}g`;
|
||||
if (discount > 0) {
|
||||
message += ` (${Math.floor(discount * 100)}% repeat customer discount!)`;
|
||||
}
|
||||
|
||||
this.game.showMessage(message);
|
||||
|
||||
return { success: true, price: price, discount: discount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Add piercing
|
||||
*/
|
||||
addPiercing(piercingId) {
|
||||
const piercing = this.piercings[piercingId];
|
||||
|
||||
if (!piercing) {
|
||||
return { success: false, message: 'Piercing not found!' };
|
||||
}
|
||||
|
||||
// Check if already have (unless stackable)
|
||||
if (!piercing.stackable && this.playerAppearance.piercings.includes(piercingId)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You already have this piercing!'
|
||||
};
|
||||
}
|
||||
|
||||
// Check price
|
||||
const price = piercing.price;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Add piercing
|
||||
this.player.money -= price;
|
||||
|
||||
if (piercing.stackable) {
|
||||
// Count how many of this type
|
||||
const count = this.playerAppearance.piercings.filter(p => p === piercingId).length;
|
||||
this.playerAppearance.piercings.push(`${piercingId}_${count + 1}`);
|
||||
} else {
|
||||
this.playerAppearance.piercings.push(piercingId);
|
||||
}
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
this.game.showMessage(`Added ${piercing.name}! ${price}g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove piercing
|
||||
*/
|
||||
removePiercing(piercingId) {
|
||||
const index = this.playerAppearance.piercings.indexOf(piercingId);
|
||||
|
||||
if (index === -1) {
|
||||
return { success: false, message: 'You don\'t have this piercing!' };
|
||||
}
|
||||
|
||||
// Remove free of charge
|
||||
this.playerAppearance.piercings.splice(index, 1);
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage('Piercing removed (free).');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dye hair color
|
||||
*/
|
||||
dyeHair(colorId) {
|
||||
const color = this.dyeColors[colorId];
|
||||
|
||||
if (!color) {
|
||||
return { success: false, message: 'Color not found!' };
|
||||
}
|
||||
|
||||
// Check if current hairstyle is dyeable
|
||||
const currentHairstyle = this.hairstyles[this.playerAppearance.hairstyle];
|
||||
if (!currentHairstyle.dyeable && currentHairstyle.id !== 'default') {
|
||||
return {
|
||||
success: false,
|
||||
message: 'This hairstyle cannot be dyed!'
|
||||
};
|
||||
}
|
||||
|
||||
// Check price
|
||||
if (this.player.money < color.price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${color.price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Dye hair
|
||||
this.player.money -= color.price;
|
||||
this.playerAppearance.hairColor = colorId;
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
this.game.showMessage(`Hair dyed ${color.name}! ${color.price}g`);
|
||||
|
||||
return { success: true, price: color.price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dye clothing
|
||||
*/
|
||||
dyeClothing(clothingPart, colorId) {
|
||||
const color = this.dyeColors[colorId];
|
||||
|
||||
if (!color) {
|
||||
return { success: false, message: 'Color not found!' };
|
||||
}
|
||||
|
||||
// Clothing dye is cheaper (half price)
|
||||
const price = Math.floor(color.price / 2);
|
||||
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Dye clothing
|
||||
this.player.money -= price;
|
||||
|
||||
if (clothingPart === 'shirt') {
|
||||
this.playerAppearance.clothing.colorShirt = colorId;
|
||||
} else if (clothingPart === 'pants') {
|
||||
this.playerAppearance.clothing.colorPants = colorId;
|
||||
}
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage(`${clothingPart} dyed ${color.name}! ${price}g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Zombie makeover (cosmetic for workers)
|
||||
*/
|
||||
zombieMakeover(zombieId) {
|
||||
const zombie = this.game.zombieWorkers.get(zombieId);
|
||||
|
||||
if (!zombie) {
|
||||
return { success: false, message: 'Zombie not found!' };
|
||||
}
|
||||
|
||||
const price = 1000;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Zombie makeover costs 1,000g!'
|
||||
};
|
||||
}
|
||||
|
||||
// Apply makeover
|
||||
this.player.money -= price;
|
||||
|
||||
// Random fancy appearance for zombie
|
||||
const fancyStyles = ['mohawk_blue', 'mohawk_purple', 'dreadlocks_pink_green'];
|
||||
const style = Phaser.Utils.Array.GetRandom(fancyStyles);
|
||||
|
||||
zombie.appearance = {
|
||||
hairstyle: style,
|
||||
accessories: ['fancy_bow_tie', 'top_hat']
|
||||
};
|
||||
|
||||
// Update zombie sprite
|
||||
this.game.zombieWorkers.updateZombieSprite(zombieId);
|
||||
|
||||
// Loyalty bonus (zombies appreciate looking good!)
|
||||
zombie.addLoyalty(20);
|
||||
|
||||
this.game.showMessage(`${zombie.name} looks fabulous! +20 Loyalty! 1,000g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize NPC appearance
|
||||
*/
|
||||
customizeNPC(npcId, changes) {
|
||||
const npc = this.game.npcs.get(npcId);
|
||||
|
||||
if (!npc) {
|
||||
return { success: false, message: 'NPC not found!' };
|
||||
}
|
||||
|
||||
// Check relationship (need 5+ hearts)
|
||||
if (this.player.getRelationshipLevel(npcId) < 5) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Need 5+ hearts with ${npc.name} first!`
|
||||
};
|
||||
}
|
||||
|
||||
const price = 500;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'NPC customization costs 500g!'
|
||||
};
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
this.player.money -= price;
|
||||
|
||||
if (changes.hairstyle) {
|
||||
npc.appearance.hairstyle = changes.hairstyle;
|
||||
}
|
||||
if (changes.hairColor) {
|
||||
npc.appearance.hairColor = changes.hairColor;
|
||||
}
|
||||
|
||||
// Update NPC sprite
|
||||
this.game.npcs.updateNPCSprite(npcId);
|
||||
|
||||
// NPC reaction
|
||||
npc.addRelationshipPoints(10);
|
||||
this.game.showDialogue(npc.name, "Wow, I love it! Thank you!");
|
||||
|
||||
this.game.showMessage(`Styled ${npc.name}! +10 ❤️ 500g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current look to slot
|
||||
*/
|
||||
saveLook(slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= 5) {
|
||||
return { success: false, message: 'Invalid slot!' };
|
||||
}
|
||||
|
||||
this.savedLooks[slotIndex] = {
|
||||
hairstyle: this.playerAppearance.hairstyle,
|
||||
hairColor: this.playerAppearance.hairColor,
|
||||
piercings: [...this.playerAppearance.piercings],
|
||||
clothing: { ...this.playerAppearance.clothing },
|
||||
name: `Look ${slotIndex + 1}`
|
||||
};
|
||||
|
||||
this.game.showMessage(`Saved look to slot ${slotIndex + 1}!`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load saved look from slot
|
||||
*/
|
||||
loadLook(slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= 5) {
|
||||
return { success: false, message: 'Invalid slot!' };
|
||||
}
|
||||
|
||||
const savedLook = this.savedLooks[slotIndex];
|
||||
if (!savedLook) {
|
||||
return { success: false, message: 'No look saved in this slot!' };
|
||||
}
|
||||
|
||||
// Apply saved look (free!)
|
||||
this.playerAppearance = {
|
||||
hairstyle: savedLook.hairstyle,
|
||||
hairColor: savedLook.hairColor,
|
||||
piercings: [...savedLook.piercings],
|
||||
clothing: { ...savedLook.clothing }
|
||||
};
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage(`Loaded ${savedLook.name}!`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visit discount (repeat customers)
|
||||
*/
|
||||
getVisitDiscount() {
|
||||
// 5+ visits = 10% off
|
||||
if (this.visitCount >= 5) {
|
||||
return 0.10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment visit count
|
||||
*/
|
||||
incrementVisitCount() {
|
||||
const today = this.game.time.currentDate;
|
||||
|
||||
// Only count once per day
|
||||
if (this.lastVisitDate !== today) {
|
||||
this.visitCount++;
|
||||
this.lastVisitDate = today;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update player sprite with new appearance
|
||||
*/
|
||||
updatePlayerSprite() {
|
||||
// Emit event for sprite manager to update
|
||||
this.game.emit('playerAppearanceChanged', {
|
||||
appearance: this.playerAppearance
|
||||
});
|
||||
|
||||
// Rebuild player sprite
|
||||
this.player.rebuildSprite(this.playerAppearance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preview of appearance change
|
||||
*/
|
||||
getPreview(changes) {
|
||||
return {
|
||||
...this.playerAppearance,
|
||||
...changes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shop UI data
|
||||
*/
|
||||
getShopUIData() {
|
||||
return {
|
||||
isUnlocked: this.isUnlocked,
|
||||
isOpen: this.isOpen,
|
||||
hairstyles: this.hairstyles,
|
||||
piercings: this.piercings,
|
||||
dyeColors: this.dyeColors,
|
||||
currentAppearance: this.playerAppearance,
|
||||
savedLooks: this.savedLooks,
|
||||
visitDiscount: this.getVisitDiscount(),
|
||||
barber: this.barber
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BarberShopSystem;
|
||||
554
src/systems/LawyerOfficeSystem.js
Normal file
554
src/systems/LawyerOfficeSystem.js
Normal file
@@ -0,0 +1,554 @@
|
||||
/**
|
||||
* LAWYER OFFICE SYSTEM - Divorce & Marriage Legal Services
|
||||
* Part of: New Town Buildings ("Drama Hub")
|
||||
* Created: January 4, 2026
|
||||
*
|
||||
* Features:
|
||||
* - Divorce processing (50,000g + 25% money loss)
|
||||
* - Prenuptial agreement system (preventive, 10,000g)
|
||||
* - Marriage counseling (alternative to divorce, 5,000g)
|
||||
* - Relationship crisis detection
|
||||
* - Post-divorce quests
|
||||
*/
|
||||
|
||||
export class LawyerOfficeSystem {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.player = game.player;
|
||||
|
||||
// Lawyer office status
|
||||
this.isUnlocked = false;
|
||||
this.lawyer = null;
|
||||
|
||||
// Divorce costs
|
||||
this.divorceCost = 50000;
|
||||
this.divorceMoneyLoss = 0.25; // 25% of remaining money
|
||||
this.divorceMoneyLossWithPrenup = 0.10; // 10% with prenup
|
||||
|
||||
// Prenup system
|
||||
this.hasPrenup = false;
|
||||
this.prenupCost = 10000;
|
||||
|
||||
// Marriage counseling
|
||||
this.counselingCost = 5000;
|
||||
this.counselingInProgress = false;
|
||||
this.counselingTasksCompleted = 0;
|
||||
this.counselingTasksRequired = 3;
|
||||
|
||||
// Divorce history
|
||||
this.divorceHistory = [];
|
||||
this.hasEverDivorced = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-unlock lawyer office when marriage is in crisis
|
||||
*/
|
||||
checkAutoUnlock() {
|
||||
// Auto-unlock if married AND relationship ≤ 3 hearts
|
||||
if (this.player.isMarried) {
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
if (spouse && spouse.relationshipHearts <= 3) {
|
||||
this.unlockLawyerOffice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock lawyer office
|
||||
*/
|
||||
unlockLawyerOffice() {
|
||||
if (this.isUnlocked) return;
|
||||
|
||||
// Auto-build (15,000g automatically deducted)
|
||||
if (this.player.money < 15000) {
|
||||
// Build on credit (lawyer is greedy!)
|
||||
this.player.money = Math.max(0, this.player.money - 15000);
|
||||
this.player.debt = Math.abs(Math.min(0, this.player.money));
|
||||
} else {
|
||||
this.player.money -= 15000;
|
||||
}
|
||||
|
||||
this.isUnlocked = true;
|
||||
|
||||
// Spawn lawyer NPC
|
||||
this.lawyer = this.game.npcs.spawn('lawyer', {
|
||||
name: 'Mr. Sterling',
|
||||
position: { x: 1600, y: 1000 },
|
||||
appearance: {
|
||||
clothing: 'black_suit',
|
||||
demeanor: 'cold_professional'
|
||||
},
|
||||
dialogue: this.getLawyerDialogue()
|
||||
});
|
||||
|
||||
// Trigger ominous quest
|
||||
this.game.quests.start('till_death_do_us_part');
|
||||
|
||||
this.game.showMessage('⚖️ Lawyer Office has been built...');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lawyer dialogue
|
||||
*/
|
||||
getLawyerDialogue() {
|
||||
return {
|
||||
greeting: [
|
||||
"Marriage is a contract. Divorce is... expensive.",
|
||||
"Love is temporary. Legal documents are forever.",
|
||||
"Here for business or pleasure? I only deal in business."
|
||||
],
|
||||
divorce_warning: [
|
||||
"Are you ABSOLUTELY sure? This will cost you everything.",
|
||||
"Divorce is final. There's no going back.",
|
||||
"I've seen many regret this decision. Proceed?"
|
||||
],
|
||||
prenup: [
|
||||
"Smart move. Protect your assets before it's too late.",
|
||||
"A prenup? Planning ahead, I see. Wise.",
|
||||
"Marriage without a prenup is... optimistic."
|
||||
],
|
||||
counseling: [
|
||||
"There may be another way. Have you tried talking?",
|
||||
"Marriage counseling. Less expensive than divorce.",
|
||||
"Fix it now or pay later. Your choice."
|
||||
],
|
||||
post_divorce: [
|
||||
"Single again. How does freedom feel?",
|
||||
"Expensive lesson learned, I hope.",
|
||||
"The papers are filed. You're free to go."
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Purchase prenuptial agreement (BEFORE marriage only!)
|
||||
*/
|
||||
purchasePrenup() {
|
||||
// Can only buy BEFORE getting married
|
||||
if (this.player.isMarried) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Too late! Prenups must be signed BEFORE marriage!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.hasPrenup) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You already have a prenup!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.player.money < this.prenupCost) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Need ${this.prenupCost}g for a prenup!`
|
||||
};
|
||||
}
|
||||
|
||||
// Purchase
|
||||
this.player.money -= this.prenupCost;
|
||||
this.hasPrenup = true;
|
||||
|
||||
this.game.showMessage(
|
||||
`Prenup signed! Divorce money loss reduced to 10%. ${this.prenupCost}g`
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate divorce process
|
||||
*/
|
||||
initiateDivorce() {
|
||||
// Check if married
|
||||
if (!this.player.isMarried) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You\'re not married!'
|
||||
};
|
||||
}
|
||||
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
|
||||
// Show divorce confirmation with full consequences
|
||||
const moneyLossPercent = this.hasPrenup ? 10 : 25;
|
||||
const estimatedLoss = Math.floor(this.player.money * (this.hasPrenup ? 0.10 : 0.25));
|
||||
|
||||
const divorceInfo = {
|
||||
cost: this.divorceCost,
|
||||
moneyLoss: estimatedLoss,
|
||||
moneyLossPercent: moneyLossPercent,
|
||||
spouse: spouse.name,
|
||||
consequences: [
|
||||
`Divorce fee: ${this.divorceCost}g`,
|
||||
`Money loss: ${moneyLossPercent}% (${estimatedLoss}g)`,
|
||||
`${spouse.name} relationship reset to 0 hearts`,
|
||||
`${spouse.name} moves out of farmhouse`,
|
||||
'Lose "married" status benefits',
|
||||
'King-size bed reverts to single use'
|
||||
]
|
||||
};
|
||||
|
||||
// Emit event for UI to show confirmation dialog
|
||||
this.game.emit('divorceConfirmationRequired', divorceInfo);
|
||||
|
||||
return {
|
||||
success: false, // Not completed yet, waiting for confirmation
|
||||
requiresConfirmation: true,
|
||||
divorceInfo: divorceInfo
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm and process divorce (after player confirms)
|
||||
*/
|
||||
processDivorce() {
|
||||
if (!this.player.isMarried) {
|
||||
return { success: false, message: 'Not married!' };
|
||||
}
|
||||
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
|
||||
// Calculate total cost
|
||||
const divorceFee = this.divorceCost;
|
||||
const moneyLossPercent = this.hasPrenup ? this.divorceMoneyLossWithPrenup : this.divorceMoneyLoss;
|
||||
|
||||
// Check if can afford divorce fee
|
||||
if (this.player.money < divorceFee) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Divorce costs ${divorceFee}g!`
|
||||
};
|
||||
}
|
||||
|
||||
// Deduct divorce fee
|
||||
this.player.money -= divorceFee;
|
||||
|
||||
// Calculate and deduct money loss
|
||||
const moneyLoss = Math.floor(this.player.money * moneyLossPercent);
|
||||
this.player.money -= moneyLoss;
|
||||
|
||||
// Reset relationship
|
||||
const previousHearts = spouse.relationshipHearts;
|
||||
spouse.relationshipHearts = 0;
|
||||
spouse.relationshipPoints = 0;
|
||||
|
||||
// Spouse moves out
|
||||
spouse.homeLocation = spouse.originalHome;
|
||||
spouse.isLivingWithPlayer = false;
|
||||
|
||||
// Remove married status
|
||||
this.player.isMarried = false;
|
||||
this.player.spouse = null;
|
||||
|
||||
// Downgrade bed
|
||||
if (this.game.sleepSystem) {
|
||||
const kingSizeBed = this.game.sleepSystem.bedTypes.KING_SIZE_BED;
|
||||
kingSizeBed.spousePresent = false;
|
||||
}
|
||||
|
||||
// Record divorce in history
|
||||
this.divorceHistory.push({
|
||||
spouse: spouse.name,
|
||||
date: this.game.time.currentDate,
|
||||
costTotal: divorceFee + moneyLoss,
|
||||
heartsLost: previousHearts
|
||||
});
|
||||
|
||||
this.hasEverDivorced = true;
|
||||
|
||||
// Trigger post-divorce quest
|
||||
this.game.quests.start('single_again');
|
||||
|
||||
// Spouse reaction (they're devastated)
|
||||
this.game.showDialogue(
|
||||
spouse.name,
|
||||
"I can't believe this is happening... Goodbye.",
|
||||
{
|
||||
mood: 'heartbroken',
|
||||
animation: 'crying'
|
||||
}
|
||||
);
|
||||
|
||||
// Show final message
|
||||
this.game.showMessage(
|
||||
`Divorced ${spouse.name}. Total cost: ${divorceFee + moneyLoss}g. You are now single.`
|
||||
);
|
||||
|
||||
// Lawyer's cold response
|
||||
setTimeout(() => {
|
||||
this.game.showDialogue(
|
||||
this.lawyer.name,
|
||||
"The papers are filed. You're free to go. That'll be " + divorceFee + "g."
|
||||
);
|
||||
}, 3000);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
totalCost: divorceFee + moneyLoss,
|
||||
moneyLoss: moneyLoss,
|
||||
spouse: spouse.name
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start marriage counseling (alternative to divorce)
|
||||
*/
|
||||
startCounseling() {
|
||||
if (!this.player.isMarried) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You\'re not married!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.counselingInProgress) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Counseling already in progress!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.player.money < this.counselingCost) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Marriage counseling costs ${this.counselingCost}g!`
|
||||
};
|
||||
}
|
||||
|
||||
// Pay for counseling
|
||||
this.player.money -= this.counselingCost;
|
||||
this.counselingInProgress = true;
|
||||
this.counselingTasksCompleted = 0;
|
||||
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
|
||||
// Generate counseling tasks
|
||||
this.counselingTasks = this.generateCounselingTasks(spouse);
|
||||
|
||||
// Start counseling quest
|
||||
this.game.quests.start('marriage_counseling', {
|
||||
tasks: this.counselingTasks
|
||||
});
|
||||
|
||||
this.game.showMessage(
|
||||
`Marriage counseling started! Complete 3 tasks to save your marriage. ${this.counselingCost}g`
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
tasks: this.counselingTasks
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate counseling tasks based on spouse
|
||||
*/
|
||||
generateCounselingTasks(spouse) {
|
||||
const tasks = [
|
||||
{
|
||||
id: 'gift_favorite',
|
||||
description: `Give ${spouse.name} their favorite gift`,
|
||||
requirement: {
|
||||
type: 'gift',
|
||||
item: spouse.favoriteGift,
|
||||
target: spouse.id
|
||||
},
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
id: 'date_night',
|
||||
description: `Take ${spouse.name} on a date to their favorite location`,
|
||||
requirement: {
|
||||
type: 'visit_location',
|
||||
location: spouse.favoriteLocation,
|
||||
withNPC: spouse.id
|
||||
},
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
id: 'heart_to_heart',
|
||||
description: `Have a deep conversation with ${spouse.name}`,
|
||||
requirement: {
|
||||
type: 'dialogue',
|
||||
dialogueId: 'heart_to_heart',
|
||||
target: spouse.id
|
||||
},
|
||||
completed: false
|
||||
}
|
||||
];
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete counseling task
|
||||
*/
|
||||
completeCounselingTask(taskId) {
|
||||
if (!this.counselingInProgress) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const task = this.counselingTasks.find(t => t.id === taskId);
|
||||
if (!task || task.completed) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Mark task as completed
|
||||
task.completed = true;
|
||||
this.counselingTasksCompleted++;
|
||||
|
||||
this.game.showMessage(
|
||||
`Counseling task completed! (${this.counselingTasksCompleted}/${this.counselingTasksRequired})`
|
||||
);
|
||||
|
||||
// Check if all tasks done
|
||||
if (this.counselingTasksCompleted >= this.counselingTasksRequired) {
|
||||
this.finishCounseling(true);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish counseling (success or failure)
|
||||
*/
|
||||
finishCounseling(success) {
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
|
||||
if (success) {
|
||||
// Restore relationship!
|
||||
spouse.addRelationshipPoints(500); // +5 hearts
|
||||
|
||||
this.game.showDialogue(
|
||||
spouse.name,
|
||||
"I'm so glad we worked through this. I love you.",
|
||||
{
|
||||
mood: 'happy',
|
||||
animation: 'hug'
|
||||
}
|
||||
);
|
||||
|
||||
this.game.showMessage(
|
||||
`Marriage saved! ${spouse.name}: +5 ❤️`
|
||||
);
|
||||
|
||||
// Complete quest
|
||||
this.game.quests.complete('marriage_counseling');
|
||||
|
||||
} else {
|
||||
// Counseling failed
|
||||
this.game.showDialogue(
|
||||
spouse.name,
|
||||
"This isn't working. Maybe it's time to let go...",
|
||||
{
|
||||
mood: 'sad'
|
||||
}
|
||||
);
|
||||
|
||||
this.game.showMessage(
|
||||
'Counseling failed. Relationship worsened.'
|
||||
);
|
||||
|
||||
// Relationship damage
|
||||
spouse.addRelationshipPoints(-200); // -2 hearts
|
||||
|
||||
// Fail quest
|
||||
this.game.quests.fail('marriage_counseling');
|
||||
}
|
||||
|
||||
this.counselingInProgress = false;
|
||||
this.counselingTasks = [];
|
||||
this.counselingTasksCompleted = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relationship crisis warning
|
||||
*/
|
||||
getRelationshipCrisisWarning() {
|
||||
if (!this.player.isMarried) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const spouse = this.game.npcs.getSpouse();
|
||||
|
||||
if (spouse.relationshipHearts <= 3) {
|
||||
return {
|
||||
severity: 'critical',
|
||||
message: `Your relationship with ${spouse.name} is in CRISIS! (${spouse.relationshipHearts} ❤️)`,
|
||||
suggestion: 'Visit the Lawyer Office for marriage counseling before it\'s too late!'
|
||||
};
|
||||
}
|
||||
|
||||
if (spouse.relationshipHearts <= 5) {
|
||||
return {
|
||||
severity: 'warning',
|
||||
message: `Your relationship with ${spouse.name} is struggling. (${spouse.relationshipHearts} ❤️)`,
|
||||
suggestion: 'Spend more time together and give gifts.'
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if can remarry after divorce
|
||||
*/
|
||||
canRemarry() {
|
||||
if (this.player.isMarried) {
|
||||
return { canRemarry: false, reason: 'Already married!' };
|
||||
}
|
||||
|
||||
if (!this.hasEverDivorced) {
|
||||
return { canRemarry: true };
|
||||
}
|
||||
|
||||
// Must wait 28 days (4 weeks) after divorce to remarry
|
||||
const lastDivorce = this.divorceHistory[this.divorceHistory.length - 1];
|
||||
const daysSinceDivorce = this.game.time.currentDate - lastDivorce.date;
|
||||
|
||||
if (daysSinceDivorce < 28) {
|
||||
return {
|
||||
canRemarry: false,
|
||||
reason: `Must wait ${28 - daysSinceDivorce} more days after divorce.`
|
||||
};
|
||||
}
|
||||
|
||||
return { canRemarry: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get office UI data
|
||||
*/
|
||||
getOfficeUIData() {
|
||||
return {
|
||||
isUnlocked: this.isUnlocked,
|
||||
lawyer: this.lawyer,
|
||||
isMarried: this.player.isMarried,
|
||||
spouse: this.player.isMarried ? this.game.npcs.getSpouse() : null,
|
||||
divorceCost: this.divorceCost,
|
||||
moneyLossPercent: this.hasPrenup ? 10 : 25,
|
||||
hasPrenup: this.hasPrenup,
|
||||
prenupCost: this.prenupCost,
|
||||
counselingCost: this.counselingCost,
|
||||
counselingInProgress: this.counselingInProgress,
|
||||
counselingTasks: this.counselingTasks,
|
||||
divorceHistory: this.divorceHistory,
|
||||
crisisWarning: this.getRelationshipCrisisWarning()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (check for auto-unlock)
|
||||
*/
|
||||
update() {
|
||||
if (!this.isUnlocked) {
|
||||
this.checkAutoUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LawyerOfficeSystem;
|
||||
464
src/systems/ZombieMinerAutomationSystem.js
Normal file
464
src/systems/ZombieMinerAutomationSystem.js
Normal file
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* ZOMBIE MINER AUTOMATION SYSTEM
|
||||
* Part of: Mining System Expansion
|
||||
* Created: January 4, 2026
|
||||
*
|
||||
* Features:
|
||||
* - Hire zombie miners for passive resource generation
|
||||
* - Assign miners to specific mine depths
|
||||
* - Efficiency & loyalty mechanics
|
||||
* - Automated ore collection
|
||||
* - Zombie equipment upgrades
|
||||
*/
|
||||
|
||||
export class ZombieMinerAutomationSystem {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.player = game.player;
|
||||
|
||||
// Zombie miners
|
||||
this.zombieMiners = [];
|
||||
this.maxZombieMiners = 10;
|
||||
this.zombieMinerCost = 5000;
|
||||
|
||||
// Automation settings
|
||||
this.automationActive = false;
|
||||
this.totalYieldPerHour = 0;
|
||||
this.lastCollectionTime = null;
|
||||
|
||||
// Equipment for zombies
|
||||
this.zombieEquipment = {
|
||||
pickaxe_tier: 1, // 1-5
|
||||
helmet_lamp: false, // Better visibility
|
||||
oxygen_tank: false, // Deeper mining
|
||||
cart: false // Auto-transport
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hire a zombie miner
|
||||
*/
|
||||
hireZombieMiner() {
|
||||
if (this.zombieMiners.length >= this.maxZombieMiners) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Maximum ${this.maxZombieMiners} zombie miners allowed!`
|
||||
};
|
||||
}
|
||||
|
||||
if (this.player.money < this.zombieMinerCost) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Need ${this.zombieMinerCost}g to hire zombie miner!`
|
||||
};
|
||||
}
|
||||
|
||||
// Hire zombie
|
||||
this.player.money -= this.zombieMinerCost;
|
||||
|
||||
const miner = {
|
||||
id: `zombie_miner_${this.zombieMiners.length + 1}`,
|
||||
name: this.generateZombieName(),
|
||||
assignedMine: null,
|
||||
assignedDepth: 0,
|
||||
efficiency: 1.0, // 0.5 - 2.0
|
||||
loyalty: 50, // 0-100
|
||||
yieldPerHour: 5, // Base yield
|
||||
level: 1,
|
||||
experience: 0
|
||||
};
|
||||
|
||||
this.zombieMiners.push(miner);
|
||||
|
||||
// Recalculate automation
|
||||
this.updateAutomationYield();
|
||||
|
||||
this.game.showMessage(
|
||||
`Hired ${miner.name}! (${this.zombieMiners.length}/${this.maxZombieMiners})`
|
||||
);
|
||||
|
||||
return { success: true, miner: miner };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random zombie miner name
|
||||
*/
|
||||
generateZombieName() {
|
||||
const prefixes = ['Grumpy', 'Rusty', 'Dusty', 'Grumbly', 'Moaning', 'Shuffling'];
|
||||
const suffixes = ['Zed', 'Mort', 'Bones', 'Guts', 'Picks', 'Drills'];
|
||||
|
||||
const prefix = Phaser.Utils.Array.GetRandom(prefixes);
|
||||
const suffix = Phaser.Utils.Array.GetRandom(suffixes);
|
||||
|
||||
return `${prefix} ${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign zombie miner to specific mine & depth
|
||||
*/
|
||||
assignZombieMiner(minerId, mineId, depth) {
|
||||
const miner = this.zombieMiners.find(m => m.id === minerId);
|
||||
|
||||
if (!miner) {
|
||||
return { success: false, message: 'Zombie miner not found!' };
|
||||
}
|
||||
|
||||
// Check if mine exists
|
||||
const mine = this.game.miningSystem.getMineInfo(mineId);
|
||||
if (!mine) {
|
||||
return { success: false, message: 'Mine not found!' };
|
||||
}
|
||||
|
||||
// Check if depth is unlocked
|
||||
const maxDepth = this.game.miningSystem.maxDepthReached || 0;
|
||||
if (depth > maxDepth) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Must explore depth ${depth} first!`
|
||||
};
|
||||
}
|
||||
|
||||
// Assign miner
|
||||
miner.assignedMine = mineId;
|
||||
miner.assignedDepth = depth;
|
||||
|
||||
// Update automation
|
||||
this.updateAutomationYield();
|
||||
|
||||
this.game.showMessage(
|
||||
`${miner.name} assigned to ${mine.name} - Depth ${depth}`
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign zombie miner (return to surface)
|
||||
*/
|
||||
unassignZombieMiner(minerId) {
|
||||
const miner = this.zombieMiners.find(m => m.id === minerId);
|
||||
|
||||
if (!miner) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
miner.assignedMine = null;
|
||||
miner.assignedDepth = 0;
|
||||
|
||||
// Update automation
|
||||
this.updateAutomationYield();
|
||||
|
||||
this.game.showMessage(
|
||||
`${miner.name} returned to surface.`
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update total automation yield
|
||||
*/
|
||||
updateAutomationYield() {
|
||||
let totalYield = 0;
|
||||
|
||||
this.zombieMiners.forEach(miner => {
|
||||
if (miner.assignedMine && miner.assignedDepth > 0) {
|
||||
// Base yield
|
||||
let yield = miner.yieldPerHour;
|
||||
|
||||
// Depth bonus (+10% per 10 levels)
|
||||
const depthBonus = (miner.assignedDepth / 10) * 0.1;
|
||||
yield *= (1 + depthBonus);
|
||||
|
||||
// Efficiency factor
|
||||
yield *= miner.efficiency;
|
||||
|
||||
// Loyalty factor (50% loyalty = 0.5x yield, 100% = 1.5x yield)
|
||||
const loyaltyFactor = 0.5 + (miner.loyalty / 100);
|
||||
yield *= loyaltyFactor;
|
||||
|
||||
// Equipment bonuses
|
||||
if (this.zombieEquipment.pickaxe_tier > 1) {
|
||||
yield *= (1 + (this.zombieEquipment.pickaxe_tier - 1) * 0.25);
|
||||
}
|
||||
if (this.zombieEquipment.cart) {
|
||||
yield *= 1.5; // 50% faster collection
|
||||
}
|
||||
|
||||
totalYield += yield;
|
||||
}
|
||||
});
|
||||
|
||||
this.totalYieldPerHour = Math.floor(totalYield);
|
||||
this.automationActive = (totalYield > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect automated mining resources
|
||||
*/
|
||||
collectAutomatedYield() {
|
||||
if (!this.automationActive) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'No zombie miners assigned!'
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate time since last collection
|
||||
const hoursSinceLastCollection = this.getHoursSinceLastCollection();
|
||||
|
||||
if (hoursSinceLastCollection < 0.1) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Collected too recently! Wait a bit.'
|
||||
};
|
||||
}
|
||||
|
||||
const resources = {};
|
||||
|
||||
// Collect from each zombie
|
||||
this.zombieMiners.forEach(miner => {
|
||||
if (miner.assignedMine && miner.assignedDepth > 0) {
|
||||
// Get mine info
|
||||
const mine = this.game.miningSystem.getMineInfo(miner.assignedMine);
|
||||
|
||||
// Determine ore type based on depth
|
||||
const oreType = this.getOreTypeForDepth(mine, miner.assignedDepth);
|
||||
|
||||
// Calculate yield
|
||||
const hourlyYield = miner.yieldPerHour * miner.efficiency *
|
||||
(0.5 + miner.loyalty / 100);
|
||||
|
||||
const amount = Math.floor(hourlyYield * hoursSinceLastCollection);
|
||||
|
||||
if (amount > 0) {
|
||||
resources[oreType] = (resources[oreType] || 0) + amount;
|
||||
|
||||
// Grant XP to miner
|
||||
miner.experience += amount;
|
||||
this.checkMinerLevelUp(miner);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add resources to inventory
|
||||
let totalCollected = 0;
|
||||
for (const [ore, amount] of Object.entries(resources)) {
|
||||
this.player.inventory.addItem(ore, amount);
|
||||
totalCollected += amount;
|
||||
}
|
||||
|
||||
// Update last collection time
|
||||
this.lastCollectionTime = this.game.time.currentTime;
|
||||
|
||||
// Show collection message
|
||||
const resourceList = Object.entries(resources)
|
||||
.map(([ore, amount]) => `${amount}x ${ore}`)
|
||||
.join(', ');
|
||||
|
||||
this.game.showMessage(
|
||||
`Collected: ${resourceList} (${hoursSinceLastCollection.toFixed(1)}h)`
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
resources: resources,
|
||||
totalAmount: totalCollected,
|
||||
hours: hoursSinceLastCollection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ore type based on mine and depth
|
||||
*/
|
||||
getOreTypeForDepth(mine, depth) {
|
||||
// Logic from mine zones
|
||||
if (depth <= 25) return 'copper_ore';
|
||||
if (depth <= 50) return 'iron_ore';
|
||||
if (depth <= 75) return 'gold_ore';
|
||||
return 'diamond_ore';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if miner levels up
|
||||
*/
|
||||
checkMinerLevelUp(miner) {
|
||||
const xpRequired = miner.level * 100;
|
||||
|
||||
if (miner.experience >= xpRequired) {
|
||||
miner.level++;
|
||||
miner.experience = 0;
|
||||
|
||||
// Bonuses per level
|
||||
miner.yieldPerHour += 1;
|
||||
miner.efficiency += 0.05;
|
||||
|
||||
this.game.showMessage(
|
||||
`${miner.name} leveled up! (Lv.${miner.level})`
|
||||
);
|
||||
|
||||
this.updateAutomationYield();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed zombie miner (restore loyalty)
|
||||
*/
|
||||
feedZombieMiner(minerId, foodType) {
|
||||
const miner = this.zombieMiners.find(m => m.id === minerId);
|
||||
|
||||
if (!miner) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Check if player has food
|
||||
if (!this.player.inventory.hasItem(foodType)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You don\'t have this food!'
|
||||
};
|
||||
}
|
||||
|
||||
// Remove food
|
||||
this.player.inventory.removeItem(foodType, 1);
|
||||
|
||||
// Loyalty bonus based on food quality
|
||||
const loyaltyGain = this.getFoodLoyaltyValue(foodType);
|
||||
miner.loyalty = Math.min(100, miner.loyalty + loyaltyGain);
|
||||
|
||||
this.game.showMessage(
|
||||
`${miner.name} ate ${foodType}. Loyalty +${loyaltyGain} (${miner.loyalty}/100)`
|
||||
);
|
||||
|
||||
// Update automation
|
||||
this.updateAutomationYield();
|
||||
|
||||
return { success: true, loyaltyGain: loyaltyGain };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get food loyalty value
|
||||
*/
|
||||
getFoodLoyaltyValue(foodType) {
|
||||
const foodValues = {
|
||||
'brain': 20, // Best food
|
||||
'meat': 15,
|
||||
'bread': 10,
|
||||
'vegetables': 5
|
||||
};
|
||||
|
||||
return foodValues[foodType] || 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade zombie equipment
|
||||
*/
|
||||
upgradeEquipment(equipmentType) {
|
||||
const costs = {
|
||||
pickaxe_tier: 3000,
|
||||
helmet_lamp: 2000,
|
||||
oxygen_tank: 2500,
|
||||
cart: 4000
|
||||
};
|
||||
|
||||
const cost = costs[equipmentType];
|
||||
|
||||
if (!cost) {
|
||||
return { success: false, message: 'Invalid equipment!' };
|
||||
}
|
||||
|
||||
// Check if already owned (except pickaxe tiers)
|
||||
if (equipmentType !== 'pickaxe_tier') {
|
||||
if (this.zombieEquipment[equipmentType]) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Already owned!'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Check pickaxe tier limit
|
||||
if (this.zombieEquipment.pickaxe_tier >= 5) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Pickaxe already max tier!'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check money
|
||||
if (this.player.money < cost) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Need ${cost}g!`
|
||||
};
|
||||
}
|
||||
|
||||
// Purchase
|
||||
this.player.money -= cost;
|
||||
|
||||
if (equipmentType === 'pickaxe_tier') {
|
||||
this.zombieEquipment.pickaxe_tier++;
|
||||
} else {
|
||||
this.zombieEquipment[equipmentType] = true;
|
||||
}
|
||||
|
||||
// Update automation
|
||||
this.updateAutomationYield();
|
||||
|
||||
this.game.showMessage(
|
||||
`Upgraded zombie equipment: ${equipmentType}! ${cost}g`
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hours since last collection
|
||||
*/
|
||||
getHoursSinceLastCollection() {
|
||||
if (!this.lastCollectionTime) {
|
||||
this.lastCollectionTime = this.game.time.currentTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const secondsElapsed = this.game.time.currentTime - this.lastCollectionTime;
|
||||
return secondsElapsed / 3600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get automation UI data
|
||||
*/
|
||||
getAutomationUIData() {
|
||||
return {
|
||||
zombieMiners: this.zombieMiners,
|
||||
maxZombieMiners: this.maxZombieMiners,
|
||||
totalYieldPerHour: this.totalYieldPerHour,
|
||||
automationActive: this.automationActive,
|
||||
equipment: this.zombieEquipment,
|
||||
canHireMore: this.zombieMiners.length < this.maxZombieMiners,
|
||||
hireCost: this.zombieMinerCost,
|
||||
hoursSinceCollection: this.getHoursSinceLastCollection()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (passive decay of loyalty)
|
||||
*/
|
||||
update(deltaTime) {
|
||||
// Loyalty slowly decays if zombies are working
|
||||
this.zombieMiners.forEach(miner => {
|
||||
if (miner.assignedMine && miner.assignedDepth > 0) {
|
||||
// Lose 1 loyalty per hour worked
|
||||
const loyaltyLoss = (deltaTime / 3600);
|
||||
miner.loyalty = Math.max(0, miner.loyalty - loyaltyLoss);
|
||||
}
|
||||
});
|
||||
|
||||
// Recalculate if needed
|
||||
if (this.automationActive) {
|
||||
this.updateAutomationYield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ZombieMinerAutomationSystem;
|
||||
Reference in New Issue
Block a user