664 lines
19 KiB
JavaScript
664 lines
19 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
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
|
|
};
|
|
}
|
|
}
|
|
|
|
|