class Item {
constructor(x, y, type, value, color, deadZombieRef = null, initialState = 'idle') {
this.pos = createVector(x, y);
this.type = type;
this.value = value;
this.color = color;
this.isGrave = (type === "grave"); // Flag for graves
this.isGun = (type === "gun"); // Flag for guns
if (this.isGrave) {
this.spawnTimer = 0;
this.zombieSpawned = false;
this.deadZombieRef = deadZombieRef; // To store reference to the dead zombie it spawned
this.state = initialState; // 'idle', 'spawning', 'consuming', 'empty', 'leaving'
this.consumeProgress = 0;
this.leaveTimer = 0; // Timer for grave to leave if zombie not killed
this.GRAVE_LEAVE_TIMEOUT = 1800; // Increased to 30 seconds (1800 frames at 60fps)
this.GRAVE_LEAVE_DURATION = 120; // 2 seconds to animate leaving
if (this.state === 'consuming') {
// If an auto-grave, it immediately starts the consuming animation
console.log("Auto-grave appeared and is consuming zombie.");
}
}
}
update() {
if (this.isGrave) {
if (this.state === 'idle') {
this.spawnTimer++;
if (this.spawnTimer > 180) { // Spawn zombie after 3 seconds
this.spawnZombie();
this.state = 'spawning';
this.leaveTimer = 0; // Reset leave timer when zombie is spawned
}
} else if (this.state === 'spawning') {
// Check if zombie died
if (this.deadZombieRef && this.deadZombieRef.state === 'dead') {
this.state = 'consuming';
this.leaveTimer = 0; // Reset leave timer, now consuming
} else {
// If zombie is still alive, grave starts its leaving timeout
this.leaveTimer++;
if (this.leaveTimer > this.GRAVE_LEAVE_TIMEOUT) {
console.log("Grave timed out, zombie not killed. Grave is leaving.");
// If the zombie is still moving, it should also leave
if (this.deadZombieRef && (this.deadZombieRef.state === 'moving' || this.deadZombieRef.state === 'idle')) { // Also check for idle state
this.deadZombieRef.state = 'leaving';
// If it's a red zombie and it didn't kill anyone, set its speed back to normal
if (this.deadZombieRef.isRed) {
this.deadZombieRef.speed = ZOMBIE_SPEED;
console.log("Red zombie leaving at normal speed due to grave timeout.");
}
}
this.deadZombieRef = null; // Grave no longer cares about this zombie
this.state = 'leaving';
this.leaveTimer = 0; // Reset for grave's leaving animation
}
}
} else if (this.state === 'consuming') {
if (this.deadZombieRef) {
// Animate dead zombie sinking into grave
let targetY = this.pos.y + ITEM_SIZE / 2;
this.deadZombieRef.pos.y = lerp(this.deadZombieRef.pos.y, targetY, 0.05);
if (this.deadZombieRef.pos.y >= targetY - 1) {
// Zombie fully consumed, mark it for removal
this.deadZombieRef.state = 'consumed';
this.deadZombieRef = null;
this.state = 'leaving'; // Grave leaves after consuming
this.leaveTimer = 0; // Reset for grave's leaving animation
console.log("Grave consumed zombie, grave is leaving.");
}
} else {
// Should not happen, but as a fallback, grave leaves
this.state = 'leaving';
this.leaveTimer = 0;
}
} else if (this.state === 'leaving') {
this.pos.y += PERSON_SPEED; // Move downwards like people/zombies
if (this.pos.y > height + ITEM_SIZE) {
this.state = 'empty'; // Mark for removal when completely off-screen
}
}
}
}
spawnZombie() {
if (!this.zombieSpawned) {
// Spawn a red zombie at the grave's position
let newZombie = new Zombie(this.pos.x, this.pos.y, true); // true for isRed
zombies.push(newZombie);
this.zombieSpawned = true;
this.deadZombieRef = newZombie; // Link grave to the zombie it spawned
console.log("Red zombie spawned from grave!");
// Unlock gun for people on first red zombie spawn
if (!people.some(p => p.hasGun)) {
people.forEach(p => p.hasGun = true);
console.log("People now have guns!");
}
}
}
display() {
push();
translate(this.pos.x, this.pos.y);
rectMode(CENTER);
if (this.isGrave) {
if (this.state === 'empty') {
// Grave disappears
} else { // Draw grave normally for all other states including 'leaving'
fill(this.color);
stroke(50);
strokeWeight(2);
rect(0, 0, ITEM_SIZE * 0.8, ITEM_SIZE * 0.5, 5); // Grave stone
fill(200);
rect(0, ITEM_SIZE * 0.1, ITEM_SIZE * 0.3, ITEM_SIZE * 0.1); // Cross horizontally
rect(0, ITEM_SIZE * 0.1, ITEM_SIZE * 0.1, ITEM_SIZE * 0.3); // Cross vertically
}
} else if (this.isGun) {
fill(this.color); // Grey for gun
stroke(50);
strokeWeight(2);
rect(-ITEM_SIZE / 4, 0, ITEM_SIZE / 2, ITEM_SIZE / 4); // Barrel
rect(ITEM_SIZE / 4, 0, ITEM_SIZE / 4, ITEM_SIZE / 2); // Handle
} else {
// Existing item display logic
fill(this.color);
stroke(50);
strokeWeight(2);
switch (this.type) {
case "chair":
rect(-ITEM_SIZE / 4, 0, ITEM_SIZE / 2, ITEM_SIZE); // Backrest
rect(ITEM_SIZE / 4, 0, ITEM_SIZE / 2, ITEM_SIZE / 2); // Seat
break;
case "table":
rect(0, 0, ITEM_SIZE, ITEM_SIZE / 2); // Tabletop
rectMode(CORNER);
rect(-ITEM_SIZE / 2 + 5, ITEM_SIZE / 4, 10, ITEM_SIZE / 4); // Leg 1
rect(ITEM_SIZE / 2 - 15, ITEM_SIZE / 4, 10, ITEM_SIZE / 4); // Leg 2
break;
case "tree":
fill('#8B4513'); // Trunk color
rect(0, ITEM_SIZE / 4, ITEM_SIZE / 4, ITEM_SIZE / 2); // Trunk
fill(this.color); // Leaves color
ellipse(0, -ITEM_SIZE / 4, ITEM_SIZE * 0.8, ITEM_SIZE * 0.8); // Leaves
break;
case "lamp":
ellipse(0, -ITEM_SIZE / 4, ITEM_SIZE / 2); // Shade
rect(0, 0, 5, ITEM_SIZE / 2); // Stand
rect(0, ITEM_SIZE / 4, ITEM_SIZE / 4, 5); // Base
break;
case "sofa":
rect(0, 0, ITEM_SIZE * 1.5, ITEM_SIZE / 2); // Seat
rect(-ITEM_SIZE * 0.7, -ITEM_SIZE / 4, ITEM_SIZE * 0.2, ITEM_SIZE / 2); // Armrest 1
rect(ITEM_SIZE * 0.7, -ITEM_SIZE / 4, ITEM_SIZE * 0.2, ITEM_SIZE / 2); // Armrest 2
rect(0, -ITEM_SIZE / 2, ITEM_SIZE * 1.5, ITEM_SIZE / 4); // Backrest
break;
case "bookshelf":
rect(0, 0, ITEM_SIZE, ITEM_SIZE); // Main frame
fill(this.color + 'A0'); // Slightly lighter color for shelves
rect(-ITEM_SIZE / 4, -ITEM_SIZE / 4, ITEM_SIZE / 2, 5); // Shelf 1
rect(-ITEM_SIZE / 4, ITEM_SIZE / 4, ITEM_SIZE / 2, 5); // Shelf 2
break; // Added break here
case "food":
ellipse(0, 0, ITEM_SIZE * 0.6, ITEM_SIZE * 0.6); // Generic food shape (e.g., a bun)
fill('#FFA07A'); // A light orange/brown for "crust"
ellipse(0, 0, ITEM_SIZE * 0.4, ITEM_SIZE * 0.4); // Inner detail
break;
case "car":
rect(0, 0, ITEM_SIZE, ITEM_SIZE / 2); // Car body
fill('#555'); // Wheel color
ellipse(-ITEM_SIZE / 4, ITEM_SIZE / 4, ITEM_SIZE / 4); // Front wheel
ellipse(ITEM_SIZE / 4, ITEM_SIZE / 4, ITEM_SIZE / 4); // Back wheel
rect(-ITEM_SIZE / 4, -ITEM_SIZE / 4, ITEM_SIZE / 2, ITEM_SIZE / 4); // Window/roof
break;
default:
rect(0, 0, ITEM_SIZE, ITEM_SIZE);
break;
}
}
pop();
}
}