💻 GAME SYSTEMS CODE - Sleep, Crafting, Bakery
Implemented 3 major game systems in JavaScript
This commit is contained in:
549
src/systems/BakeryShopSystem.js
Normal file
549
src/systems/BakeryShopSystem.js
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export 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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BakeryShopSystem;
|
||||||
574
src/systems/CraftingTablesSystem.js
Normal file
574
src/systems/CraftingTablesSystem.js
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
/**
|
||||||
|
* CRAFTING TABLES SYSTEM - Recipe Management & Item Creation
|
||||||
|
* Part of: Home Upgrade & Customization System
|
||||||
|
* Created: January 4, 2026
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Small crafting table (50 basic recipes)
|
||||||
|
* - Large planning table (100+ advanced recipes)
|
||||||
|
* - Recipe unlocking system
|
||||||
|
* - Ingredient checking
|
||||||
|
* - Batch crafting
|
||||||
|
* - Gronk's expedition integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class CraftingTablesSystem {
|
||||||
|
constructor(game) {
|
||||||
|
this.game = game;
|
||||||
|
this.player = game.player;
|
||||||
|
|
||||||
|
// Table types
|
||||||
|
this.tables = {
|
||||||
|
SMALL: {
|
||||||
|
id: 'small_table',
|
||||||
|
name: 'Small Crafting Table',
|
||||||
|
cost: 0,
|
||||||
|
unlocked: true,
|
||||||
|
maxRecipes: 50,
|
||||||
|
craftingSpeed: 1.0,
|
||||||
|
sprite: 'interior_table_small.png',
|
||||||
|
description: 'A basic workbench for simple crafting.',
|
||||||
|
categories: ['tools_wooden', 'repairs', 'food_basic', 'potions_beginner']
|
||||||
|
},
|
||||||
|
LARGE: {
|
||||||
|
id: 'large_table',
|
||||||
|
name: 'Large Planning Table',
|
||||||
|
cost: 5000,
|
||||||
|
requirements: {
|
||||||
|
metGronk: true,
|
||||||
|
questCompleted: 'builders_vision'
|
||||||
|
},
|
||||||
|
unlocked: false,
|
||||||
|
maxRecipes: 100,
|
||||||
|
craftingSpeed: 1.5,
|
||||||
|
sprite: 'interior_table_large.png',
|
||||||
|
description: 'An advanced table for complex projects and expeditions.',
|
||||||
|
categories: ['tools_wooden', 'repairs', 'food_basic', 'potions_beginner',
|
||||||
|
'expedition_planning', 'mechanisms', 'weapons_advanced', 'gronk_special']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Crafting recipes database
|
||||||
|
this.recipes = this.initializeRecipes();
|
||||||
|
|
||||||
|
// Player's current table
|
||||||
|
this.currentTable = { ...this.tables.SMALL };
|
||||||
|
|
||||||
|
// Unlocked recipes
|
||||||
|
this.unlockedRecipes = [];
|
||||||
|
|
||||||
|
// Crafting state
|
||||||
|
this.isCrafting = false;
|
||||||
|
this.craftingQueue = [];
|
||||||
|
this.currentCraft = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all crafting recipes
|
||||||
|
*/
|
||||||
|
initializeRecipes() {
|
||||||
|
return {
|
||||||
|
// TOOLS - WOODEN TIER
|
||||||
|
wooden_hoe: {
|
||||||
|
id: 'wooden_hoe',
|
||||||
|
name: 'Wooden Hoe',
|
||||||
|
category: 'tools_wooden',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
wood: 10,
|
||||||
|
rope: 2
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'wooden_hoe',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 5, // seconds
|
||||||
|
unlocked: true,
|
||||||
|
description: 'Basic farming tool for tilling soil.'
|
||||||
|
},
|
||||||
|
wooden_pickaxe: {
|
||||||
|
id: 'wooden_pickaxe',
|
||||||
|
name: 'Wooden Pickaxe',
|
||||||
|
category: 'tools_wooden',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
wood: 15,
|
||||||
|
stone: 5
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'wooden_pickaxe',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 8,
|
||||||
|
unlocked: true,
|
||||||
|
description: 'Basic mining tool for breaking rocks.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// REPAIRS
|
||||||
|
locket_cleaning: {
|
||||||
|
id: 'locket_cleaning',
|
||||||
|
name: 'Clean Ana\'s Locket',
|
||||||
|
category: 'repairs',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
cloth: 1,
|
||||||
|
water: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'locket_clean',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 3,
|
||||||
|
unlocked: false,
|
||||||
|
questRequired: 'memory_lane',
|
||||||
|
description: 'Restore Ana\'s precious locket to its former shine.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// FOOD - BASIC
|
||||||
|
sandwich: {
|
||||||
|
id: 'sandwich',
|
||||||
|
name: 'Sandwich',
|
||||||
|
category: 'food_basic',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
bread: 2,
|
||||||
|
cheese: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'sandwich',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 2,
|
||||||
|
energy_restore: 20,
|
||||||
|
unlocked: true,
|
||||||
|
description: 'A simple but filling meal.'
|
||||||
|
},
|
||||||
|
salad: {
|
||||||
|
id: 'salad',
|
||||||
|
name: 'Garden Salad',
|
||||||
|
category: 'food_basic',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
lettuce: 3,
|
||||||
|
tomato: 2,
|
||||||
|
cucumber: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'salad',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 3,
|
||||||
|
energy_restore: 25,
|
||||||
|
unlocked: true,
|
||||||
|
description: 'Fresh vegetables in a healthy mix.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// POTIONS - BEGINNER
|
||||||
|
healing_potion_basic: {
|
||||||
|
id: 'healing_potion_basic',
|
||||||
|
name: 'Basic Healing Potion',
|
||||||
|
category: 'potions_beginner',
|
||||||
|
table: 'SMALL',
|
||||||
|
ingredients: {
|
||||||
|
red_herb: 2,
|
||||||
|
water: 1,
|
||||||
|
glass_bottle: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'healing_potion_basic',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 10,
|
||||||
|
health_restore: 50,
|
||||||
|
unlocked: false,
|
||||||
|
skillRequired: { alchemy: 1 },
|
||||||
|
description: 'Restores 50 HP.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// EXPEDITION PLANNING (Large Table Only)
|
||||||
|
expedition_map: {
|
||||||
|
id: 'expedition_map',
|
||||||
|
name: 'Expedition Map',
|
||||||
|
category: 'expedition_planning',
|
||||||
|
table: 'LARGE',
|
||||||
|
ingredients: {
|
||||||
|
paper: 5,
|
||||||
|
ink: 2,
|
||||||
|
compass: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'expedition_map',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 20,
|
||||||
|
unlocked: false,
|
||||||
|
npcRequired: 'gronk',
|
||||||
|
description: 'Required for planning expeditions with Gronk.'
|
||||||
|
},
|
||||||
|
expedition_supplies: {
|
||||||
|
id: 'expedition_supplies',
|
||||||
|
name: 'Expedition Supplies',
|
||||||
|
category: 'expedition_planning',
|
||||||
|
table: 'LARGE',
|
||||||
|
ingredients: {
|
||||||
|
rope: 10,
|
||||||
|
torch: 5,
|
||||||
|
food_rations: 20,
|
||||||
|
water_bottle: 10
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'expedition_supplies',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 30,
|
||||||
|
unlocked: false,
|
||||||
|
npcRequired: 'gronk',
|
||||||
|
description: 'Everything needed for a successful expedition.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// COMPLEX MECHANISMS (Large Table Only)
|
||||||
|
automated_sprinkler: {
|
||||||
|
id: 'automated_sprinkler',
|
||||||
|
name: 'Automated Sprinkler',
|
||||||
|
category: 'mechanisms',
|
||||||
|
table: 'LARGE',
|
||||||
|
ingredients: {
|
||||||
|
iron_pipe: 10,
|
||||||
|
gears: 5,
|
||||||
|
water_pump: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'automated_sprinkler',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 60,
|
||||||
|
unlocked: false,
|
||||||
|
skillRequired: { engineering: 3 },
|
||||||
|
description: 'Waters crops automatically in a 3x3 area.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// ADVANCED WEAPONS (Large Table Only)
|
||||||
|
steel_sword: {
|
||||||
|
id: 'steel_sword',
|
||||||
|
name: 'Steel Sword',
|
||||||
|
category: 'weapons_advanced',
|
||||||
|
table: 'LARGE',
|
||||||
|
ingredients: {
|
||||||
|
steel_ingot: 5,
|
||||||
|
leather: 2,
|
||||||
|
ruby: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'steel_sword',
|
||||||
|
quantity: 1
|
||||||
|
},
|
||||||
|
craftTime: 45,
|
||||||
|
damage: 50,
|
||||||
|
unlocked: false,
|
||||||
|
skillRequired: { combat: 5 },
|
||||||
|
description: 'A powerful blade forged from steel.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// GRONK SPECIAL (Large Table Only)
|
||||||
|
gronk_explosive: {
|
||||||
|
id: 'gronk_explosive',
|
||||||
|
name: 'Gronk\'s Explosive',
|
||||||
|
category: 'gronk_special',
|
||||||
|
table: 'LARGE',
|
||||||
|
ingredients: {
|
||||||
|
gunpowder: 5,
|
||||||
|
wire: 3,
|
||||||
|
metal_casing: 1,
|
||||||
|
gronks_secret_ingredient: 1
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
item: 'gronk_explosive',
|
||||||
|
quantity: 3
|
||||||
|
},
|
||||||
|
craftTime: 90,
|
||||||
|
unlocked: false,
|
||||||
|
npcRequired: 'gronk',
|
||||||
|
relationshipRequired: { gronk: 7 },
|
||||||
|
description: 'BOOM! Gronk\'s special explosive for clearing obstacles.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purchase Large Planning Table
|
||||||
|
*/
|
||||||
|
purchaseLargeTable() {
|
||||||
|
const largeTable = this.tables.LARGE;
|
||||||
|
|
||||||
|
// Check if already purchased
|
||||||
|
if (largeTable.unlocked) {
|
||||||
|
return { success: false, message: 'You already own the Large Table!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
if (!this.player.hasMetNPC('gronk')) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'You need to meet Gronk first!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.player.hasCompletedQuest('builders_vision')) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Complete "Builder\'s Vision" quest first!'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cost
|
||||||
|
if (this.player.money < largeTable.cost) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Not enough money! Need ${largeTable.cost}g`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase
|
||||||
|
this.player.money -= largeTable.cost;
|
||||||
|
largeTable.unlocked = true;
|
||||||
|
this.currentTable = { ...largeTable };
|
||||||
|
|
||||||
|
// Unlock advanced recipes
|
||||||
|
this.unlockAdvancedRecipes();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Purchased ${largeTable.name} for ${largeTable.cost}g!`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock advanced recipes when Large Table is purchased
|
||||||
|
*/
|
||||||
|
unlockAdvancedRecipes() {
|
||||||
|
Object.values(this.recipes).forEach(recipe => {
|
||||||
|
if (recipe.table === 'LARGE' && !recipe.npcRequired && !recipe.skillRequired) {
|
||||||
|
recipe.unlocked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if player can craft a recipe
|
||||||
|
*/
|
||||||
|
canCraft(recipeId) {
|
||||||
|
const recipe = this.recipes[recipeId];
|
||||||
|
|
||||||
|
if (!recipe) {
|
||||||
|
return { canCraft: false, reason: 'Recipe not found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if recipe is unlocked
|
||||||
|
if (!recipe.unlocked) {
|
||||||
|
return { canCraft: false, reason: 'Recipe locked' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check table requirement
|
||||||
|
if (recipe.table === 'LARGE' && !this.tables.LARGE.unlocked) {
|
||||||
|
return { canCraft: false, reason: 'Requires Large Planning Table' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check NPC requirement
|
||||||
|
if (recipe.npcRequired && !this.player.hasMetNPC(recipe.npcRequired)) {
|
||||||
|
return { canCraft: false, reason: `Requires meeting ${recipe.npcRequired}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check relationship requirement
|
||||||
|
if (recipe.relationshipRequired) {
|
||||||
|
for (const [npc, level] of Object.entries(recipe.relationshipRequired)) {
|
||||||
|
if (this.player.getRelationshipLevel(npc) < level) {
|
||||||
|
return {
|
||||||
|
canCraft: false,
|
||||||
|
reason: `Requires ${level}+ hearts with ${npc}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check skill requirement
|
||||||
|
if (recipe.skillRequired) {
|
||||||
|
for (const [skill, level] of Object.entries(recipe.skillRequired)) {
|
||||||
|
if (this.player.getSkillLevel(skill) < level) {
|
||||||
|
return {
|
||||||
|
canCraft: false,
|
||||||
|
reason: `Requires ${skill} level ${level}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ingredients
|
||||||
|
const missingIngredients = [];
|
||||||
|
for (const [ingredient, amount] of Object.entries(recipe.ingredients)) {
|
||||||
|
const playerAmount = this.player.inventory.getItemCount(ingredient);
|
||||||
|
if (playerAmount < amount) {
|
||||||
|
missingIngredients.push({
|
||||||
|
item: ingredient,
|
||||||
|
have: playerAmount,
|
||||||
|
need: amount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingIngredients.length > 0) {
|
||||||
|
return {
|
||||||
|
canCraft: false,
|
||||||
|
reason: 'Missing ingredients',
|
||||||
|
missingIngredients: missingIngredients
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canCraft: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start crafting a recipe
|
||||||
|
*/
|
||||||
|
craft(recipeId, quantity = 1) {
|
||||||
|
const canCraftCheck = this.canCraft(recipeId);
|
||||||
|
|
||||||
|
if (!canCraftCheck.canCraft) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: canCraftCheck.reason,
|
||||||
|
missingIngredients: canCraftCheck.missingIngredients
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipe = this.recipes[recipeId];
|
||||||
|
|
||||||
|
// Remove ingredients
|
||||||
|
for (const [ingredient, amount] of Object.entries(recipe.ingredients)) {
|
||||||
|
this.player.inventory.removeItem(ingredient, amount * quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate craft time (with table speed bonus)
|
||||||
|
const tableSpeed = this.currentTable.craftingSpeed;
|
||||||
|
const totalTime = (recipe.craftTime / tableSpeed) * quantity;
|
||||||
|
|
||||||
|
// Add to crafting queue
|
||||||
|
const craftJob = {
|
||||||
|
recipe: recipe,
|
||||||
|
quantity: quantity,
|
||||||
|
timeRemaining: totalTime,
|
||||||
|
totalTime: totalTime
|
||||||
|
};
|
||||||
|
|
||||||
|
this.craftingQueue.push(craftJob);
|
||||||
|
|
||||||
|
// Start crafting if not already crafting
|
||||||
|
if (!this.isCrafting) {
|
||||||
|
this.startNextCraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Crafting ${quantity}x ${recipe.name}... (${Math.floor(totalTime)}s)`,
|
||||||
|
craftJob: craftJob
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start next craft in queue
|
||||||
|
*/
|
||||||
|
startNextCraft() {
|
||||||
|
if (this.craftingQueue.length === 0) {
|
||||||
|
this.isCrafting = false;
|
||||||
|
this.currentCraft = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isCrafting = true;
|
||||||
|
this.currentCraft = this.craftingQueue[0];
|
||||||
|
|
||||||
|
// Emit crafting started event
|
||||||
|
this.game.emit('craftingStarted', {
|
||||||
|
recipe: this.currentCraft.recipe,
|
||||||
|
quantity: this.currentCraft.quantity,
|
||||||
|
time: this.currentCraft.totalTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update crafting progress
|
||||||
|
*/
|
||||||
|
update(deltaTime) {
|
||||||
|
if (!this.isCrafting || !this.currentCraft) return;
|
||||||
|
|
||||||
|
// Decrease time remaining
|
||||||
|
this.currentCraft.timeRemaining -= deltaTime;
|
||||||
|
|
||||||
|
// Check if completed
|
||||||
|
if (this.currentCraft.timeRemaining <= 0) {
|
||||||
|
this.completeCraft();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete current craft
|
||||||
|
*/
|
||||||
|
completeCraft() {
|
||||||
|
if (!this.currentCraft) return;
|
||||||
|
|
||||||
|
const recipe = this.currentCraft.recipe;
|
||||||
|
const quantity = this.currentCraft.quantity;
|
||||||
|
|
||||||
|
// Add crafted item to inventory
|
||||||
|
this.player.inventory.addItem(
|
||||||
|
recipe.output.item,
|
||||||
|
recipe.output.quantity * quantity
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show completion message
|
||||||
|
this.game.showMessage(
|
||||||
|
`✅ Crafted ${quantity}x ${recipe.name}!`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Emit crafting completed event
|
||||||
|
this.game.emit('craftingCompleted', {
|
||||||
|
recipe: recipe,
|
||||||
|
quantity: quantity
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove from queue
|
||||||
|
this.craftingQueue.shift();
|
||||||
|
|
||||||
|
// Start next craft
|
||||||
|
this.startNextCraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available recipes for current table
|
||||||
|
*/
|
||||||
|
getAvailableRecipes() {
|
||||||
|
return Object.values(this.recipes).filter(recipe => {
|
||||||
|
// Filter by table type
|
||||||
|
if (recipe.table === 'LARGE' && !this.tables.LARGE.unlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipe.unlocked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get crafting UI data
|
||||||
|
*/
|
||||||
|
getCraftingUIData() {
|
||||||
|
return {
|
||||||
|
currentTable: this.currentTable,
|
||||||
|
isCrafting: this.isCrafting,
|
||||||
|
currentCraft: this.currentCraft,
|
||||||
|
queue: this.craftingQueue,
|
||||||
|
availableRecipes: this.getAvailableRecipes(),
|
||||||
|
largeTableUnlocked: this.tables.LARGE.unlocked
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CraftingTablesSystem;
|
||||||
384
src/systems/SleepSystem.js
Normal file
384
src/systems/SleepSystem.js
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
/**
|
||||||
|
* SLEEP SYSTEM - Energy Regeneration & Bed Upgrades
|
||||||
|
* Part of: Home Upgrade & Customization System
|
||||||
|
* Created: January 4, 2026
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - 3-tier bed system (sleeping bag → wooden → king-size)
|
||||||
|
* - Energy regeneration based on bed quality
|
||||||
|
* - Time skip mechanics (8 hours sleep)
|
||||||
|
* - Partner bonuses for married players
|
||||||
|
* - Dream sequences and nightmares
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class SleepSystem {
|
||||||
|
constructor(game) {
|
||||||
|
this.game = game;
|
||||||
|
this.player = game.player;
|
||||||
|
this.isSleeping = false;
|
||||||
|
this.currentBed = null;
|
||||||
|
|
||||||
|
// Bed types configuration
|
||||||
|
this.bedTypes = {
|
||||||
|
SLEEPING_BAG: {
|
||||||
|
id: 'sleeping_bag',
|
||||||
|
name: 'Sleeping Bag',
|
||||||
|
cost: 0,
|
||||||
|
unlocked: true,
|
||||||
|
energyRegen: 50, // Restores 50% energy
|
||||||
|
sleepDuration: 8, // hours
|
||||||
|
dreamChance: 0, // No dreams
|
||||||
|
nightmareChance: 0.1, // 10% nightmare chance
|
||||||
|
partnerBonus: false,
|
||||||
|
sprite: 'interior_bed_sleepingbag.png',
|
||||||
|
description: 'A basic sleeping bag. Uncomfortable but functional.'
|
||||||
|
},
|
||||||
|
WOODEN_BED: {
|
||||||
|
id: 'wooden_bed',
|
||||||
|
name: 'Wooden Bed',
|
||||||
|
cost: 2000,
|
||||||
|
requirements: {
|
||||||
|
farmhouseLevel: 1
|
||||||
|
},
|
||||||
|
unlocked: false,
|
||||||
|
energyRegen: 90, // Restores 90% energy
|
||||||
|
sleepDuration: 8,
|
||||||
|
dreamChance: 0.2, // 20% dream chance
|
||||||
|
nightmareChance: 0.05, // 5% nightmare chance
|
||||||
|
partnerBonus: false,
|
||||||
|
sprite: 'interior_bed_wooden.png',
|
||||||
|
description: 'A comfortable wooden bed. Much better than the floor!'
|
||||||
|
},
|
||||||
|
KING_SIZE_BED: {
|
||||||
|
id: 'king_size',
|
||||||
|
name: 'King-size Bed',
|
||||||
|
cost: 10000,
|
||||||
|
requirements: {
|
||||||
|
farmhouseLevel: 2,
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
unlocked: false,
|
||||||
|
energyRegen: 100, // Restores 100% energy
|
||||||
|
sleepDuration: 8,
|
||||||
|
dreamChance: 0.3, // 30% dream chance
|
||||||
|
nightmareChance: 0, // No nightmares!
|
||||||
|
partnerBonus: true, // +50 relationship points
|
||||||
|
spousePresent: false,
|
||||||
|
sprite: 'interior_bed_kingsize.png',
|
||||||
|
description: 'A luxurious king-size bed. Perfect for two!'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Current player's bed (starts with sleeping bag)
|
||||||
|
this.playerBed = { ...this.bedTypes.SLEEPING_BAG };
|
||||||
|
|
||||||
|
// Sleep state
|
||||||
|
this.sleepStartTime = null;
|
||||||
|
this.sleepEndTime = null;
|
||||||
|
this.dreamSequence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if player can use a specific bed type
|
||||||
|
*/
|
||||||
|
canUseBed(bedType) {
|
||||||
|
const bed = this.bedTypes[bedType];
|
||||||
|
|
||||||
|
// Check if unlocked
|
||||||
|
if (!bed.unlocked && bedType !== 'SLEEPING_BAG') {
|
||||||
|
return { canUse: false, reason: 'Bed not purchased yet' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
if (bed.requirements) {
|
||||||
|
if (bed.requirements.farmhouseLevel &&
|
||||||
|
this.player.farmhouseLevel < bed.requirements.farmhouseLevel) {
|
||||||
|
return {
|
||||||
|
canUse: false,
|
||||||
|
reason: `Requires Farmhouse Level ${bed.requirements.farmhouseLevel}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bed.requirements.married && !this.player.isMarried) {
|
||||||
|
return {
|
||||||
|
canUse: false,
|
||||||
|
reason: 'Requires marriage'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canUse: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purchase a bed upgrade
|
||||||
|
*/
|
||||||
|
purchaseBed(bedType) {
|
||||||
|
const bed = this.bedTypes[bedType];
|
||||||
|
|
||||||
|
// Check if can purchase
|
||||||
|
if (bed.unlocked) {
|
||||||
|
return { success: false, message: 'You already own this bed!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cost
|
||||||
|
if (this.player.money < bed.cost) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Not enough money! Need ${bed.cost}g`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
const canUse = this.canUseBed(bedType);
|
||||||
|
if (!canUse.canUse) {
|
||||||
|
return { success: false, message: canUse.reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase bed
|
||||||
|
this.player.money -= bed.cost;
|
||||||
|
bed.unlocked = true;
|
||||||
|
this.playerBed = { ...bed };
|
||||||
|
|
||||||
|
// Trigger furniture placement
|
||||||
|
this.game.emit('bedPurchased', {
|
||||||
|
bedType: bedType,
|
||||||
|
bed: bed
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Purchased ${bed.name} for ${bed.cost}g!`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate sleep sequence
|
||||||
|
*/
|
||||||
|
sleep() {
|
||||||
|
// Check if already sleeping
|
||||||
|
if (this.isSleeping) {
|
||||||
|
return { success: false, message: 'Already sleeping!' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if tired enough (energy < 50%)
|
||||||
|
if (this.player.energy > this.player.maxEnergy * 0.5) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "You're not tired enough to sleep yet."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start sleep
|
||||||
|
this.isSleeping = true;
|
||||||
|
this.sleepStartTime = this.game.time.currentTime;
|
||||||
|
this.sleepEndTime = this.game.time.currentTime + (this.playerBed.sleepDuration * 3600); // Convert to seconds
|
||||||
|
|
||||||
|
// Roll for dream/nightmare
|
||||||
|
this.dreamSequence = this.rollForDream();
|
||||||
|
|
||||||
|
// Trigger sleep animation
|
||||||
|
this.game.emit('sleepStarted', {
|
||||||
|
bed: this.playerBed,
|
||||||
|
duration: this.playerBed.sleepDuration,
|
||||||
|
dreamSequence: this.dreamSequence
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fast-forward time
|
||||||
|
this.game.time.skipTime(this.playerBed.sleepDuration);
|
||||||
|
|
||||||
|
// Wake up after duration
|
||||||
|
setTimeout(() => {
|
||||||
|
this.wakeUp();
|
||||||
|
}, 100); // Instant wake-up after time skip
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll for dream or nightmare
|
||||||
|
*/
|
||||||
|
rollForDream() {
|
||||||
|
const rand = Math.random();
|
||||||
|
|
||||||
|
// Check nightmare first
|
||||||
|
if (rand < this.playerBed.nightmareChance) {
|
||||||
|
return {
|
||||||
|
type: 'nightmare',
|
||||||
|
sequence: this.getRandomNightmare()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check dream
|
||||||
|
if (rand < this.playerBed.dreamChance + this.playerBed.nightmareChance) {
|
||||||
|
return {
|
||||||
|
type: 'dream',
|
||||||
|
sequence: this.getRandomDream()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // No dream
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get random dream sequence
|
||||||
|
*/
|
||||||
|
getRandomDream() {
|
||||||
|
const dreams = [
|
||||||
|
{
|
||||||
|
id: 'memory_ana',
|
||||||
|
title: 'Memory of Ana',
|
||||||
|
description: 'You dream of Ana, smiling in the sunlight...',
|
||||||
|
effect: { mood: +10 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'farming_success',
|
||||||
|
title: 'Bountiful Harvest',
|
||||||
|
description: 'You dream of your crops growing tall and strong.',
|
||||||
|
effect: { farming_xp: 50 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zombie_peace',
|
||||||
|
title: 'Peaceful Workers',
|
||||||
|
description: 'Your zombie workers are happy and productive.',
|
||||||
|
effect: { loyalty: +5 }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return dreams[Math.floor(Math.random() * dreams.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get random nightmare sequence
|
||||||
|
*/
|
||||||
|
getRandomNightmare() {
|
||||||
|
const nightmares = [
|
||||||
|
{
|
||||||
|
id: 'ana_loss',
|
||||||
|
title: 'The Loss',
|
||||||
|
description: 'Fragments of that terrible day flash before you...',
|
||||||
|
effect: { mood: -20 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zombie_attack',
|
||||||
|
title: 'Undead Uprising',
|
||||||
|
description: 'Your workers turn hostile, advancing slowly...',
|
||||||
|
effect: { energy: -10 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'crop_failure',
|
||||||
|
title: 'Withered Fields',
|
||||||
|
description: 'Your entire farm crumbles to dust.',
|
||||||
|
effect: { mood: -10 }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return nightmares[Math.floor(Math.random() * nightmares.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wake up from sleep
|
||||||
|
*/
|
||||||
|
wakeUp() {
|
||||||
|
if (!this.isSleeping) return;
|
||||||
|
|
||||||
|
// Calculate energy restoration
|
||||||
|
const energyRestored = (this.player.maxEnergy * this.playerBed.energyRegen) / 100;
|
||||||
|
this.player.energy = Math.min(
|
||||||
|
this.player.maxEnergy,
|
||||||
|
this.player.energy + energyRestored
|
||||||
|
);
|
||||||
|
|
||||||
|
// Partner bonus (if married & king-size bed)
|
||||||
|
if (this.playerBed.partnerBonus && this.player.isMarried) {
|
||||||
|
const spouse = this.game.npcs.getSpouse();
|
||||||
|
if (spouse) {
|
||||||
|
spouse.addRelationshipPoints(50);
|
||||||
|
this.game.showMessage(`${spouse.name} cuddles closer. +50 ❤️`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply dream/nightmare effects
|
||||||
|
if (this.dreamSequence) {
|
||||||
|
this.applyDreamEffects(this.dreamSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
this.isSleeping = false;
|
||||||
|
this.currentBed = null;
|
||||||
|
this.dreamSequence = null;
|
||||||
|
|
||||||
|
// Trigger wake-up event
|
||||||
|
this.game.emit('wakeUp', {
|
||||||
|
energyRestored: energyRestored,
|
||||||
|
newEnergy: this.player.energy,
|
||||||
|
time: this.game.time.currentTime
|
||||||
|
});
|
||||||
|
|
||||||
|
// New day announcements
|
||||||
|
this.game.showMessage(`Good morning! Energy restored: ${Math.floor(energyRestored)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply dream/nightmare effects
|
||||||
|
*/
|
||||||
|
applyDreamEffects(dreamSequence) {
|
||||||
|
const sequence = dreamSequence.sequence;
|
||||||
|
|
||||||
|
// Show dream/nightmare message
|
||||||
|
const typeIcon = dreamSequence.type === 'dream' ? '✨' : '💀';
|
||||||
|
this.game.showMessage(`${typeIcon} ${sequence.title}: ${sequence.description}`);
|
||||||
|
|
||||||
|
// Apply effects
|
||||||
|
if (sequence.effect.mood) {
|
||||||
|
this.player.mood += sequence.effect.mood;
|
||||||
|
}
|
||||||
|
if (sequence.effect.energy) {
|
||||||
|
this.player.energy += sequence.effect.energy;
|
||||||
|
}
|
||||||
|
if (sequence.effect.farming_xp) {
|
||||||
|
this.player.addXP('farming', sequence.effect.farming_xp);
|
||||||
|
}
|
||||||
|
if (sequence.effect.loyalty && this.game.zombieWorkers) {
|
||||||
|
this.game.zombieWorkers.addGlobalLoyalty(sequence.effect.loyalty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's a good time to sleep
|
||||||
|
*/
|
||||||
|
canSleepNow() {
|
||||||
|
const hour = this.game.time.getHour();
|
||||||
|
|
||||||
|
// Can sleep between 8 PM (20:00) and 2 AM (02:00)
|
||||||
|
if ((hour >= 20 && hour <= 23) || (hour >= 0 && hour <= 2)) {
|
||||||
|
return { canSleep: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canSleep: false,
|
||||||
|
message: "It's not nighttime yet. Sleep between 8 PM - 2 AM."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current bed info for UI
|
||||||
|
*/
|
||||||
|
getCurrentBedInfo() {
|
||||||
|
return {
|
||||||
|
...this.playerBed,
|
||||||
|
isSleeping: this.isSleeping,
|
||||||
|
canSleep: this.canSleepNow().canSleep
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update (called every frame)
|
||||||
|
*/
|
||||||
|
update(deltaTime) {
|
||||||
|
// Sleep system update logic (if needed)
|
||||||
|
// Currently handled by events and timers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SleepSystem;
|
||||||
Reference in New Issue
Block a user