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:
2025-12-27 23:32:22 +01:00
parent 611cd35777
commit 822c586843
12 changed files with 454 additions and 40 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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
*/

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

View File

@@ -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);
}