feat(expansion): implement Phase 3 (Town Restoration) and Phase 4 (Cannabis Textiles)
- Added TownSquareScene and linked it with M key transition - Integrated TownRestorationSystem with material costs and inventory - Added locked shop items in NPCShopSystem until buildings are restored - Updated InteractionSystem to handle ruin restoration triggers - Expanded Cannabis farming to yield Hemp Fiber - Added Hemp Clothing crafting recipe and procedural icons - Refactored StatusEffectSystem and NPCShopSystem to global classes
This commit is contained in:
@@ -18,6 +18,13 @@ class FarmingSystem {
|
||||
daysPerStage: 2,
|
||||
sellPrice: 15,
|
||||
seedCost: 8
|
||||
},
|
||||
'cannabis': {
|
||||
name: 'Cannabis',
|
||||
growthStages: 4,
|
||||
daysPerStage: 2,
|
||||
sellPrice: 60,
|
||||
seedCost: 20
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -154,16 +161,18 @@ class FarmingSystem {
|
||||
// Determine texture based on stage
|
||||
let textureKey = `crop_${crop.type}_stage_${crop.stage}`;
|
||||
|
||||
// Fallback textures
|
||||
if (!this.scene.textures.exists(textureKey)) {
|
||||
textureKey = 'carrots_stages'; // Use carrot stages as fallback
|
||||
}
|
||||
|
||||
if (this.scene.textures.exists(textureKey)) {
|
||||
crop.sprite = this.scene.add.sprite(screenPos.x, screenPos.y, textureKey);
|
||||
crop.sprite.setOrigin(0.5, 1);
|
||||
crop.sprite.setScale(0.8);
|
||||
crop.sprite.setDepth(this.scene.iso.getDepth(crop.gridX, crop.gridY));
|
||||
|
||||
// Apply tilts/fallback tints
|
||||
if (textureKey === 'carrots_stages' && crop.type === 'cannabis') {
|
||||
crop.sprite.setTint(0x55ff55); // Green tint for cannabis fallback
|
||||
} else if (crop.type === 'cannabis') {
|
||||
crop.sprite.setTint(0x88ff88); // Subtle green boost for specific texture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +191,12 @@ class FarmingSystem {
|
||||
|
||||
// Give items
|
||||
if (this.scene.inventorySystem) {
|
||||
this.scene.inventorySystem.addItem(crop.type, 1);
|
||||
if (crop.type === 'cannabis') {
|
||||
this.scene.inventorySystem.addItem('cannabis_buds', 2);
|
||||
this.scene.inventorySystem.addItem('hemp_fiber', 3);
|
||||
} else {
|
||||
this.scene.inventorySystem.addItem(crop.type, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Give gold
|
||||
|
||||
@@ -259,13 +259,10 @@ class InteractionSystem {
|
||||
const decor = this.scene.terrainSystem.decorationsMap.get(decorKey);
|
||||
|
||||
if (decor && decor.type === 'chest') {
|
||||
// Open chest and spawn loot!
|
||||
// ... chest opening logic
|
||||
console.log('📦 Opening treasure chest!');
|
||||
|
||||
// Random loot
|
||||
const lootTable = ['item_scrap', 'item_chip', 'item_wood', 'item_stone', 'item_bone'];
|
||||
const lootCount = Phaser.Math.Between(2, 4);
|
||||
|
||||
for (let i = 0; i < lootCount; i++) {
|
||||
const randomLoot = Phaser.Utils.Array.GetRandom(lootTable);
|
||||
if (this.scene.lootSystem) {
|
||||
@@ -274,13 +271,44 @@ class InteractionSystem {
|
||||
this.scene.lootSystem.spawnLoot(gridX + offsetX, gridY + offsetY, randomLoot);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove chest
|
||||
this.scene.terrainSystem.removeDecoration(gridX, gridY);
|
||||
return;
|
||||
}
|
||||
|
||||
// RUIN INTERACTION
|
||||
if (decor && decor.type.includes('ruin')) {
|
||||
console.log('🏚️ Interacted with Ruin at', gridX, gridY);
|
||||
if (this.scene.townRestorationSystem) {
|
||||
// Find a building that matches this location or type
|
||||
// For now, let's just trigger a generic notification or the first building
|
||||
const building = this.scene.townRestorationSystem.buildings.get('jakob_shop'); // Demo link
|
||||
if (building) {
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: gridX * 48, y: gridY * 48 - 40,
|
||||
text: `Restore ${building.name}?`,
|
||||
color: '#FFD700'
|
||||
});
|
||||
|
||||
// Try start restoration if resources exist
|
||||
if (this.scene.townRestorationSystem.hasMaterials(building.materials)) {
|
||||
this.scene.townRestorationSystem.startRestoration(building.id);
|
||||
} else {
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.showNotification) {
|
||||
ui.showNotification({
|
||||
title: 'Missing Materials',
|
||||
text: `Need Wood: ${building.materials.wood}, Stone: ${building.materials.stone}`,
|
||||
icon: '📦'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 4. Try Planting Tree (Manual Planting)
|
||||
if (!isAttack && (activeTool === 'tree_sapling' || activeTool === 'item_sapling')) {
|
||||
const tile = this.scene.terrainSystem.getTile(gridX, gridY);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* @date 2025-12-23
|
||||
*/
|
||||
|
||||
export default class NPCShopSystem {
|
||||
class NPCShopSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
|
||||
@@ -52,15 +52,16 @@ export default class NPCShopSystem {
|
||||
location: { x: 200, y: 200 },
|
||||
inventory: [
|
||||
// Tools
|
||||
{ id: 'iron_axe', name: 'Iron Axe', price: 200, stock: 5, category: 'tools' },
|
||||
{ id: 'iron_pickaxe', name: 'Iron Pickaxe', price: 200, stock: 5, category: 'tools' },
|
||||
{ id: 'iron_hoe', name: 'Iron Hoe', price: 150, stock: 5, category: 'tools' },
|
||||
{ id: 'iron_axe', name: 'Iron Axe', price: 200, stock: 5, category: 'tools', locked: true },
|
||||
{ id: 'iron_pickaxe', name: 'Iron Pickaxe', price: 200, stock: 5, category: 'tools', locked: true },
|
||||
{ id: 'iron_hoe', name: 'Iron Hoe', price: 150, stock: 5, category: 'tools', locked: true },
|
||||
{ id: 'watering_can', name: 'Watering Can', price: 100, stock: 10, category: 'tools' },
|
||||
|
||||
// Weapons
|
||||
{ id: 'iron_sword', name: 'Iron Sword', price: 500, stock: 3, category: 'weapons' },
|
||||
{ id: 'steel_sword', name: 'Steel Sword', price: 1000, stock: 2, category: 'weapons' },
|
||||
{ id: 'crossbow', name: 'Crossbow', price: 800, stock: 2, category: 'weapons' },
|
||||
{ id: 'iron_sword', name: 'Iron Sword', price: 500, stock: 3, category: 'weapons', locked: true },
|
||||
{ id: 'steel_sword', name: 'Steel Sword', price: 1000, stock: 2, category: 'weapons', locked: true },
|
||||
{ id: 'crossbow', name: 'Crossbow', price: 800, stock: 2, category: 'weapons', locked: true },
|
||||
|
||||
|
||||
// Armor
|
||||
{ id: 'leather_armor', name: 'Leather Armor', price: 300, stock: 5, category: 'armor' },
|
||||
@@ -104,6 +105,7 @@ export default class NPCShopSystem {
|
||||
{ id: 'corn_seeds', name: 'Corn Seeds', price: 8, stock: 150, category: 'seeds' },
|
||||
{ id: 'tomato_seeds', name: 'Tomato Seeds', price: 10, stock: 100, category: 'seeds' },
|
||||
{ id: 'strawberry_seeds', name: 'Strawberry Seeds', price: 15, stock: 80, category: 'seeds' },
|
||||
{ id: 'cannabis_seeds', name: 'Cannabis Seeds', price: 20, stock: 50, category: 'seeds' },
|
||||
|
||||
// Materials
|
||||
{ id: 'wood', name: 'Wood', price: 10, stock: 500, category: 'materials' },
|
||||
@@ -138,7 +140,7 @@ export default class NPCShopSystem {
|
||||
{ id: 'bandage', name: 'Bandage', price: 15, stock: 100, category: 'medical' },
|
||||
{ id: 'medicine', name: 'Medicine', price: 80, stock: 30, category: 'medical' }
|
||||
],
|
||||
buyback: ['herbs', 'mushrooms', 'zombie_samples']
|
||||
buyback: ['herbs', 'mushrooms', 'zombie_samples', 'cannabis', 'cannabis_buds']
|
||||
}
|
||||
];
|
||||
|
||||
@@ -332,13 +334,25 @@ export default class NPCShopSystem {
|
||||
this.itemListContainer.add(priceText);
|
||||
|
||||
// Buy button
|
||||
const buyBtn = this.scene.add.rectangle(350, y, 100, 35, 0x228B22);
|
||||
buyBtn.setStrokeStyle(2, 0x32CD32);
|
||||
const isLocked = item.locked && !this.isShopRestored(this.currentShop.id);
|
||||
const buyColor = isLocked ? 0x666666 : 0x228B22;
|
||||
const buyBtn = this.scene.add.rectangle(350, y, 100, 35, buyColor);
|
||||
buyBtn.setStrokeStyle(2, isLocked ? 0x999999 : 0x32CD32);
|
||||
buyBtn.setInteractive();
|
||||
buyBtn.on('pointerdown', () => this.buyItem(item));
|
||||
buyBtn.on('pointerdown', () => {
|
||||
if (isLocked) {
|
||||
this.showNotification({
|
||||
title: 'Shop Ruined',
|
||||
text: `Restore the shop to unlock this item!`,
|
||||
icon: '🏚️'
|
||||
});
|
||||
} else {
|
||||
this.buyItem(item);
|
||||
}
|
||||
});
|
||||
this.itemListContainer.add(buyBtn);
|
||||
|
||||
const buyText = this.scene.add.text(350, y, 'BUY', {
|
||||
const buyText = this.scene.add.text(350, y, isLocked ? 'LOCKED' : 'BUY', {
|
||||
fontSize: '14px',
|
||||
fontFamily: 'Arial',
|
||||
color: '#ffffff',
|
||||
@@ -349,6 +363,24 @@ export default class NPCShopSystem {
|
||||
});
|
||||
}
|
||||
|
||||
isShopRestored(shopId) {
|
||||
if (!this.scene.townRestorationSystem) return true; // Fallback if system missing
|
||||
|
||||
// Map shopId to buildingId
|
||||
const mapping = {
|
||||
'blacksmith': 'jakob_shop', // or whichever ID correctly matches TownRestorationSystem
|
||||
'baker': 'lena_bakery',
|
||||
'healer': 'dr_chen_clinic'
|
||||
};
|
||||
|
||||
const buildingId = mapping[shopId];
|
||||
if (!buildingId) return true; // General trader is always open
|
||||
|
||||
const building = this.scene.townRestorationSystem.buildings.get(buildingId);
|
||||
return building ? building.isRestored : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by category
|
||||
*/
|
||||
|
||||
101
src/systems/StatusEffectSystem.js
Normal file
101
src/systems/StatusEffectSystem.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* StatusEffectSystem.js
|
||||
* ====================
|
||||
* Manages temporary status effects for the player.
|
||||
*/
|
||||
|
||||
class StatusEffectSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.activeEffects = new Map();
|
||||
|
||||
// Initial state
|
||||
this.originalSpeed = 0;
|
||||
|
||||
console.log('🌈 StatusEffectSystem initialized');
|
||||
}
|
||||
|
||||
applyEffect(type, duration = 15000) {
|
||||
if (this.activeEffects.has(type)) {
|
||||
// Refresh duration
|
||||
const effect = this.activeEffects.get(type);
|
||||
effect.endTime = Date.now() + duration;
|
||||
console.log(`✨ Refreshed effect: ${type}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const effect = {
|
||||
type,
|
||||
startTime: Date.now(),
|
||||
endTime: Date.now() + duration
|
||||
};
|
||||
|
||||
this.activeEffects.set(type, effect);
|
||||
this.startEffect(type);
|
||||
return true;
|
||||
}
|
||||
|
||||
startEffect(type) {
|
||||
console.log(`🚀 Starting effect: ${type}`);
|
||||
|
||||
if (type === 'high') {
|
||||
// Visual: Rainbow Tint
|
||||
const uiScene = this.scene.scene.get('UIScene');
|
||||
if (uiScene && uiScene.overlayGraphics) {
|
||||
this.scene.tweens.addCounter({
|
||||
from: 0,
|
||||
to: 360,
|
||||
duration: 4000,
|
||||
repeat: -1,
|
||||
onUpdate: (tween) => {
|
||||
if (!this.activeEffects.has('high')) return;
|
||||
const hull = Phaser.Display.Color.HSVToRGB(tween.getValue() / 360, 0.4, 1);
|
||||
const alpha = 0.1 + Math.sin(tween.getValue() * 0.05) * 0.05; // Breathing alpha
|
||||
uiScene.overlayGraphics.clear();
|
||||
uiScene.overlayGraphics.fillStyle(hull.color, alpha);
|
||||
uiScene.overlayGraphics.fillRect(0, 0, this.scene.scale.width, this.scene.scale.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gameplay: Movement slow
|
||||
if (this.scene.player) {
|
||||
this.originalSpeed = this.scene.player.moveSpeed;
|
||||
this.scene.player.moveSpeed *= 0.6;
|
||||
}
|
||||
|
||||
// Camera Shake/Waves?
|
||||
this.scene.cameras.main.setLerp(0.05, 0.05); // Looser camera
|
||||
}
|
||||
}
|
||||
|
||||
stopEffect(type) {
|
||||
console.log(`🛑 Stopping effect: ${type}`);
|
||||
this.activeEffects.delete(type);
|
||||
|
||||
if (type === 'high') {
|
||||
// Clear visual
|
||||
const uiScene = this.scene.scene.get('UIScene');
|
||||
if (uiScene && uiScene.overlayGraphics) {
|
||||
uiScene.overlayGraphics.clear();
|
||||
}
|
||||
|
||||
// Reset movement
|
||||
if (this.scene.player) {
|
||||
this.scene.player.moveSpeed = this.originalSpeed || 100;
|
||||
}
|
||||
|
||||
// Reset camera
|
||||
this.scene.cameras.main.setLerp(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
const now = Date.now();
|
||||
this.activeEffects.forEach((effect, type) => {
|
||||
if (now >= effect.endTime) {
|
||||
this.stopEffect(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -208,6 +208,11 @@ class TownRestorationSystem {
|
||||
// Check milestones
|
||||
this.checkMilestones();
|
||||
|
||||
// Update visuals in the current scene
|
||||
if (this.scene.updateBuildingVisuals) {
|
||||
this.scene.updateBuildingVisuals(buildingId);
|
||||
}
|
||||
|
||||
this.showNotification({
|
||||
title: 'Building Complete!',
|
||||
text: `✅ ${building.name} restored!`,
|
||||
@@ -319,19 +324,23 @@ class TownRestorationSystem {
|
||||
return rewards[milestone] || { text: 'Bonus unlocked!' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check materials
|
||||
*/
|
||||
hasMaterials(required) {
|
||||
// TODO: Check actual inventory
|
||||
return true; // For now
|
||||
hasMaterials(materials) {
|
||||
if (!this.scene.inventorySystem) return true; // Safety fallback
|
||||
|
||||
for (const [item, count] of Object.entries(materials)) {
|
||||
if (!this.scene.inventorySystem.hasItem(item, count)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume materials
|
||||
*/
|
||||
consumeMaterials(materials) {
|
||||
// TODO: Remove from inventory
|
||||
if (!this.scene.inventorySystem) return;
|
||||
|
||||
for (const [item, count] of Object.entries(materials)) {
|
||||
this.scene.inventorySystem.removeItem(item, count);
|
||||
}
|
||||
console.log('📦 Materials consumed:', materials);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user