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

@@ -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": [

View File

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

View File

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

View File

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

View File

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

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

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

View File

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