💻 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:
2026-01-04 12:39:31 +01:00
parent d11b69cc23
commit 2478589c71
3 changed files with 1681 additions and 0 deletions

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

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

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