550 lines
15 KiB
JavaScript
550 lines
15 KiB
JavaScript
/**
|
|
* BAKERY SHOP SYSTEM - Food & Gift Purchasing
|
|
* Part of: New Town Buildings
|
|
* Created: January 4, 2026
|
|
*
|
|
* Features:
|
|
* - Food shop with fresh baked goods
|
|
* - Gift system (best gifts for NPCs)
|
|
* - Bulk purchase discounts
|
|
* - Time-of-day inventory changes
|
|
* - Special events (baking competition, birthday cakes)
|
|
*/
|
|
|
|
class BakeryShopSystem {
|
|
constructor(game) {
|
|
this.game = game;
|
|
this.player = game.player;
|
|
|
|
// Bakery status
|
|
this.isUnlocked = false;
|
|
this.isOpen = false;
|
|
this.baker = null;
|
|
|
|
// Shop inventory
|
|
this.inventory = this.initializeInventory();
|
|
|
|
// Special events
|
|
this.currentEvent = null;
|
|
this.lastBakingCompetition = null;
|
|
|
|
// Birthday cake orders
|
|
this.birthdayCakeOrders = [];
|
|
}
|
|
|
|
/**
|
|
* Initialize bakery inventory
|
|
*/
|
|
initializeInventory() {
|
|
return {
|
|
fresh_bread: {
|
|
id: 'fresh_bread',
|
|
name: 'Fresh Bread',
|
|
price: 50,
|
|
energyRestore: 20,
|
|
giftValue: 1, // +1 heart
|
|
stock: 20,
|
|
restockTime: 'morning', // Restocks at 6 AM
|
|
description: 'Warm, freshly baked bread.',
|
|
favoredBy: ['all'] // Everyone likes bread!
|
|
},
|
|
cake: {
|
|
id: 'cake',
|
|
name: 'Cake',
|
|
price: 200,
|
|
energyRestore: 50,
|
|
giftValue: 5, // +5 hearts
|
|
stock: 5,
|
|
restockTime: 'afternoon', // Restocks at 2 PM
|
|
description: 'A beautiful layered cake.',
|
|
favoredBy: ['ana', 'kai', 'gronk', 'children']
|
|
},
|
|
pie: {
|
|
id: 'pie',
|
|
name: 'Fruit Pie',
|
|
price: 150,
|
|
energyRestore: 40,
|
|
giftValue: 3, // +3 hearts
|
|
stock: 8,
|
|
restockTime: 'morning',
|
|
description: 'Sweet fruit pie with flaky crust.',
|
|
favoredBy: ['ana', 'elderly_npcs']
|
|
},
|
|
cookies: {
|
|
id: 'cookies',
|
|
name: 'Cookies',
|
|
price: 25,
|
|
energyRestore: 10,
|
|
giftValue: 1, // +1 heart
|
|
stock: 30,
|
|
restockTime: 'always', // Always available
|
|
description: 'Crunchy homemade cookies.',
|
|
favoredBy: ['children', 'kai']
|
|
},
|
|
croissants: {
|
|
id: 'croissants',
|
|
name: 'Croissants',
|
|
price: 75,
|
|
energyRestore: 25,
|
|
giftValue: 2, // +2 hearts
|
|
stock: 15,
|
|
restockTime: 'morning',
|
|
description: 'Buttery, flaky croissants. Fancy!',
|
|
favoredBy: ['ana', 'lawyer', 'wealthy_npcs']
|
|
},
|
|
|
|
// SPECIAL SEASONAL ITEMS
|
|
pumpkin_pie: {
|
|
id: 'pumpkin_pie',
|
|
name: 'Pumpkin Pie',
|
|
price: 180,
|
|
energyRestore: 45,
|
|
giftValue: 4,
|
|
stock: 6,
|
|
seasonal: 'autumn',
|
|
description: 'Spiced pumpkin pie for autumn.',
|
|
favoredBy: ['gronk', 'farmer_npcs']
|
|
},
|
|
gingerbread: {
|
|
id: 'gingerbread',
|
|
name: 'Gingerbread',
|
|
price: 100,
|
|
energyRestore: 30,
|
|
giftValue: 3,
|
|
stock: 12,
|
|
seasonal: 'winter',
|
|
description: 'Festive gingerbread cookies.',
|
|
favoredBy: ['children', 'kai', 'festive_npcs']
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unlock bakery (requires town restoration)
|
|
*/
|
|
unlockBakery() {
|
|
// Check requirements
|
|
if (!this.player.hasCompletedQuest('first_bread')) {
|
|
return {
|
|
success: false,
|
|
message: 'Complete "First Bread" quest first!'
|
|
};
|
|
}
|
|
|
|
if (!this.player.hasMaterials({ wood: 100, wheat: 50 })) {
|
|
return {
|
|
success: false,
|
|
message: 'Need 100 Wood + 50 Wheat to build bakery!'
|
|
};
|
|
}
|
|
|
|
if (this.player.money < 8000) {
|
|
return {
|
|
success: false,
|
|
message: 'Need 8,000g to build bakery!'
|
|
};
|
|
}
|
|
|
|
// Build bakery
|
|
this.player.removeMaterials({ wood: 100, wheat: 50 });
|
|
this.player.money -= 8000;
|
|
this.isUnlocked = true;
|
|
|
|
// Spawn baker NPC
|
|
this.baker = this.game.npcs.spawn('baker', {
|
|
name: 'Maria',
|
|
position: { x: 1200, y: 800 }, // Bakery location
|
|
dialogue: this.getBakerDialogue()
|
|
});
|
|
|
|
// Open immediately
|
|
this.isOpen = true;
|
|
|
|
this.game.showMessage('🥖 Bakery is now open!');
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Get baker NPC dialogue
|
|
*/
|
|
getBakerDialogue() {
|
|
return {
|
|
greeting: [
|
|
"Welcome to the bakery! Fresh bread daily!",
|
|
"Hello dear! What can I bake for you today?",
|
|
"Mmm, smell that fresh bread!"
|
|
],
|
|
buying: [
|
|
"Excellent choice!",
|
|
"Fresh from the oven!",
|
|
"Enjoy, dear!"
|
|
],
|
|
gift_suggestion: [
|
|
"Cakes make wonderful gifts!",
|
|
"Everyone loves fresh bread.",
|
|
"Looking for a special gift? Try the pie!"
|
|
],
|
|
competition: [
|
|
"Weekly baking competition this Sunday!",
|
|
"Show me your best recipe!",
|
|
"The winner gets a special prize!"
|
|
],
|
|
birthday: [
|
|
"Need a birthday cake? I can make one!",
|
|
"Tell me who it's for, I'll personalize it!",
|
|
"Birthday cakes are my specialty!"
|
|
]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Purchase item from bakery
|
|
*/
|
|
buyItem(itemId, quantity = 1) {
|
|
const item = this.inventory[itemId];
|
|
|
|
if (!item) {
|
|
return { success: false, message: 'Item not found!' };
|
|
}
|
|
|
|
// Check if bakery is open
|
|
if (!this.isOpen) {
|
|
return { success: false, message: 'Bakery is closed!' };
|
|
}
|
|
|
|
// Check seasonal availability
|
|
if (item.seasonal && !this.isCurrentSeason(item.seasonal)) {
|
|
return {
|
|
success: false,
|
|
message: `${item.name} is only available in ${item.seasonal}!`
|
|
};
|
|
}
|
|
|
|
// Check stock
|
|
if (item.stock < quantity) {
|
|
return {
|
|
success: false,
|
|
message: `Only ${item.stock} ${item.name} in stock!`
|
|
};
|
|
}
|
|
|
|
// Calculate price (with bulk discount)
|
|
const discount = this.getBulkDiscount(quantity);
|
|
const totalPrice = Math.floor(item.price * quantity * (1 - discount));
|
|
|
|
// Check money
|
|
if (this.player.money < totalPrice) {
|
|
return {
|
|
success: false,
|
|
message: `Not enough money! Need ${totalPrice}g`
|
|
};
|
|
}
|
|
|
|
// Purchase
|
|
this.player.money -= totalPrice;
|
|
item.stock -= quantity;
|
|
|
|
// Add to inventory
|
|
this.player.inventory.addItem(itemId, quantity);
|
|
|
|
// Show message
|
|
let message = `Purchased ${quantity}x ${item.name} for ${totalPrice}g!`;
|
|
if (discount > 0) {
|
|
message += ` (${Math.floor(discount * 100)}% bulk discount!)`;
|
|
}
|
|
|
|
this.game.showMessage(message);
|
|
|
|
return { success: true, totalPrice: totalPrice, discount: discount };
|
|
}
|
|
|
|
/**
|
|
* Calculate bulk discount
|
|
*/
|
|
getBulkDiscount(quantity) {
|
|
// 10+ items = 20% off
|
|
if (quantity >= 10) {
|
|
return 0.20;
|
|
}
|
|
// 5-9 items = 10% off
|
|
if (quantity >= 5) {
|
|
return 0.10;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gift baked good to NPC
|
|
*/
|
|
giftItem(itemId, npcId) {
|
|
const item = this.inventory[itemId];
|
|
const npc = this.game.npcs.get(npcId);
|
|
|
|
if (!item || !npc) {
|
|
return { success: false };
|
|
}
|
|
|
|
// Check if player has item
|
|
if (!this.player.inventory.hasItem(itemId)) {
|
|
return {
|
|
success: false,
|
|
message: 'You don\'t have this item!'
|
|
};
|
|
}
|
|
|
|
// Calculate gift value (bonus if NPC favors this item)
|
|
let giftValue = item.giftValue;
|
|
if (item.favoredBy.includes(npc.id) || item.favoredBy.includes('all')) {
|
|
giftValue *= 2; // Double hearts if favored!
|
|
}
|
|
|
|
// Remove from inventory
|
|
this.player.inventory.removeItem(itemId, 1);
|
|
|
|
// Add relationship points
|
|
npc.addRelationshipPoints(giftValue);
|
|
|
|
// NPC reaction
|
|
const reaction = this.getNPCGiftReaction(npc, item, giftValue);
|
|
|
|
this.game.showMessage(
|
|
`${npc.name}: "${reaction}" +${giftValue} ❤️`
|
|
);
|
|
|
|
return { success: true, heartsAdded: giftValue, reaction: reaction };
|
|
}
|
|
|
|
/**
|
|
* Get NPC reaction to gift
|
|
*/
|
|
getNPCGiftReaction(npc, item, heartsAdded) {
|
|
if (heartsAdded >= 5) {
|
|
return "Oh wow! This is amazing! Thank you so much!";
|
|
}
|
|
if (heartsAdded >= 3) {
|
|
return "This is wonderful! You're so thoughtful!";
|
|
}
|
|
if (heartsAdded >= 1) {
|
|
return "Thank you! I love fresh baked goods!";
|
|
}
|
|
return "Thanks.";
|
|
}
|
|
|
|
/**
|
|
* Order birthday cake
|
|
*/
|
|
orderBirthdayCake(npcId) {
|
|
const npc = this.game.npcs.get(npcId);
|
|
|
|
if (!npc) {
|
|
return { success: false, message: 'NPC not found!' };
|
|
}
|
|
|
|
// Check cost
|
|
const cakePrice = 500; // Special birthday cake
|
|
if (this.player.money < cakePrice) {
|
|
return {
|
|
success: false,
|
|
message: `Birthday cakes cost ${cakePrice}g!`
|
|
};
|
|
}
|
|
|
|
// Order cake
|
|
this.player.money -= cakePrice;
|
|
|
|
const order = {
|
|
npc: npc,
|
|
orderDate: this.game.time.currentDate,
|
|
deliveryDate: npc.birthday,
|
|
delivered: false
|
|
};
|
|
|
|
this.birthdayCakeOrders.push(order);
|
|
|
|
this.game.showMessage(
|
|
`Ordered birthday cake for ${npc.name}! Will be delivered on their birthday.`
|
|
);
|
|
|
|
return { success: true, order: order };
|
|
}
|
|
|
|
/**
|
|
* Start weekly baking competition
|
|
*/
|
|
startBakingCompetition() {
|
|
this.currentEvent = {
|
|
type: 'baking_competition',
|
|
startDate: this.game.time.currentDate,
|
|
endDate: this.game.time.currentDate + 7, // Lasts 1 week
|
|
participants: [],
|
|
playerScore: 0,
|
|
prize: {
|
|
money: 1000,
|
|
item: 'golden_whisk',
|
|
hearts: 5 // +5 hearts with baker
|
|
}
|
|
};
|
|
|
|
this.game.showMessage(
|
|
'🥖 Weekly Baking Competition started! Bring your best recipe!'
|
|
);
|
|
|
|
return this.currentEvent;
|
|
}
|
|
|
|
/**
|
|
* Submit recipe to competition
|
|
*/
|
|
submitCompetitionRecipe(recipeId) {
|
|
if (!this.currentEvent || this.currentEvent.type !== 'baking_competition') {
|
|
return {
|
|
success: false,
|
|
message: 'No baking competition active!'
|
|
};
|
|
}
|
|
|
|
// Check if player has baked this recipe
|
|
const recipe = this.game.crafting.recipes[recipeId];
|
|
if (!recipe || !this.player.inventory.hasItem(recipeId)) {
|
|
return {
|
|
success: false,
|
|
message: 'You need to bake this recipe first!'
|
|
};
|
|
}
|
|
|
|
// Calculate score based on recipe complexity
|
|
const score = this.calculateRecipeScore(recipe);
|
|
this.currentEvent.playerScore = Math.max(this.currentEvent.playerScore, score);
|
|
|
|
this.game.showMessage(
|
|
`Recipe submitted! Score: ${score}/100`
|
|
);
|
|
|
|
return { success: true, score: score };
|
|
}
|
|
|
|
/**
|
|
* Calculate recipe score for competition
|
|
*/
|
|
calculateRecipeScore(recipe) {
|
|
let score = 0;
|
|
|
|
// Base score from ingredients count
|
|
score += Object.keys(recipe.ingredients).length * 10;
|
|
|
|
// Bonus for craft time (complexity)
|
|
score += Math.min(recipe.craftTime, 50);
|
|
|
|
// Bonus for energy restore (if food)
|
|
if (recipe.energy_restore) {
|
|
score += recipe.energy_restore / 2;
|
|
}
|
|
|
|
// Random factor (luck)
|
|
score += Math.random() * 20;
|
|
|
|
return Math.min(Math.floor(score), 100);
|
|
}
|
|
|
|
/**
|
|
* Check seasonal availability
|
|
*/
|
|
isCurrentSeason(season) {
|
|
const currentSeason = this.game.time.getSeason();
|
|
return currentSeason === season;
|
|
}
|
|
|
|
/**
|
|
* Restock inventory (called at specific times)
|
|
*/
|
|
restockInventory() {
|
|
const hour = this.game.time.getHour();
|
|
|
|
Object.values(this.inventory).forEach(item => {
|
|
// Morning restock (6 AM)
|
|
if (hour === 6 && item.restockTime === 'morning') {
|
|
item.stock = this.getMaxStock(item.id);
|
|
}
|
|
|
|
// Afternoon restock (2 PM)
|
|
if (hour === 14 && item.restockTime === 'afternoon') {
|
|
item.stock = this.getMaxStock(item.id);
|
|
}
|
|
|
|
// Always available items restock every hour
|
|
if (item.restockTime === 'always') {
|
|
item.stock = this.getMaxStock(item.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get max stock for item
|
|
*/
|
|
getMaxStock(itemId) {
|
|
const defaults = {
|
|
fresh_bread: 20,
|
|
cake: 5,
|
|
pie: 8,
|
|
cookies: 30,
|
|
croissants: 15,
|
|
pumpkin_pie: 6,
|
|
gingerbread: 12
|
|
};
|
|
|
|
return defaults[itemId] || 10;
|
|
}
|
|
|
|
/**
|
|
* Update shop (called every hour)
|
|
*/
|
|
update() {
|
|
const hour = this.game.time.getHour();
|
|
|
|
// Open/close shop
|
|
this.isOpen = (hour >= 6 && hour <= 20); // Open 6 AM - 8 PM
|
|
|
|
// Restock
|
|
this.restockInventory();
|
|
|
|
// Check birthday cake deliveries
|
|
this.checkBirthdayCakeDeliveries();
|
|
}
|
|
|
|
/**
|
|
* Check and deliver birthday cakes
|
|
*/
|
|
checkBirthdayCakeDeliveries() {
|
|
const today = this.game.time.currentDate;
|
|
|
|
this.birthdayCakeOrders.forEach(order => {
|
|
if (!order.delivered && order.deliveryDate === today) {
|
|
// Deliver cake
|
|
this.player.inventory.addItem('birthday_cake_' + order.npc.id, 1);
|
|
order.delivered = true;
|
|
|
|
this.game.showMessage(
|
|
`🎂 Birthday cake for ${order.npc.name} is ready!`
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get shop UI data
|
|
*/
|
|
getShopUIData() {
|
|
return {
|
|
isUnlocked: this.isUnlocked,
|
|
isOpen: this.isOpen,
|
|
inventory: this.inventory,
|
|
currentEvent: this.currentEvent,
|
|
baker: this.baker,
|
|
openingHours: '6 AM - 8 PM'
|
|
};
|
|
}
|
|
}
|
|
|
|
|