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:
@@ -153,6 +153,21 @@
|
||||
},
|
||||
"unlocked": false,
|
||||
"craftTime": 2000
|
||||
},
|
||||
"hemp_clothing": {
|
||||
"id": "hemp_clothing",
|
||||
"name": "Hemp Clothing",
|
||||
"description": "Durable and sustainable clothing made from hemp fiber.",
|
||||
"category": "materials",
|
||||
"ingredients": {
|
||||
"hemp_fiber": 5
|
||||
},
|
||||
"result": {
|
||||
"item": "hemp_clothing",
|
||||
"quantity": 1
|
||||
},
|
||||
"unlocked": true,
|
||||
"craftTime": 3000
|
||||
}
|
||||
},
|
||||
"categories": [
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
<script src="src/scenes/PrologueScene.js"></script> <!-- 🎬 Story Prologue -->
|
||||
<script src="src/scenes/UIScene.js"></script>
|
||||
<script src="src/scenes/StoryScene.js"></script>
|
||||
<script src="src/scenes/TownSquareScene.js"></script>
|
||||
|
||||
<!-- 🛠️ CRAFTING SYSTEM -->
|
||||
<script src="src/systems/CraftingSystem.js"></script>
|
||||
|
||||
@@ -164,7 +164,8 @@ class Player {
|
||||
right: Phaser.Input.Keyboard.KeyCodes.RIGHT,
|
||||
// Actions
|
||||
space: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
||||
shift: Phaser.Input.Keyboard.KeyCodes.SHIFT
|
||||
shift: Phaser.Input.Keyboard.KeyCodes.SHIFT,
|
||||
r: Phaser.Input.Keyboard.KeyCodes.R
|
||||
});
|
||||
|
||||
// Gamepad Events
|
||||
@@ -306,6 +307,11 @@ class Player {
|
||||
if (this.keys.space && Phaser.Input.Keyboard.JustDown(this.keys.space)) {
|
||||
this.handleFarmingAction();
|
||||
}
|
||||
|
||||
// 🚬 R KEY - Use Item
|
||||
if (this.keys.r && Phaser.Input.Keyboard.JustDown(this.keys.r)) {
|
||||
this.useSelectedItem();
|
||||
}
|
||||
}
|
||||
|
||||
updateHeldItem() {
|
||||
@@ -748,4 +754,57 @@ class Player {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
useSelectedItem() {
|
||||
const uiScene = this.scene.scene.get('UIScene');
|
||||
const invSys = this.scene.inventorySystem;
|
||||
|
||||
if (!uiScene || !invSys) return;
|
||||
|
||||
const selectedIdx = uiScene.selectedSlot;
|
||||
const slot = invSys.slots[selectedIdx];
|
||||
if (!slot || slot.count <= 0) return;
|
||||
|
||||
console.log(`🌀 Attempting to use item: ${slot.type}`);
|
||||
|
||||
if (slot.type === 'cannabis_buds') {
|
||||
// SMOKE IT!
|
||||
if (this.scene.statusEffectSystem) {
|
||||
const used = this.scene.statusEffectSystem.applyEffect('high');
|
||||
if (used) {
|
||||
invSys.removeItem(slot.type, 1);
|
||||
// Visual feedback
|
||||
this.createSmokeParticles(this.gridX, this.gridY);
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: this.sprite.x, y: this.sprite.y - 60, text: '🚬 *puff puff*', color: '#55ff55'
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (slot.type === 'health_potion' || slot.type === 'medicine') {
|
||||
// Generic healing
|
||||
if (this.hp < this.maxHp) {
|
||||
this.hp = Math.min(this.maxHp, this.hp + 20);
|
||||
invSys.removeItem(slot.type, 1);
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: this.sprite.x, y: this.sprite.y - 60, text: '💖 Recovered!', color: '#ff5555'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createSmokeParticles(gridX, gridY) {
|
||||
// Reuse particle logic, or create locally
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const smoke = this.scene.add.circle(this.sprite.x, this.sprite.y - 40, 4 + Math.random() * 4, 0xcccccc, 0.6);
|
||||
this.scene.tweens.add({
|
||||
targets: smoke,
|
||||
x: smoke.x + (Math.random() - 0.5) * 50,
|
||||
y: smoke.y - 100 - Math.random() * 50,
|
||||
alpha: 0,
|
||||
scale: 3,
|
||||
duration: 2000 + Math.random() * 1000,
|
||||
onComplete: () => smoke.destroy()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ const config = {
|
||||
debug: false
|
||||
}
|
||||
},
|
||||
scene: [BootScene, PreloadScene, TiledTestScene, StoryScene, PrologueScene, GameScene, UIScene],
|
||||
scene: [BootScene, PreloadScene, TiledTestScene, StoryScene, PrologueScene, GameScene, UIScene, TownSquareScene],
|
||||
scale: {
|
||||
mode: Phaser.Scale.FIT,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Game Scene - Glavna igralna scena
|
||||
|
||||
class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'GameScene' });
|
||||
@@ -6,13 +7,14 @@ class GameScene extends Phaser.Scene {
|
||||
this.terrainContainer = null;
|
||||
this.player = null;
|
||||
this.npcs = []; // Array za NPCje
|
||||
this.statusEffectSystem = null;
|
||||
|
||||
// Settings
|
||||
this.settings = {
|
||||
viewDistance: 'HIGH', // LOW, MEDIUM, HIGH
|
||||
particles: 'HIGH', // NONE, LOW, HIGH
|
||||
viewDistance: 'HIGH',
|
||||
particles: 'HIGH',
|
||||
shadows: true
|
||||
};
|
||||
this.townRestorationSystem = null;
|
||||
}
|
||||
|
||||
async create() {
|
||||
@@ -258,6 +260,7 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
// Initialize Farming System
|
||||
this.farmingSystem = new FarmingSystem(this);
|
||||
this.statusEffectSystem = new StatusEffectSystem(this);
|
||||
console.log('🌾 Farming system initialized!');
|
||||
|
||||
// Initialize Build System
|
||||
@@ -759,6 +762,12 @@ class GameScene extends Phaser.Scene {
|
||||
this.craftingUI.toggle();
|
||||
}
|
||||
});
|
||||
|
||||
// Add M key to transition to Town Square
|
||||
this.input.keyboard.on('keydown-M', () => {
|
||||
console.log('🔄 Transitioning to Town Square...');
|
||||
this.scene.start('TownSquareScene');
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================
|
||||
@@ -774,6 +783,8 @@ class GameScene extends Phaser.Scene {
|
||||
this.lootSystem = new LootSystem(this);
|
||||
this.interactionSystem = new InteractionSystem(this);
|
||||
this.farmingSystem = new FarmingSystem(this);
|
||||
this.statusEffectSystem = new StatusEffectSystem(this);
|
||||
this.townRestorationSystem = new TownRestorationSystem(this);
|
||||
this.buildingSystem = new BuildingSystem(this);
|
||||
// this.pathfinding = new Pathfinding(this); // REMOVED: Using PathfindingSystem (Worker) instead
|
||||
this.questSystem = new QuestSystem(this);
|
||||
@@ -1879,6 +1890,7 @@ class GameScene extends Phaser.Scene {
|
||||
if (this.lootSystem) this.lootSystem.update(delta);
|
||||
if (this.interactionSystem) this.interactionSystem.update(delta);
|
||||
if (this.farmingSystem) this.farmingSystem.update(delta);
|
||||
if (this.statusEffectSystem) this.statusEffectSystem.update(time, delta);
|
||||
if (this.buildSystem) this.buildSystem.update(delta);
|
||||
if (this.questSystem) this.questSystem.update(delta);
|
||||
if (this.multiplayerSystem) this.multiplayerSystem.update(delta);
|
||||
|
||||
92
src/scenes/TownSquareScene.js
Normal file
92
src/scenes/TownSquareScene.js
Normal file
@@ -0,0 +1,92 @@
|
||||
class TownSquareScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'TownSquareScene' });
|
||||
this.buildingsSprites = new Map();
|
||||
}
|
||||
|
||||
create() {
|
||||
console.log('🏘️ TownSquareScene: Initialized!');
|
||||
this.cameras.main.setBackgroundColor('#8B4513');
|
||||
|
||||
// Access the global TownRestorationSystem
|
||||
this.townRestorationSystem = window.townRestorationSystem || new TownRestorationSystem(this);
|
||||
|
||||
this.add.text(512, 50, 'TOWN SQUARE', {
|
||||
fontSize: '48px', color: '#FFD700', fontFamily: 'Courier New', fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.add.text(512, 100, 'Press M to go back to Farm', {
|
||||
fontSize: '18px', color: '#ffffff', fontFamily: 'Courier New'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Transition back
|
||||
this.input.keyboard.on('keydown-M', () => {
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
|
||||
// Create Ruins
|
||||
this.createBuilding(400, 300, 'jakob_shop', 'Jakob\'s Shop');
|
||||
this.createBuilding(650, 300, 'lena_bakery', 'Lena\'s Bakery');
|
||||
this.createBuilding(512, 500, 'dr_chen_clinic', 'Dr. Chen\'s Clinic');
|
||||
}
|
||||
|
||||
createBuilding(x, y, id, name) {
|
||||
if (!this.townRestorationSystem) return;
|
||||
|
||||
const buildingData = this.townRestorationSystem.buildings.get(id);
|
||||
const isRestored = buildingData ? buildingData.isRestored : false;
|
||||
|
||||
const color = isRestored ? 0x44aa44 : 0x666666;
|
||||
const base = this.add.rectangle(x, y, 150, 150, color, 0.8);
|
||||
base.setStrokeStyle(3, 0xffffff);
|
||||
|
||||
const label = this.add.text(x, y, `${name}\n${isRestored ? '✅ RESTORED' : '🏚️ RUIN'}`, {
|
||||
fontSize: '16px', color: '#ffffff', align: 'center', fontFamily: 'Courier New', fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
base.setInteractive();
|
||||
base.on('pointerdown', () => {
|
||||
const currentData = this.townRestorationSystem.buildings.get(id);
|
||||
if (!currentData || !currentData.isRestored) {
|
||||
this.interactWithRuin(id);
|
||||
} else {
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.showNotification) {
|
||||
ui.showNotification({ text: `${name} is functional!`, icon: '🏪' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.buildingsSprites.set(id, { base, label, name });
|
||||
}
|
||||
|
||||
interactWithRuin(id) {
|
||||
const building = this.townRestorationSystem.buildings.get(id);
|
||||
if (!building) return;
|
||||
|
||||
if (this.townRestorationSystem.hasMaterials(building.materials)) {
|
||||
this.townRestorationSystem.startRestoration(id);
|
||||
} else {
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.showNotification) {
|
||||
ui.showNotification({
|
||||
title: 'Resources Needed',
|
||||
text: `Wood: ${building.materials.wood}, Stone: ${building.materials.stone}`,
|
||||
icon: '📦'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBuildingVisuals(id) {
|
||||
const sprite = this.buildingsSprites.get(id);
|
||||
if (sprite) {
|
||||
sprite.base.setFillStyle(0x44aa44);
|
||||
sprite.label.setText(`${sprite.name}\n✅ RESTORED`);
|
||||
|
||||
// Celebration effect
|
||||
this.cameras.main.shake(500, 0.01);
|
||||
console.log(`✨ Visuals updated for ${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -713,7 +713,11 @@ class TextureGenerator {
|
||||
{ name: 'artefact_old', color: '#8B4513' }, // Ancient Pot (Brown)
|
||||
{ name: 'milk', color: '#FFFFFF' }, // White
|
||||
{ name: 'glowing_milk', color: '#00FF00' }, // Green
|
||||
{ name: 'animal_feed', color: '#F4A460' } // Sandy Brown (Feed)
|
||||
{ name: 'animal_feed', color: '#F4A460' }, // Sandy Brown (Feed)
|
||||
{ name: 'cannabis', color: '#228B22' }, // Green
|
||||
{ name: 'cannabis_seeds', color: '#55ff55' }, // Lighter Green
|
||||
{ name: 'hemp_fiber', color: '#DEB887' }, // Tan/Beige
|
||||
{ name: 'hemp_clothing', color: '#2E8B57' } // SeaGreen
|
||||
];
|
||||
items.forEach(item => {
|
||||
const it = typeof item === 'string' ? item : item.name;
|
||||
@@ -747,6 +751,17 @@ class TextureGenerator {
|
||||
x.fillStyle = '#DAA520';
|
||||
x.fillRect(13, 22, 6, 3);
|
||||
}
|
||||
else if (it === 'cannabis') {
|
||||
// Cannabis Leaf (Stylized)
|
||||
x.fillStyle = '#228B22';
|
||||
// Center leaf
|
||||
x.beginPath(); x.ellipse(16, 12, 3, 10, 0, 0, Math.PI * 2); x.fill();
|
||||
// Side leaves
|
||||
x.beginPath(); x.ellipse(10, 16, 3, 8, -Math.PI / 4, 0, Math.PI * 2); x.fill();
|
||||
x.beginPath(); x.ellipse(22, 16, 3, 8, Math.PI / 4, 0, Math.PI * 2); x.fill();
|
||||
x.beginPath(); x.ellipse(10, 24, 3, 6, -Math.PI / 2.5, 0, Math.PI * 2); x.fill();
|
||||
x.beginPath(); x.ellipse(22, 24, 3, 6, Math.PI / 2.5, 0, Math.PI * 2); x.fill();
|
||||
}
|
||||
else if (it.includes('seeds')) {
|
||||
// Seed Bag
|
||||
x.fillStyle = '#DEB887'; // Burlywood bag
|
||||
@@ -758,6 +773,42 @@ class TextureGenerator {
|
||||
x.fillStyle = color;
|
||||
x.beginPath(); x.arc(16, 18, 4, 0, Math.PI * 2); x.fill();
|
||||
}
|
||||
else if (it === 'cannabis_buds') {
|
||||
// Stylized "bud" (forest green clusters)
|
||||
x.fillStyle = '#228B22';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const ang = (i / 5) * Math.PI * 2;
|
||||
x.beginPath();
|
||||
x.arc(16 + Math.cos(ang) * 6, 16 + Math.sin(ang) * 6, 5, 0, Math.PI * 2);
|
||||
x.fill();
|
||||
}
|
||||
x.fillStyle = '#8FBC8F'; // Frosted look
|
||||
x.beginPath(); x.arc(16, 16, 4, 0, Math.PI * 2); x.fill();
|
||||
}
|
||||
else if (it === 'hemp_fiber') {
|
||||
// Bundle of fibers (tan/straw color)
|
||||
x.strokeStyle = '#DEB887';
|
||||
x.lineWidth = 2;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
x.beginPath();
|
||||
x.moveTo(10 + i * 3, 24);
|
||||
x.quadraticCurveTo(16, 16, 10 + (4 - i) * 3, 8);
|
||||
x.stroke();
|
||||
}
|
||||
// Tie
|
||||
x.fillStyle = '#8B4513';
|
||||
x.fillRect(10, 15, 12, 2);
|
||||
}
|
||||
else if (it === 'hemp_clothing') {
|
||||
// Simple T-shirt shape
|
||||
x.fillStyle = '#2E8B57'; // SeaGreen
|
||||
x.fillRect(8, 10, 16, 18); // Body
|
||||
x.fillRect(4, 10, 6, 8); // Left sleeve
|
||||
x.fillRect(22, 10, 6, 8); // Right sleeve
|
||||
// Collar
|
||||
x.fillStyle = '#000';
|
||||
x.beginPath(); x.arc(16, 10, 4, 0, Math.PI); x.fill();
|
||||
}
|
||||
else {
|
||||
// Default Circle
|
||||
x.fillStyle = color;
|
||||
|
||||
Reference in New Issue
Block a user