function createAI(startX, startY, isTempAI = false) {
return {
x: startX,
y: startY,
tx: startX,
ty: startY,
targetCol: 0,
targetRow: 0,
targetType: 2,
timer: random(0, 20),
phaseOffset: random(TWO_PI),
isMovingToDrop: false,
isTemp: isTempAI,
mode: isTempAI ? 'DESTROY' : 'IDLE',
carDir: 1,
hasDestroyTarget: false,
swingTimer: 0,
update: function() {
let activeGrid = gameState === 'INSIDE_HOUSE' ? insideGrid : grid;
let activeFalling = gameState === 'INSIDE_HOUSE' ? insideFallingBlocks : fallingBlocks;
let moveSpeed = (globalTaskQueue.length > 0 || this.mode === 'DRIVING' || this.mode === 'DESTROY' || this.mode === 'LEAVING') ? 0.25 : 0.05;
this.x = lerp(this.x, this.tx, moveSpeed);
this.y = lerp(this.y, this.ty, moveSpeed);
// --- LUMBERJACK DESTROY MODE ---
if (this.mode === 'DESTROY') {
if (!this.hasDestroyTarget) {
if (globalDestroyQueue.length > 0) {
let t = globalDestroyQueue.pop();
this.targetCol = t.c;
this.targetRow = t.r;
// Safe array check to prevent out-of-bounds crash
if (activeGrid[this.targetCol] && activeGrid[this.targetCol][this.targetRow] !== undefined) {
this.tx = this.targetCol * blockSize + blockSize / 2;
this.ty = this.targetRow * blockSize + blockSize / 2;
this.hasDestroyTarget = true;
}
} else {
// Queue is empty. Optimize checking to prevent massive CPU lag!
let hasBlocks = false;
for (let c = 0; c < cols; c++) {
if (!activeGrid[c]) continue;
for (let r = 0; r < rows; r++) {
if (activeGrid[c][r] > 0) { hasBlocks = true; break; }
}
if (hasBlocks) break;
}
if (hasBlocks && activeFalling.length === 0) {
// Repopulate queue safely
let remaining = [];
for(let c=0; c<cols; c++) {
if(!activeGrid[c]) continue;
for(let r=0; r<rows; r++) {
if(activeGrid[c][r] > 0) remaining.push({c, r});
}
}
globalDestroyQueue = shuffle(remaining);
} else if (!hasBlocks && activeFalling.length === 0) {
// EVERYTHING IS DESTROYED! Time to leave or idle.
this.mode = this.isTemp ? 'LEAVING' : 'IDLE';
if (!this.isTemp) {
this.ty = 120;
currentTaskName = "Idle";
} else {
this.tx = random(-200, width + 200);
this.ty = -200; // Fly away into the sky!
}
}
}
} else {
// We have a target, fly to it!
if (dist(this.x, this.y, this.tx, this.ty) < 15) {
this.swingTimer = 10;
// Add safe navigation to prevent crash
if (activeGrid[this.targetCol] && activeGrid[this.targetCol][this.targetRow] > 0) {
activeGrid[this.targetCol][this.targetRow] = 0; // Destroy block
playSFX('break');
}
this.hasDestroyTarget = false;
}
// Failsafe: if block is already gone or grid changed
if (activeGrid[this.targetCol] && activeGrid[this.targetCol][this.targetRow] <= 0) {
this.hasDestroyTarget = false;
} else if (!activeGrid[this.targetCol]) {
this.hasDestroyTarget = false;
}
}
}
// --- LEAVING MODE (For Temp AIs) ---
else if (this.mode === 'LEAVING') {
// Logic is handled in drawGame() where they are removed safely
}
// --- NORMAL BEHAVIORS ---
else {
if (this.isMovingToDrop && abs(this.x - this.tx) < 5) {
dropBlock(this.targetCol, this.targetType);
this.isMovingToDrop = false;
this.timer = 0;
}
let dropDelay = 3;
if (this.mode === 'BUILD') {
if (globalTaskQueue.length > 150) dropDelay = 0;
else if (globalTaskQueue.length > 40) dropDelay = 1;
}
if (!this.isMovingToDrop && globalReceivingData === 0 && this.mode === 'BUILD') {
this.timer++;
if (globalTaskQueue.length > 0) {
if (this.timer > dropDelay) {
let task = globalTaskQueue.shift();
this.targetCol = task.col;
// Bounds clamp just in case
if (this.targetCol >= cols) this.targetCol = cols - 1;
if (this.targetCol < 0) this.targetCol = 0;
this.targetType = task.type;
this.tx = this.targetCol * blockSize + blockSize / 2;
this.isMovingToDrop = true;
}
} else {
this.mode = 'IDLE';
currentTaskName = "Idle";
}
}
if (this.mode === 'IDLE') {
this.ty = 120;
this.timer++;
if (this.timer > 80 && globalTaskQueue.length === 0) {
this.targetCol = floor(random(cols));
this.targetType = 2;
this.tx = this.targetCol * blockSize + blockSize / 2;
this.isMovingToDrop = true;
this.timer = random(0, 20);
}
}
}
},
draw: function() {
push();
translate(this.x, this.y + sin(frameCount * 0.1 + this.phaseOffset) * 8);
// --- AXE RENDERING ---
if (this.mode === 'DESTROY' || this.swingTimer > 0) {
push();
translate(20, 0); // Put in right hand
if (this.swingTimer > 0) {
rotate(PI / 3); // Swing animation
this.swingTimer--;
} else {
rotate(-PI / 6); // Hold ready
}
fill(100, 50, 0); rect(-2, -15, 4, 30, 2); // Handle
fill(180); arc(5, -10, 16, 22, -PI/2, PI/2); // Blade
pop();
}
if (globalReceivingData > 0 && this.mode !== 'DESTROY') {
let alpha = map(globalReceivingData, 0, 30, 255, 0);
stroke(0, 255, 255, alpha);
strokeWeight(3); noFill();
arc(0, -60, 20, 20, PI + QUARTER_PI, TWO_PI - QUARTER_PI);
arc(0, -50, 40, 40, PI + QUARTER_PI, TWO_PI - QUARTER_PI);
}
// Top Hat
fill(30); noStroke(); rectMode(CENTER);
rect(0, -25, 60, 6, 3);
rect(0, -45, 36, 40, 2);
fill(220, 50, 50); rect(0, -30, 36, 8);
// Computer Head
fill(230); stroke(100); strokeWeight(3);
rect(0, 0, 46, 46, 8);
// Eyes
noStroke();
let isBusy = globalTaskQueue.length > 0 || this.mode === 'DRIVING' || this.mode === 'DESTROY';
fill(this.mode === 'DESTROY' ? color(255, 50, 50) : (isBusy ? color(0, 200, 255) : color(50, 255, 100)));
// Angry eyes if destroying
if (this.mode === 'DESTROY') {
push();
translate(-12, -5); rotate(PI/8); rect(0, 0, 12, 4);
pop();
push();
translate(12, -5); rotate(-PI/8); rect(0, 0, 12, 4);
pop();
} else {
circle(-12, -5, 10); circle(12, -5, 10);
}
// Mouth
stroke(100); strokeWeight(3); noFill();
if (this.mode === 'DESTROY') arc(0, 10, 16, 10, PI, TWO_PI); // Angry mouth
else if (isBusy) circle(0, 10, 8);
else arc(0, 8, 16, 12, 0, PI);
pop();
}
};
}