Files
ai-qpid-pvz/game/js/systems/economy.js
T
Antigravity 6457a8904e feat: Initial commit Plants vs Zombies Web Edition v2.4
- 5 Biomes: Day, Night, Pool, Fog, Roof (25 Campaign levels)
- Minigames: Wall-nut Bowling, I Zombie, Roguelike, Boss Rush & Dr. Zomboss
- Systems: Zen Garden, Workshop Crafting, Almanac, Tree of Wisdom, Achievements
- Backend: REST API + SQLite Accounts & Save Synchronization
- Infrastructure: Nginx HTTPS & Docker deployment
2026-09-17 09:30:52 +00:00

742 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Economy, Sun, Coin Drops & Crazy Dave's Shop System
window.EconomySystem = class EconomySystem {
constructor() {
this.sun = 50;
this.suns = [];
this.coins = [];
this.skySunTimer = 0;
this.skySunInterval = 8.5;
this.isNight = false;
this.isConveyor = false; // For Wall-nut bowling mini-game
this.isIZombie = false;
// Total coins & upgrades persistent storage
this.reloadProfileData();
// Full Card Registry
this.allCards = [
{ id: 'sunflower', name: 'Подсолнух', cost: 50, cooldown: 7.5, timer: 0 },
{ id: 'peashooter', name: 'Горохострел', cost: 100, cooldown: 7.5, timer: 0 },
{ id: 'potatomine', name: 'Мина', cost: 25, cooldown: 18.0, timer: 0 },
{ id: 'wallnut', name: 'Стеноорех', cost: 50, cooldown: 20.0, timer: 0 },
{ id: 'repeater', name: 'Повторитель', cost: 200, cooldown: 7.5, timer: 0 },
{ id: 'snowpea', name: 'Ледяной', cost: 175, cooldown: 10.0, timer: 0 },
{ id: 'chomper', name: 'Зубастик', cost: 150, cooldown: 10.0, timer: 0 },
{ id: 'cherrybomb', name: 'Вишня', cost: 150, cooldown: 25.0, timer: 0 },
{ id: 'jalapeno', name: 'Халапеньо', cost: 125, cooldown: 30.0, timer: 0 },
{ id: 'sunshroom', name: 'Солнцегриб', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'fumeshroom', name: 'Дымогриб', cost: 75, cooldown: 7.5, timer: 0 },
{ id: 'lilypad', name: 'Кувшинка', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'gravebuster', name: 'Могилоед', cost: 75, cooldown: 15.0, timer: 0 },
{ id: 'flowerpot', name: 'Горшок', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'cabbagepult', name: 'Капуста', cost: 100, cooldown: 7.5, timer: 0 },
{ id: 'melonpult', name: 'Арбуз', cost: 300, cooldown: 15.0, timer: 0 }
];
this.selectedCard = null;
this.animTime = 0;
this.setDeckForLevel(1, 'day');
}
reloadProfileData() {
if (window.PvZAuth) {
const uData = window.PvZAuth.getUserData();
this.totalCoins = uData.coins !== undefined ? uData.coins : 250;
this.upgrades = uData.upgrades || { extraSlot: false, hasRake: false };
} else {
this.totalCoins = parseInt(localStorage.getItem('pvz_coins') || '250', 10);
this.upgrades = {
extraSlot: localStorage.getItem('pvz_upg_slot') === 'true',
hasRake: localStorage.getItem('pvz_upg_rake') === 'true'
};
}
}
saveProfileData() {
localStorage.setItem('pvz_coins', this.totalCoins.toString());
localStorage.setItem('pvz_upg_slot', this.upgrades.extraSlot ? 'true' : 'false');
localStorage.setItem('pvz_upg_rake', this.upgrades.hasRake ? 'true' : 'false');
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
window.PvZAuth.saveUserData({
coins: this.totalCoins,
upgrades: this.upgrades
});
}
}
setDeckForLevel(level = 1, biome = 'day') {
this.isNight = biome === 'night';
this.isConveyor = false;
this.isIZombie = false;
// Card slots count (5 default, 6 with Dave's extra slot upgrade)
const slotCount = this.upgrades.extraSlot ? 6 : 5;
// Choose relevant deck based on biome and progression
let availableIds = [];
if (biome === 'roof') {
availableIds = ['flowerpot', 'cabbagepult', 'melonpult', 'wallnut', 'jalapeno', 'potatomine'];
} else if (biome === 'fog') {
availableIds = ['sunshroom', 'fumeshroom', 'lilypad', 'cabbagepult', 'wallnut', 'snowpea'];
} else if (biome === 'night') {
availableIds = ['sunshroom', 'fumeshroom', 'potatomine', 'wallnut', 'gravebuster', 'hypnoshroom'];
} else if (biome === 'pool') {
availableIds = ['sunflower', 'peashooter', 'lilypad', 'wallnut', 'repeater', 'snowpea'];
} else {
if (level === 1) availableIds = ['sunflower', 'peashooter', 'potatomine', 'wallnut', 'cherrybomb'];
else if (level === 2) availableIds = ['sunflower', 'peashooter', 'repeater', 'wallnut', 'jalapeno'];
else availableIds = ['sunflower', 'peashooter', 'snowpea', 'chomper', 'wallnut', 'jalapeno'];
}
this.cards = this.allCards.filter(c => availableIds.includes(c.id)).slice(0, slotCount);
}
setBossDeck() {
this.isConveyor = true;
this.isIZombie = false;
this.cards = [
{ id: 'flowerpot', name: 'Горшок', cost: 0, cooldown: 3.5, timer: 0 },
{ id: 'cabbagepult', name: 'Капуста', cost: 0, cooldown: 5.0, timer: 0 },
{ id: 'melonpult', name: 'Арбуз', cost: 0, cooldown: 10.0, timer: 0 },
{ id: 'jalapeno', name: 'Халапеньо', cost: 0, cooldown: 14.0, timer: 0 },
{ id: 'snowpea', name: 'Ледяной', cost: 0, cooldown: 8.0, timer: 0 }
];
}
setConveyorDeck() {
this.isConveyor = true;
this.isIZombie = false;
this.cards = [
{ id: 'bowling_nut', name: 'Орех-шар', cost: 0, cooldown: 4.0, timer: 0 },
{ id: 'bowling_cherry', name: 'Взрывной', cost: 0, cooldown: 12.0, timer: 0 }
];
}
setIZombieDeck() {
this.isConveyor = false;
this.isIZombie = true;
this.isNight = true;
this.sun = 200;
this.cards = [
{ id: 'z_normal', name: 'Обычный', cost: 50, cooldown: 2.0, timer: 0 },
{ id: 'z_conehead', name: 'Конус', cost: 75, cooldown: 3.0, timer: 0 },
{ id: 'z_polevaulter', name: 'С шестом', cost: 75, cooldown: 4.0, timer: 0 },
{ id: 'z_buckethead', name: 'Ведро', cost: 125, cooldown: 5.0, timer: 0 },
{ id: 'z_football', name: 'Футболист', cost: 175, cooldown: 6.0, timer: 0 }
];
}
reset() {
let baseSun = this.isConveyor ? 0 : (this.isIZombie ? 200 : 50);
if (!this.isConveyor && !this.isIZombie && this.workshop) {
baseSun += this.workshop.getPerkLevel('startSun') * 50;
}
this.sun = baseSun;
this.suns = [];
this.coins = [];
this.skySunTimer = 0;
this.selectedCard = null;
this.cards.forEach(c => c.timer = 0);
}
addSun(amount) {
this.sun += amount;
if (this.engine && this.engine.achievements) {
this.engine.achievements.setProgress('sun_tycoon', this.sun);
}
}
canAfford(cost) {
return this.isConveyor || this.sun >= cost;
}
spendSun(cost) {
if (this.isConveyor) return true;
if (this.sun >= cost) {
this.sun -= cost;
return true;
}
return false;
}
addCoins(amount) {
this.totalCoins += amount;
this.saveProfileData();
}
buyUpgrade(type) {
if (type === 'extraSlot' && this.totalCoins >= 500 && !this.upgrades.extraSlot) {
this.addCoins(-500);
this.upgrades.extraSlot = true;
this.saveProfileData();
this.setDeckForLevel(1, this.isNight ? 'night' : 'day');
return true;
} else if (type === 'rake' && this.totalCoins >= 150 && !this.upgrades.hasRake) {
this.addCoins(-150);
this.upgrades.hasRake = true;
this.saveProfileData();
return true;
}
return false;
}
spawnSun(x, y, value = 25, isFalling = false, targetY = null) {
this.suns.push({
x,
y,
targetY: targetY !== null ? targetY : y,
value,
isFalling,
fallSpeed: 90,
radius: 26,
life: 13.0,
collecting: false,
toRemove: false,
pulse: Math.random() * 5
});
}
spawnCoin(x, y, type = 'silver') {
const val = type === 'diamond' ? 200 : (type === 'gold' ? 50 : 10);
this.coins.push({
x,
y,
type,
value: val,
radius: 16,
life: 12.0,
collecting: false,
toRemove: false,
pulse: Math.random() * 5
});
}
update(dt) {
this.animTime += dt;
// Cooldown timers with fastCool perk bonus
const fastCoolLvl = this.workshop ? this.workshop.getPerkLevel('fastCool') : 0;
const cooldownMultiplier = 1 + fastCoolLvl * 0.15;
for (const card of this.cards) {
if (card.timer > 0) {
card.timer = Math.max(0, card.timer - dt * cooldownMultiplier);
}
}
// Sky sun spawner (disabled at night and in conveyor mode)
if (!this.isNight && !this.isConveyor) {
this.skySunTimer += dt;
if (this.skySunTimer >= this.skySunInterval) {
this.skySunTimer = 0;
const spawnX = 140 + Math.random() * 640;
const targetY = 140 + Math.random() * 380;
this.spawnSun(spawnX, 40, 25, true, targetY);
}
}
// Active suns
for (const s of this.suns) {
s.pulse += dt * 4;
if (s.collecting) {
const targetX = 50;
const targetY = 45;
const dx = targetX - s.x;
const dy = targetY - s.y;
const dist = Math.hypot(dx, dy);
if (dist < 15) {
this.addSun(s.value);
s.toRemove = true;
} else {
s.x += (dx / dist) * 750 * dt;
s.y += (dy / dist) * 750 * dt;
}
} else {
if (s.isFalling && s.y < s.targetY) {
s.y += s.fallSpeed * dt;
}
s.life -= dt;
if (s.life <= 0) s.toRemove = true;
}
}
this.suns = this.suns.filter(s => !s.toRemove);
// Active coins
for (const c of this.coins) {
c.pulse += dt * 5;
if (c.collecting) {
const targetX = 850;
const targetY = 45;
const dx = targetX - c.x;
const dy = targetY - c.y;
const dist = Math.hypot(dx, dy);
if (dist < 15) {
this.addCoins(c.value);
c.toRemove = true;
} else {
c.x += (dx / dist) * 750 * dt;
c.y += (dy / dist) * 750 * dt;
}
} else {
c.life -= dt;
if (c.life <= 0) c.toRemove = true;
}
}
this.coins = this.coins.filter(c => !c.toRemove);
}
handleClick(mouseX, mouseY) {
// 0. Workshop Floating Loot
if (this.workshop) {
for (let i = this.workshop.floatingLoot.length - 1; i >= 0; i--) {
const loot = this.workshop.floatingLoot[i];
if (!loot.collected && Math.hypot(mouseX - loot.x, mouseY - loot.y) <= 30) {
this.workshop.collectLoot(loot);
return { handled: true, action: 'loot_collected' };
}
}
}
// 1. Suns
for (let i = this.suns.length - 1; i >= 0; i--) {
const s = this.suns[i];
if (!s.collecting && Math.hypot(mouseX - s.x, mouseY - s.y) <= s.radius + 14) {
s.collecting = true;
if (window.PvZAudio) window.PvZAudio.playSun();
if (this.workshop) {
this.sunCount = (this.sunCount || 0) + 1;
if (this.sunCount >= 8) {
this.sunCount = 0;
this.workshop.addMaterial('sunEssence', 1);
}
}
return { handled: true, action: 'sun_collected' };
}
}
// 2. Coins
for (let i = this.coins.length - 1; i >= 0; i--) {
const c = this.coins[i];
if (!c.collecting && Math.hypot(mouseX - c.x, mouseY - c.y) <= c.radius + 14) {
c.collecting = true;
if (window.PvZAudio) window.PvZAudio.playCoin();
return { handled: true, action: 'coin_collected' };
}
}
// 3. Seed Cards
const cardStartX = 105;
const cardY = 8;
const cardW = 66;
const cardH = 72;
const spacing = 72;
for (let i = 0; i < this.cards.length; i++) {
const card = this.cards[i];
const cx = cardStartX + i * spacing;
if (mouseX >= cx && mouseX <= cx + cardW && mouseY >= cardY && mouseY <= cardY + cardH) {
if (this.canAfford(card.cost) && card.timer <= 0) {
this.selectedCard = (this.selectedCard === card.id) ? null : card.id;
}
return { handled: true, action: 'card_selected' };
}
}
// 4. Shovel
const shovelX = cardStartX + this.cards.length * spacing + 12;
if (mouseX >= shovelX && mouseX <= shovelX + 54 && mouseY >= cardY && mouseY <= cardY + cardH) {
this.selectedCard = (this.selectedCard === 'shovel') ? null : 'shovel';
return { handled: true, action: 'shovel_selected' };
}
return { handled: false };
}
startCooldown(cardId) {
const card = this.cards.find(c => c.id === cardId);
if (card) {
card.timer = card.cooldown;
}
}
draw(ctx) {
// HUD Bar Background
ctx.fillStyle = this.isNight ? '#1a100c' : '#2c1810';
ctx.fillRect(0, 0, 900, 90);
ctx.fillStyle = this.isNight ? '#2a1810' : '#422817';
ctx.fillRect(0, 0, 900, 86);
// 1. Sun Bank Box (or Conveyor indicator)
if (!this.isConveyor) {
ctx.fillStyle = '#e0c080';
ctx.beginPath();
ctx.roundRect(10, 8, 85, 72, 8);
ctx.fill();
ctx.strokeStyle = '#6d4c41';
ctx.lineWidth = 3;
ctx.stroke();
this.drawSunGraphic(ctx, 36, 34, 16, 0, 1);
ctx.fillStyle = '#000';
ctx.font = 'bold 18px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(this.sun, 52, 66);
} else {
// Conveyor belt indicator
ctx.fillStyle = '#616161';
ctx.beginPath();
ctx.roundRect(10, 8, 85, 72, 8);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('КОНВЕЙЕР', 52, 48);
}
// 2. Draw Cards
const cardStartX = 105;
const cardY = 8;
const cardW = 66;
const cardH = 72;
const spacing = 72;
for (let i = 0; i < this.cards.length; i++) {
const card = this.cards[i];
const cx = cardStartX + i * spacing;
const isSelected = this.selectedCard === card.id;
const affordable = this.canAfford(card.cost);
const ready = card.timer <= 0;
ctx.fillStyle = isSelected ? '#fff9c4' : (affordable && ready ? '#fff8e1' : '#b0bec5');
ctx.beginPath();
ctx.roundRect(cx, cardY, cardW, cardH, 6);
ctx.fill();
ctx.strokeStyle = isSelected ? '#ffeb3b' : (affordable && ready ? '#8d6e63' : '#546e7a');
ctx.lineWidth = isSelected ? 3 : 2;
ctx.stroke();
this.drawCardThumbnail(ctx, card.id, cx + cardW / 2, cardY + 28);
if (this.workshop) {
const lvl = this.workshop.getPlantLevel(card.id);
if (lvl > 1) {
ctx.fillStyle = lvl === 3 ? '#e65100' : '#f57f17';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(lvl === 3 ? '★★★' : '★★', cx + cardW - 4, cardY + 11);
}
}
ctx.fillStyle = '#263238';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(card.name, cx + cardW / 2, cardY + 54);
if (!this.isConveyor) {
ctx.fillStyle = affordable ? '#1b5e20' : '#b71c1c';
ctx.font = 'bold 11px sans-serif';
ctx.fillText(card.cost, cx + cardW / 2, cardY + 68);
} else {
ctx.fillStyle = '#1b5e20';
ctx.font = 'bold 10px sans-serif';
ctx.fillText('БЕСПЛАТНО', cx + cardW / 2, cardY + 68);
}
if (card.timer > 0) {
const cdRatio = card.timer / card.cooldown;
ctx.fillStyle = 'rgba(0, 0, 0, 0.55)';
ctx.beginPath();
ctx.rect(cx, cardY, cardW, cardH * cdRatio);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 13px sans-serif';
ctx.fillText(Math.ceil(card.timer), cx + cardW / 2, cardY + cardH / 2 + 5);
}
}
// 3. Shovel
const shovelX = cardStartX + this.cards.length * spacing + 12;
const isShovelSelected = this.selectedCard === 'shovel';
ctx.fillStyle = isShovelSelected ? '#ffcdd2' : '#efebe9';
ctx.beginPath();
ctx.roundRect(shovelX, cardY, 54, cardH, 6);
ctx.fill();
ctx.strokeStyle = isShovelSelected ? '#d32f2f' : '#8d6e63';
ctx.lineWidth = isShovelSelected ? 3 : 2;
ctx.stroke();
this.drawShovelGraphic(ctx, shovelX + 27, cardY + 36);
ctx.fillStyle = '#37474f';
ctx.font = 'bold 9px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Лопата', shovelX + 27, cardY + 66);
// 4. Coin Bag / Wallet in Top Right
ctx.fillStyle = '#ffe082';
ctx.beginPath();
ctx.roundRect(595, 20, 80, 26, 4);
ctx.fill();
ctx.strokeStyle = '#ff8f00';
ctx.lineWidth = 1.5;
ctx.stroke();
// Coin icon
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.arc(608, 33, 7, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#3e2723';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`💰 ${this.totalCoins}`, 645, 38);
// 5. Active Suns
for (const s of this.suns) {
const alpha = Math.min(1, s.life / 1.5);
this.drawSunGraphic(ctx, s.x, s.y, s.radius, s.pulse, alpha);
}
// 6. Active Coins
for (const c of this.coins) {
this.drawCoinGraphic(ctx, c.x, c.y, c.radius, c.type, c.pulse);
}
}
drawSunGraphic(ctx, x, y, radius, pulseAngle, alpha = 1) {
ctx.save();
ctx.globalAlpha = alpha;
ctx.translate(x, y);
const numRays = 12;
ctx.fillStyle = '#fbc02d';
for (let i = 0; i < numRays; i++) {
ctx.save();
ctx.rotate(pulseAngle + (i * Math.PI * 2) / numRays);
ctx.beginPath();
ctx.moveTo(-radius * 0.25, -radius * 0.8);
ctx.lineTo(0, -radius * 1.35);
ctx.lineTo(radius * 0.25, -radius * 0.8);
ctx.closePath();
ctx.fill();
ctx.restore();
}
const grad = ctx.createRadialGradient(0, 0, radius * 0.3, 0, 0, radius);
grad.addColorStop(0, '#fffde7');
grad.addColorStop(0.5, '#fff176');
grad.addColorStop(1, '#fbc02d');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
drawCoinGraphic(ctx, x, y, radius, type, pulse) {
ctx.save();
ctx.translate(x, y);
const spin = Math.sin(pulse * 3);
ctx.scale(spin, 1);
if (type === 'gold') {
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#b78103';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#b78103';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('$', 0, 4);
} else if (type === 'diamond') {
ctx.fillStyle = '#80deea';
ctx.beginPath();
ctx.moveTo(0, -radius);
ctx.lineTo(radius, 0);
ctx.lineTo(0, radius);
ctx.lineTo(-radius, 0);
ctx.closePath();
ctx.fill();
} else {
// Silver
ctx.fillStyle = '#e0e0e0';
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#757575';
ctx.lineWidth = 2;
ctx.stroke();
}
ctx.restore();
}
drawCardThumbnail(ctx, type, x, y) {
ctx.save();
ctx.translate(x, y);
ctx.scale(0.52, 0.52);
if (type === 'sunflower') {
ctx.fillStyle = '#fbc02d';
for (let i = 0; i < 8; i++) {
ctx.save();
ctx.rotate((i * Math.PI * 2) / 8);
ctx.beginPath();
ctx.ellipse(0, -18, 6, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
ctx.fillStyle = '#5d4037';
ctx.beginPath();
ctx.arc(0, 0, 14, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'peashooter' || type === 'snowpea' || type === 'repeater') {
const col = type === 'snowpea' ? '#29b6f6' : (type === 'repeater' ? '#2e7d32' : '#4caf50');
ctx.fillStyle = col;
ctx.beginPath();
ctx.arc(0, 0, 16, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(14, 0, 8, 10, 0, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'wallnut' || type === 'bowling_nut') {
ctx.fillStyle = '#8d6e63';
ctx.beginPath();
ctx.ellipse(0, 0, 16, 22, 0, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'cherrybomb' || type === 'bowling_cherry') {
ctx.fillStyle = '#d32f2f';
ctx.beginPath();
ctx.arc(-9, 4, 12, 0, Math.PI * 2);
ctx.arc(9, 4, 12, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'potatomine') {
ctx.fillStyle = '#a1887f';
ctx.beginPath();
ctx.ellipse(0, 6, 16, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#f44336';
ctx.beginPath();
ctx.arc(0, -6, 4, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'chomper') {
ctx.fillStyle = '#7b1fa2';
ctx.beginPath();
ctx.arc(0, 0, 18, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'jalapeno') {
ctx.fillStyle = '#d50000';
ctx.beginPath();
ctx.ellipse(0, 0, 10, 20, 0, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'sunshroom') {
ctx.fillStyle = '#fbc02d';
ctx.beginPath();
ctx.arc(0, 0, 14, Math.PI, 0);
ctx.fill();
} else if (type === 'fumeshroom') {
ctx.fillStyle = '#8e24aa';
ctx.beginPath();
ctx.arc(0, 0, 16, Math.PI, 0);
ctx.fill();
} else if (type === 'lilypad') {
ctx.fillStyle = '#2e7d32';
ctx.beginPath();
ctx.ellipse(0, 0, 20, 10, 0, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'gravebuster') {
ctx.strokeStyle = '#33691e';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(0, 0, 14, 0, Math.PI * 2);
ctx.stroke();
} else if (type === 'flowerpot') {
ctx.fillStyle = '#bf360c';
ctx.beginPath();
ctx.moveTo(-14, 4);
ctx.lineTo(14, 4);
ctx.lineTo(10, 16);
ctx.lineTo(-10, 16);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#d84315';
ctx.fillRect(-16, 0, 32, 5);
} else if (type === 'cabbagepult') {
ctx.fillStyle = '#8d6e63';
ctx.fillRect(-12, 6, 24, 6);
ctx.fillStyle = '#81c784';
ctx.beginPath();
ctx.arc(4, -4, 11, 0, Math.PI * 2);
ctx.fill();
} else if (type === 'melonpult') {
ctx.fillStyle = '#5d4037';
ctx.fillRect(-14, 6, 28, 8);
ctx.fillStyle = '#2e7d32';
ctx.beginPath();
ctx.ellipse(4, -4, 14, 11, 0, 0, Math.PI * 2);
ctx.fill();
} else if (type && type.startsWith('z_')) {
ctx.fillStyle = '#809973';
ctx.beginPath();
ctx.roundRect(-12, -14, 24, 26, [8, 8, 4, 4]);
ctx.fill();
ctx.fillStyle = '#fff9c4';
ctx.beginPath();
ctx.arc(-5, -4, 4, 0, Math.PI * 2);
ctx.arc(5, -4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(-5, -4, 1.5, 0, Math.PI * 2);
ctx.arc(5, -4, 1.5, 0, Math.PI * 2);
ctx.fill();
if (type === 'z_conehead') {
ctx.fillStyle = '#ff6d00';
ctx.beginPath();
ctx.moveTo(-10, -12); ctx.lineTo(10, -12); ctx.lineTo(0, -30); ctx.closePath();
ctx.fill();
} else if (type === 'z_buckethead') {
ctx.fillStyle = '#90a4ae';
ctx.beginPath();
ctx.roundRect(-12, -26, 24, 16, 4);
ctx.fill();
} else if (type === 'z_football') {
ctx.fillStyle = '#b71c1c';
ctx.beginPath();
ctx.arc(0, -10, 15, Math.PI, 0);
ctx.fill();
}
}
ctx.restore();
}
drawShovelGraphic(ctx, x, y) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(0.35);
ctx.strokeStyle = '#8d6e63';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(0, -16);
ctx.lineTo(0, 8);
ctx.stroke();
ctx.fillStyle = '#90a4ae';
ctx.beginPath();
ctx.moveTo(-9, 8);
ctx.lineTo(9, 8);
ctx.lineTo(6, 20);
ctx.lineTo(0, 24);
ctx.lineTo(-6, 20);
ctx.closePath();
ctx.fill();
ctx.restore();
}
};