draw() {
if (this.hp <= 0) return;
push();
let bodyOffset = -this.h * 0.15;
let cx = this.x + this.w/2;
let cy = this.y + this.h/2;
let gunDist = this.w * 0.6 + 15;
let shieldDist = this.w * 0.4 + 10;
let gunHX = this.aimVec.x * gunDist;
let gunHY = bodyOffset + this.aimVec.y * gunDist;
let shieldX = -this.aimVec.x * shieldDist;
let shieldY = bodyOffset - this.aimVec.y * shieldDist + 6;
if (this.hp > 0 && !this.blockingFrames) {
let gunX = cx + gunHX;
let gunY = cy + gunHY;
let endX = gunX + this.aimVec.x * 1200;
let endY = gunY + this.aimVec.y * 1200;
stroke(this.color.levels[0], this.color.levels[1], this.color.levels[2], 30);
strokeWeight(2);
line(gunX, gunY, endX, endY);
}
if (this.orbitalDamage > 0) {
for(let i=0; i<2; i++) {
let ox = cx + cos(this.orbitalAngle + i*PI) * (this.w + 15);
let oy = cy + bodyOffset + sin(this.orbitalAngle + i*PI) * (this.h + 15);
fill(this.color); noStroke();
circle(ox, oy, 12);
fill(255); circle(ox, oy, 4);
}
}
let vMag = dist(0, 0, this.vx, this.vy);
let stretchAngle = atan2(this.vy, this.vx);
let stretchFactor = constrain(vMag * 0.04, 0, 0.4);
translate(cx, cy);
// Dynamic Leg Placement
let walkPhase = frameCount * 0.4;
let lift1 = (this.onGround && abs(this.vx) > 0.5) ? max(0, sin(walkPhase)) * (this.h*0.3) : 0;
let lift2 = (this.onGround && abs(this.vx) > 0.5) ? max(0, -sin(walkPhase)) * (this.h*0.3) : 0;
let footYBase = this.h * 0.4; // Places feet slightly above the bottom of the physics box
let foot1X = -this.w * 0.25;
let foot1Y = footYBase - lift1;
let foot2X = this.w * 0.25;
let foot2Y = footYBase - lift2;
let cR = red(this.color) * 0.7;
let cG = green(this.color) * 0.7;
let cB = blue(this.color) * 0.7;
stroke(cR, cG, cB, this.alpha);
strokeWeight(max(4, this.w * 0.2));
strokeCap(ROUND);
line(0, bodyOffset + this.h*0.1, foot1X, foot1Y);
line(0, bodyOffset + this.h*0.1, foot2X, foot2Y);
noStroke();
fill(cR, cG, cB, this.alpha);
circle(foot1X, foot1Y, max(8, this.w*0.3));
circle(foot2X, foot2Y, max(8, this.w*0.3));
stroke(this.color.levels[0]*0.8, this.color.levels[1]*0.8, this.color.levels[2]*0.8, this.alpha);
strokeWeight(max(3, this.w * 0.15));
line(0, bodyOffset, shieldX, shieldY);
line(0, bodyOffset, gunHX, gunHY);
if (this.blockingFrames > 0) {
noFill();
stroke(255);
strokeWeight(4);
let progress = 1 - (this.blockingFrames / this.blockDuration);
let shieldSize = this.w * 2.5 + (progress * 40);
circle(0, bodyOffset, shieldSize);
}
push();
translate(0, bodyOffset);
rotate(stretchAngle);
this.color.setAlpha(this.alpha);
fill(this.color);
noStroke();
ellipse(0, 0, this.w * (1.1 + stretchFactor), this.h * (1.1 - stretchFactor * 0.5));
this.color.setAlpha(255);
rotate(-stretchAngle);
let eyeOffsetX = this.aimVec.x * (this.w * 0.2);
let eyeOffsetY = this.aimVec.y * (this.h * 0.2);
let eyeW = this.w * 0.35;
fill(255, this.alpha);
circle(eyeOffsetX - this.w*0.2, eyeOffsetY, eyeW);
circle(eyeOffsetX + this.w*0.2, eyeOffsetY, eyeW);
fill(20, 20, 24, this.alpha);
circle(eyeOffsetX - this.w*0.2 + this.aimVec.x * 2, eyeOffsetY + this.aimVec.y * 2, eyeW * 0.4);
circle(eyeOffsetX + this.w*0.2 + this.aimVec.x * 2, eyeOffsetY + this.aimVec.y * 2, eyeW * 0.4);
pop();
push();
translate(shieldX, shieldY);
fill(40, 40, 45, this.alpha);
noStroke();
circle(0, 0, 18);
if (this.blockTimer > 0) {
fill(255, 100);
let p = 1 - (this.blockTimer / this.maxBlockCooldown);
arc(0, 0, 18, 18, -HALF_PI, -HALF_PI + TWO_PI * p, PIE);
} else {
fill(255, this.alpha);
circle(0, 0, 18);
}
if (this.blockingFrames > 0) {
fill(255, 150);
circle(0, 0, 28);
}
pop();
push();
let aimAngle = atan2(this.aimVec.y, this.aimVec.x);
translate(gunHX, gunHY);
rotate(aimAngle);
fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.alpha);
noStroke();
circle(0, 0, 14);
if (abs(aimAngle) > HALF_PI) {
scale(1, -1);
}
this.color.setAlpha(this.alpha);
fill(40, 40, 45, this.alpha);
stroke(255, 255, 255, this.alpha);
strokeWeight(2);
let gunLen = 22 + min(this.maxAmmo * 2, 35);
let t = 8;
let h = 16;
beginShape();
vertex(-t/2, 0);
vertex(t/2, 0);
vertex(t/2, -h + t);
vertex(gunLen, -h + t);
vertex(gunLen, -h);
vertex(-t/2, -h);
endShape(CLOSE);
noStroke();
let ammoYOffset = -h - 6;
let barrelCenter = (gunLen - t/2) / 2;
let space = min((gunLen + t/2) / this.maxAmmo, 7);
let startX = barrelCenter - ((this.maxAmmo - 1) * space) / 2;
if (this.reloadTimer > 0) {
let progress = 1 - (this.reloadTimer / this.reloadTime);
fill(255, 255, 255, 220);
rectMode(CORNER);
let barW = (this.maxAmmo - 1) * space + 6;
rect(startX - 3, ammoYOffset - 3, barW * progress, 6, 2);
} else {
for(let i=0; i < this.maxAmmo; i++) {
if (i < this.ammo) fill(255, 210, 50, this.alpha);
else fill(80, this.alpha);
circle(startX + i * space, ammoYOffset, 4);
}
}
this.color.setAlpha(255);
pop();
pop();
noStroke();
fill(255, 0, 0);
rectMode(CORNER);
rect(this.x - 5, this.y - this.h*0.5 - 25, this.w + 10, 6, 2);
fill(this.poisonTimer > 0 ? color(100, 255, 100) : color(0, 255, 0));
let hpW = map(max(0, this.hp), 0, this.maxHp, 0, this.w + 10);
rect(this.x - 5, this.y - this.h*0.5 - 25, hpW, 6, 2);
}