Code edit (1 edits merged)
Please save this source code
User prompt
Update with: self.update = function() { if (!self.visible) return; if (self.extending || self.retracting) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.speed) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; // Chain segment logic remains the same... } else { if (self.extending) { self.extending = false; // Pause briefly at extension LK.setTimeout(function() { self.retract(); }, 30); } else if (self.retracting) { self.deactivate(); } } } };
User prompt
Update with: case RETRACTING_LEFT: case RETRACTING_RIGHT: if (!self.activeFist || !self.activeFist.visible) { setBodyState('normal'); self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; self.activeFist = null; // Add this line to clear the reference } break;
User prompt
Update with: var BossFist = Container.expand(function() { var self = Container.call(this); // Create both fist sprites and manage visibility var leftFist = self.attachAsset('boss1leftfist', { anchorX: 0.5, anchorY: 0.5, visible: false }); var rightFist = self.attachAsset('boss1rightfist', { anchorX: 0.5, anchorY: 0.5, visible: false }); self.activate = function(x, y, isLeft) { self.x = x; self.y = y; self.startX = x; self.startY = y; self.isLeft = isLeft; self.visible = true; self.extending = false; self.retracting = false; // Show correct fist, hide other leftFist.visible = isLeft; rightFist.visible = !isLeft; }; self.deactivate = function() { while (self.chainSegments.length > 0) { chainSegmentPool.recycle(self.chainSegments.pop()); } self.visible = false; self.extending = false; self.retracting = false; leftFist.visible = false; rightFist.visible = false; };
User prompt
Replace bossfist class with: var BossFist = Container.expand(function() { var self = Container.call(this); var fist = self.attachAsset('boss1rightfist', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function(x, y, isLeft) { self.x = x; self.y = y; self.startX = x; self.startY = y; self.isLeft = isLeft; self.visible = true; self.extending = false; self.retracting = false; // Important: Make fist visible and set correct asset fist.setAsset(isLeft ? 'boss1leftfist' : 'boss1rightfist'); fist.visible = true; }; self.extend = function(targetX, targetY) { self.targetX = targetX; self.targetY = targetY; self.extending = true; self.retracting = false; }; self.update = function() { if (!self.visible) return; if (self.extending || self.retracting) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.speed) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; // Update chain segments var segmentSpacing = 40; var numSegments = Math.floor(dist / segmentSpacing); // Clean up old segments while (self.chainSegments.length > 0) { chainSegmentPool.recycle(self.chainSegments.pop()); } // Create new segments for (var i = 1; i <= numSegments; i++) { var t = i / numSegments; var segX = self.startX + (self.x - self.startX) * t; var segY = self.startY + (self.y - self.startY) * t; var segment = chainSegmentPool.spawn(segX, segY); if (segment) { var angle = Math.atan2(dy, dx); segment.rotation = angle; self.chainSegments.push(segment); } } } else { if (self.extending) { self.extending = false; } else if (self.retracting) { self.deactivate(); } } } }; self.retract = function() { self.targetX = self.startX; self.targetY = self.startY; self.extending = false; self.retracting = true; }; self.deactivate = function() { while (self.chainSegments.length > 0) { chainSegmentPool.recycle(self.chainSegments.pop()); } self.visible = false; self.extending = false; self.retracting = false; fist.visible = false; }; // Initialize properties self.chainSegments = []; self.speed = 20; return self; });
Code edit (1 edits merged)
Please save this source code
User prompt
Update with: case FIST_LEFT: case FIST_RIGHT: if (!self.activeFist || !self.activeFist.visible) { // If fist is somehow missing, recover setBodyState('normal'); self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; break; } if (self.activeFist && !self.activeFist.extending) { self.state = self.state === FIST_LEFT ? RETRACTING_LEFT : RETRACTING_RIGHT; self.stateDuration = 0; if (self.activeFist) { self.activeFist.retract(); } } break; case RETRACTING_LEFT: case RETRACTING_RIGHT: if (!self.activeFist || !self.activeFist.visible) { setBodyState('normal'); self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; } break;
Code edit (6 edits merged)
Please save this source code
User prompt
Update with: case WALKING: setBodyState('normal'); self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; self.y = self.bobBaseY + self.bobOffset; self.attackTimer--; if (self.attackTimer <= 0) { if (!self.hasCompletedFirstAttack) { // First attack is always arrows self.state = ARROW_ATTACK; self.hasCompletedFirstAttack = true; } else { // After first attack, randomize with bias towards arrows var rand = Math.random(); if (rand < 0.6) { // 60% chance for arrows self.state = ARROW_ATTACK; } else { // 40% chance split between left/right fist self.state = Math.random() < 0.5 ? CHARGING_LEFT : CHARGING_RIGHT; } } self.stateDuration = 0; } break;
User prompt
Update with: case ARROW_ATTACK: // Keep bobbing motion during arrow attack self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; self.y = self.bobBaseY + self.bobOffset; if (self.stateDuration === 1) { var centerY = self.y + 200; enemyArrowPool.spawn(self.x, centerY).activate(self.x, centerY, dragon.x, dragon.y); enemyArrowPool.spawn(self.x - 100, centerY).activate(self.x - 100, centerY, dragon.x - 300, dragon.y); enemyArrowPool.spawn(self.x + 100, centerY).activate(self.x + 100, centerY, dragon.x + 300, dragon.y); } else if (self.stateDuration > 60) { self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; } break;
Code edit (4 edits merged)
Please save this source code
User prompt
Update with: case WALKING: setBodyState('normal'); self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; self.y = self.bobBaseY + self.bobOffset; self.attackTimer--; if (self.attackTimer <= 0) { if (self.stateDuration < 600) { self.state = ARROW_ATTACK; } else { self.state = Math.random() < 0.5 ? CHARGING_LEFT : CHARGING_RIGHT; } self.stateDuration = 0; } break;
User prompt
Update with: self.health = 100; self.state = ENTERING; self.stateDuration = 0; self.bobSpeed = 0.02; self.bobOffset = 0; self.bobBaseY = 2732/2; // Add base Y position self.activeFist = null; self.attackTimer = 0; self.attackDelay = 120;
User prompt
Update with: case WALKING: setBodyState('normal'); // Calculate bob offset self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; // Apply bob offset relative to base position self.y = 2732/2 + self.bobOffset; self.attackTimer--; if (self.attackTimer <= 0) { if (self.stateDuration < 600) { self.state = ARROW_ATTACK; } else { self.state = Math.random() < 0.5 ? CHARGING_LEFT : CHARGING_RIGHT; } self.stateDuration = 0; } break;
Code edit (1 edits merged)
Please save this source code
User prompt
update boss as needed with: var Boss = Container.expand(function() { var self = Container.call(this); // States var ENTERING = 'entering'; var WALKING = 'walking'; var ARROW_ATTACK = 'arrow_attack'; var CHARGING_LEFT = 'charging_left'; var CHARGING_RIGHT = 'charging_right'; var FIST_LEFT = 'fist_left'; var FIST_RIGHT = 'fist_right'; var RETRACTING_LEFT = 'retracting_left'; var RETRACTING_RIGHT = 'retracting_right'; var DYING = 'dying'; // Main sprite var body = self.attachAsset('boss1', { anchorX: 0.5, anchorY: 0, scale: 2 }); // Properties self.health = 100; self.state = ENTERING; self.stateDuration = 0; self.bobOffset = 0; self.bobSpeed = 0.02; self.lastBobOffset = 0; self.activeFist = null; self.attackTimer = 0; self.attackDelay = 120; // Position at creation self.x = 2048 / 2; self.y = 2732; // Start at bottom of screen // Update logic self.update = function() { self.stateDuration++; self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; switch(self.state) { case ENTERING: self.y -= 2; if (self.y <= 2732 / 2) { self.state = WALKING; self.attackTimer = self.attackDelay; } break; // ... rest of the states remain the same ... } self.lastBobOffset = self.bobOffset; }; self.damage = function(amount) { self.health -= amount; bossHealthBar.updateHealth(self.health / 100); body.tint = 0xFF0000; LK.setTimeout(function() { body.tint = 0xFFFFFF; }, 5); if (self.health <= 0 && self.state !== DYING) { self.state = DYING; self.stateDuration = 0; game.shake(20, 1000); } }; return self; });
Code edit (22 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: boss is null' in or related to this line: 'boss.update();' Line Number: 2110
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
add more variance
User prompt
add a bit of variance to defeat particle scale
User prompt
reduce their velocity a little bit
User prompt
increas the velocity of defeat particles
User prompt
increase the life time of defeat particles
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var Boss = Container.expand(function () { var self = Container.call(this); // States var ENTERING = 'entering'; var WALKING = 'walking'; var ARROW_ATTACK = 'arrow_attack'; var CHARGING_LEFT = 'charging_left'; var CHARGING_RIGHT = 'charging_right'; var FIST_LEFT = 'fist_left'; var FIST_RIGHT = 'fist_right'; var RETRACTING_LEFT = 'retracting_left'; var RETRACTING_RIGHT = 'retracting_right'; var DYING = 'dying'; // All boss sprites var normalBody = self.attachAsset('boss1', { anchorX: 0.5, anchorY: 0, alpha: 1 }); var leftBody = self.attachAsset('boss1left', { anchorX: 0.5, anchorY: 0, alpha: 0 }); var rightBody = self.attachAsset('boss1right', { anchorX: 0.5, anchorY: 0, alpha: 0 }); var leftNoFist = self.attachAsset('boss1leftnofist', { anchorX: 0.5, anchorY: 0, alpha: 0 }); var rightNoFist = self.attachAsset('boss1rightnofist', { anchorX: 0.5, anchorY: 0, alpha: 0 }); // Properties self.health = 100; self.state = ENTERING; self.stateDuration = 0; self.bobSpeed = 0.02; self.bobOffset = 0; self.bobBaseY = 2732 / 2; // Add base Y position self.activeFist = null; self.attackTimer = 0; self.attackDelay = 120; // Initial position self.x = 2048 / 2; self.y = 2732; function setBodyState(state) { normalBody.alpha = 0; leftBody.alpha = 0; rightBody.alpha = 0; leftNoFist.alpha = 0; rightNoFist.alpha = 0; switch (state) { case 'normal': normalBody.alpha = 1; break; case 'left': leftBody.alpha = 1; break; case 'right': rightBody.alpha = 1; break; case 'leftNoFist': leftNoFist.alpha = 1; break; case 'rightNoFist': rightNoFist.alpha = 1; break; } } function flashDamage() { [normalBody, leftBody, rightBody, leftNoFist, rightNoFist].forEach(function (sprite) { if (sprite.alpha > 0) { sprite.tint = 0xFF0000; LK.setTimeout(function () { sprite.tint = 0xFFFFFF; }, 5); } }); } self.update = function () { self.stateDuration++; self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; switch (self.state) { case ENTERING: setBodyState('normal'); self.y -= 2; if (self.y <= 2732 / 2) { self.state = WALKING; self.attackTimer = self.attackDelay; } break; case WALKING: setBodyState('normal'); // Calculate bob offset self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 15; // Apply bob offset relative to base position self.y = 2732 / 2 + self.bobOffset; self.attackTimer--; if (self.attackTimer <= 0) { if (self.stateDuration < 600) { self.state = ARROW_ATTACK; } else { self.state = Math.random() < 0.5 ? CHARGING_LEFT : CHARGING_RIGHT; } self.stateDuration = 0; } break; case ARROW_ATTACK: if (self.stateDuration === 1) { var centerY = self.y + 200; enemyArrowPool.spawn(self.x, centerY).activate(self.x, centerY, dragon.x, dragon.y); enemyArrowPool.spawn(self.x - 100, centerY).activate(self.x - 100, centerY, dragon.x - 300, dragon.y); enemyArrowPool.spawn(self.x + 100, centerY).activate(self.x + 100, centerY, dragon.x + 300, dragon.y); } else if (self.stateDuration > 60) { self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; } break; case CHARGING_LEFT: case CHARGING_RIGHT: var isLeft = self.state === CHARGING_LEFT; setBodyState(isLeft ? 'left' : 'right'); if (self.stateDuration < 60) { var targetX = self.x + (isLeft ? -200 : 200); var targetY = self.y + 300; if (Math.random() < 0.3) { var angle = Math.random() * Math.PI * 2; var radius = Math.random() * 200 + 100; var px = targetX + Math.cos(angle) * radius; var py = targetY + Math.sin(angle) * radius; var particle = particlePool.spawn(px, py); if (particle) { var dx = targetX - px; var dy = targetY - py; var dist = Math.sqrt(dx * dx + dy * dy); particle.vx = dx / dist * 10; particle.vy = dy / dist * 10; } } } else { self.state = isLeft ? FIST_LEFT : FIST_RIGHT; self.stateDuration = 0; setBodyState(isLeft ? 'leftNoFist' : 'rightNoFist'); self.activeFist = bossFistPool.spawn(self.x + (isLeft ? -200 : 200), self.y + 300, isLeft); if (self.activeFist) { self.activeFist.extend(dragon.x, dragon.y); } } break; case FIST_LEFT: case FIST_RIGHT: if (self.activeFist && !self.activeFist.extending) { self.state = self.state === FIST_LEFT ? RETRACTING_LEFT : RETRACTING_RIGHT; self.stateDuration = 0; if (self.activeFist) { self.activeFist.retract(); } } break; case RETRACTING_LEFT: case RETRACTING_RIGHT: if (!self.activeFist || !self.activeFist.visible) { setBodyState('normal'); self.state = WALKING; self.attackTimer = self.attackDelay; self.stateDuration = 0; } break; case DYING: self.y += 1; self.x += Math.sin(self.stateDuration * 0.2) * 5; if (self.stateDuration % 10 === 0) { for (var i = 0; i < 3; i++) { defeatParticlePool.spawn(self.x + (Math.random() * 400 - 200), self.y + Math.random() * 400, 0xff0000); } } if (self.y > 2732 + 500) { game.removeChild(self); bossHealthBar.visible = false; } break; } self.lastBobOffset = self.bobOffset; }; self.damage = function (amount) { self.health -= amount; bossHealthBar.updateHealth(self.health / 100); flashDamage(); if (self.health <= 0 && self.state !== DYING) { self.state = DYING; self.stateDuration = 0; game.shake(20, 1000); } }; return self; }); var BossFist = Container.expand(function () { var self = Container.call(this); var fist = self.attachAsset('boss1rightfist', { anchorX: 0.5, anchorY: 0.5 }); self.chainSegments = []; self.isLeft = false; self.targetX = 0; self.targetY = 0; self.startX = 0; self.startY = 0; self.extending = false; self.retracting = false; self.speed = 20; self.activate = function (x, y, isLeft) { self.x = x; self.y = y; self.startX = x; self.startY = y; self.isLeft = isLeft; self.visible = true; // Switch sprite based on direction fist.setAsset(isLeft ? 'boss1leftfist' : 'boss1rightfist'); }; self.extend = function (targetX, targetY) { self.targetX = targetX; self.targetY = targetY; self.extending = true; self.retracting = false; }; self.retract = function () { self.targetX = self.startX; self.targetY = self.startY; self.extending = false; self.retracting = true; }; self.update = function () { if (!self.visible) { return; } if (self.extending || self.retracting) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.speed) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; // Update chain segments var segmentSpacing = 40; var numSegments = Math.floor(dist / segmentSpacing); // Clean up old segments while (self.chainSegments.length > 0) { chainSegmentPool.recycle(self.chainSegments.pop()); } // Create new segments for (var i = 1; i <= numSegments; i++) { var t = i / numSegments; var segX = self.startX + (self.x - self.startX) * t; var segY = self.startY + (self.y - self.startY) * t; // Add slight curve var perpX = -(self.targetY - self.startY); var perpY = self.targetX - self.startX; var len = Math.sqrt(perpX * perpX + perpY * perpY); if (len > 0) { var curveAmt = Math.sin(t * Math.PI) * 50; segX += perpX / len * curveAmt; segY += perpY / len * curveAmt; } var segment = chainSegmentPool.spawn(segX, segY); if (segment) { var angle = Math.atan2(dy, dx); segment.rotation = angle; self.chainSegments.push(segment); } } } else { if (self.retracting) { self.deactivate(); } } } }; self.deactivate = function () { while (self.chainSegments.length > 0) { chainSegmentPool.recycle(self.chainSegments.pop()); } self.visible = false; self.extending = false; self.retracting = false; }; return self; }); var BossHealthBar = Container.expand(function () { var self = Container.call(this); var emptyMeter = self.attachAsset('meter', { anchorX: 0.5, anchorY: 1.0, tint: 0x333333 // Dark gray for empty }); var healthMeter = self.attachAsset('meter', { anchorX: 0.5, anchorY: 1.0, tint: 0xff0000 // Red for health }); self.updateHealth = function (percent) { healthMeter.scaleX = percent; }; return self; }); var ChainSegment = Container.expand(function () { var self = Container.call(this); var link = self.attachAsset('fistlink', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function (x, y, rotation) { self.x = x; self.y = y; self.rotation = rotation; self.visible = true; }; self.deactivate = function () { self.visible = false; }; return self; }); var Cloud = Container.expand(function () { var self = Container.call(this); var cloud = self.attachAsset('cloudShape', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function (x, y, scale, alpha) { self.x = x; self.y = y; self.visible = true; self.speedY = 1 + Math.random() * 2; self.speedX = (Math.random() - 0.5) * 1.5; self.targetY = self.horizonY; cloud.scaleX = scale; cloud.scaleY = scale * 0.8; cloud.alpha = alpha || 0.8; }; self.deactivate = function () { self.visible = false; }; return self; }); var DefeatParticle = Container.expand(function () { var self = Container.call(this); var particle = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.15, scaleY: 0.15 }); self.activate = function (x, y, tint) { self.x = x + (Math.random() * 40 - 20); // Wider initial spread self.y = y + (Math.random() * 40 - 20); // Wider initial spread self.visible = true; self.age = 0; self.lifespan = 225; // Tripled lifespan for longer visual impact // More dramatic explosion velocities var angle = Math.PI * 2 * Math.random(); var speed = 15 + Math.random() * 25; // Adjusted speed for more variance self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; self.rotation = Math.random() * Math.PI * 2; particle.alpha = 1; // Larger, more varied particle sizes var scaleVariance = 0.3 + Math.random() * 0.6; // Increased variance for scale particle.scaleX = scaleVariance; particle.scaleY = scaleVariance; // Apply tint if provided if (tint !== undefined) { particle.tint = tint; } else { particle.tint = 0xffffff; // Default white } }; self.update = function () { // Check bounds first if (self.y < -50 || self.y > 2732 || self.x < -50 || self.x > 2048) { defeatParticlePool.recycle(self); return; } self.x += self.vx; self.y += self.vy; self.age++; // Keep strong deceleration self.vx *= 0.87; self.vy *= 0.87; var lifePercent = self.age / self.lifespan; // Fade out curve particle.alpha = Math.max(0, 1 - lifePercent * 1.7); // More dramatic shrink effect as particles fade var scale = (0.5 - 0.3 * lifePercent) * (1 - lifePercent * 0.5); particle.scaleX = scale; particle.scaleY = scale; if (lifePercent >= 1) { defeatParticlePool.recycle(self); } }; self.deactivate = function () { self.visible = false; // Reset tint when deactivated particle.tint = 0xffffff; }; return self; }); var DragonHead = Container.expand(function () { var self = Container.call(this); // Dragon sprites var body = self.attachAsset('dragonBody', { anchorX: 0.5, anchorY: 0.7, scaleX: 2, scaleY: 2 }); var head = self.attachAsset('dragonHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); var headOpen = self.attachAsset('dragonHeadOpen', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, alpha: 0 }); var lookLeft = self.attachAsset('dragonLook', { anchorX: 0.5, anchorY: 0.5, scaleX: -2, // Mirror for left direction scaleY: 2, alpha: 0 }); var lookRight = self.attachAsset('dragonLook', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, alpha: 0 }); var lookLeftOpen = self.attachAsset('dragonLookOpen', { anchorX: 0.5, anchorY: 0.5, scaleX: -2, // Mirror for left direction scaleY: 2, alpha: 0 }); var lookRightOpen = self.attachAsset('dragonLookOpen', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, alpha: 0 }); self.currentScale = 2; // Starting scale var horizontalSpeed = 0; // Position tracking variables var targetX = 2048 / 2; var targetY = 2732 * 0.2; var smoothingFactor = 0.12; var prevX = null; var prevY = null; // Rotation variables var targetTilt = 0; var tiltSmoothingFactor = 0.11; var tiltScaleFactor = 0.09; // Scale tracking var scaleHistory = new Array(5).fill(2); // Start with default scale of 2 var scaleIndex = 0; self.update = function () { // Position tracking if (facekit.noseTip) { targetX = facekit.noseTip.x; targetY = facekit.noseTip.y - 400; // Initialize previous positions if not set if (prevX === null) { prevX = targetX; prevY = targetY; } // Weighted average between previous and target position var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor; var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor; self.x = newX; self.y = newY; if (prevX !== null) { horizontalSpeed = Math.abs(self.x - prevX); } self.currentShakeSpeed = horizontalSpeed; // Update previous positions prevX = newX; prevY = newY; } // Rotation tracking - CORRECTED VERSION if (facekit.leftEye && facekit.rightEye) { targetTilt = calculateFaceTilt() * tiltScaleFactor; // Limit rotation to ±15 degrees - DON'T convert to radians here targetTilt = Math.max(-15, Math.min(15, targetTilt)); self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor; } if (facekit.leftEye && facekit.rightEye && facekit.noseTip) { // Calculate face direction by comparing horizontal positions var faceCenter = (facekit.leftEye.x + facekit.rightEye.x) / 2; var noseDiff = facekit.noseTip.x - faceCenter; // Calculate eye alignment for head rotation var eyeYDiff = facekit.rightEye.y - facekit.leftEye.y; // Reset all direction indicators lookLeft.alpha = 0; lookRight.alpha = 0; lookLeftOpen.alpha = 0; lookRightOpen.alpha = 0; // Combined detection logic var showingTurnedHead = false; if (noseDiff < -120 && Math.abs(eyeYDiff) < 30 || eyeYDiff > 40) { // Looking left (nose to left OR right eye lower) if (facekit.mouthOpen) { lookLeftOpen.alpha = 1; } else { lookLeft.alpha = 1; } showingTurnedHead = true; } else if (noseDiff > 120 && Math.abs(eyeYDiff) < 30 || eyeYDiff < -40) { // Looking right (nose to right OR left eye lower) if (facekit.mouthOpen) { lookRightOpen.alpha = 1; } else { lookRight.alpha = 1; } showingTurnedHead = true; } // Set original head visibility based on whether a turned head is showing if (showingTurnedHead) { head.alpha = 0; headOpen.alpha = 0; } else { // Only then handle regular head visibility based on mouth state if (facekit && facekit.mouthOpen) { head.alpha = 0; headOpen.alpha = 1; } else { head.alpha = 1; headOpen.alpha = 0; } } } // Scale adjustment based on face size if (facekit.leftEye && facekit.rightEye) { var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); var newScale = eyeDistance / 250; // Adjusted divisor for dragon head // Update rolling average scaleHistory[scaleIndex] = newScale; scaleIndex = (scaleIndex + 1) % scaleHistory.length; // Calculate average scale var avgScale = scaleHistory.reduce(function (a, b) { return a + b; }, 0) / scaleHistory.length; // Apply with gentle smoothing (limited range) var targetScale = Math.max(1.5, Math.min(2.5, avgScale)); head.scaleX = head.scaleX * 0.85 + targetScale * 0.15; self.currentScale = head.scaleX; // Track the current scale head.scaleY = head.scaleY * 0.85 + targetScale * 0.15; headOpen.scaleX = headOpen.scaleX * 0.85 + targetScale * 0.15; headOpen.scaleY = headOpen.scaleY * 0.85 + targetScale * 0.15; body.scaleX = body.scaleX * 0.85 + targetScale * 0.15; body.scaleY = body.scaleY * 0.85 + targetScale * 0.15; lookLeft.scaleX = lookLeft.scaleX * 0.85 - targetScale * 0.15; lookLeft.scaleY = lookLeft.scaleY * 0.85 + targetScale * 0.15; lookRight.scaleX = lookRight.scaleX * 0.85 + targetScale * 0.15; lookRight.scaleY = lookRight.scaleY * 0.85 + targetScale * 0.15; lookLeftOpen.scaleX = lookLeftOpen.scaleX * 0.85 - targetScale * 0.15; lookLeftOpen.scaleY = lookLeftOpen.scaleY * 0.85 + targetScale * 0.15; lookRightOpen.scaleX = lookRightOpen.scaleX * 0.85 + targetScale * 0.15; lookRightOpen.scaleY = lookRightOpen.scaleY * 0.85 + targetScale * 0.15; } }; function calculateFaceTilt() { if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) { // Calculate midpoint between eyes var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2; // Calculate angle between eye midpoint and mouth, negated to fix direction var dx = facekit.mouthCenter.x - eyeMidX; var dy = facekit.mouthCenter.y - eyeMidY; var angle = -(Math.atan2(dx, dy) * (180 / Math.PI)); // Reduced angle impact return Math.max(-15, Math.min(15, angle * 0.15)); } return 0; // Default to straight when face points aren't available } self.flashDamage = function () { var dragonSprites = [body]; // body // Add whichever head sprite is currently visible if (head && head.alpha > 0) { dragonSprites.push(head); } if (headOpen && headOpen.alpha > 0) { dragonSprites.push(headOpen); } if (lookLeft && lookLeft.alpha > 0) { dragonSprites.push(lookLeft); } if (lookRight && lookRight.alpha > 0) { dragonSprites.push(lookRight); } if (lookLeftOpen && lookLeftOpen.alpha > 0) { dragonSprites.push(lookLeftOpen); } if (lookRightOpen && lookRightOpen.alpha > 0) { dragonSprites.push(lookRightOpen); } dragonSprites.forEach(function (sprite) { sprite.tint = 0xFF0000; LK.setTimeout(function () { sprite.tint = 0xFFFFFF; }, 10); }); }; return self; }); var DragonShadow = Container.expand(function () { var self = Container.call(this); var shadow = self.attachAsset('dragonShadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, scaleX: 1, scaleY: 0.3 }); var positionSmoothing = 0.12; var scaleSmoothing = 0.08; self.update = function (dragonX, dragonY, dragonScale) { if (dragonX !== undefined && dragonY !== undefined && dragonScale !== undefined) { // X position follow self.x += (dragonX - self.x) * positionSmoothing; // Y position based on scale (Z-depth) var baseY = 2732 * 0.6; var scaleOffset = (dragonScale - 1) * 300; var targetY = baseY + scaleOffset; self.y += (targetY - self.y) * positionSmoothing; // Scale calculation based on dragon's Y position var heightRatio = dragonY / 2732; var scaleMultiplier = 0.2 + heightRatio * 1.5; // Add Y-position based size adjustment (0-20% increase based on Y position) var yPositionScale = 1 + self.y / 2732 * 0.2; var targetScaleX = scaleMultiplier * yPositionScale; var targetScaleY = scaleMultiplier * 0.3 * yPositionScale; var shadow = self.getChildAt(0); // Get the shadow sprite shadow.scaleX += (targetScaleX - shadow.scaleX) * scaleSmoothing; shadow.scaleY += (targetScaleY - shadow.scaleY) * scaleSmoothing; shadow.alpha = 0.3 + heightRatio * 0.2; } }; return self; }); var DustParticle = Container.expand(function () { var self = Container.call(this); var dust = self.attachAsset('dust', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function (x, y) { self.x = x + (Math.random() * 20 - 10); self.y = y; self.visible = true; self.age = 0; self.lifespan = 60; // Fixed lifespan for consistency dust.alpha = 0.9; // Vertical velocity - move upward self.vy = -1.5; // Faster upward movement self.vx = (Math.random() - 0.5) * 5; // Slightly more horizontal movement // Add rotation for visual interest self.rotation = Math.random() * Math.PI * 2; }; self.update = function () { // Update position self.x += self.vx; self.y += self.vy; self.age++; // Calculate life percentage var lifePercent = self.age / self.lifespan; // Fade out dust.alpha = 0.8 * (1 - lifePercent); // Grow slightly as it rises var scale = 1 + lifePercent * 0.2; // Linear growth from 0.2 to 0.3 dust.scaleX = scale; dust.scaleY = scale; // Recycle when lifetime is over if (lifePercent >= 1) { dustParticlePool.recycle(self); } }; self.deactivate = function () { self.visible = false; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); // States var RUNNING = 'running'; var LEAPING = 'leaping'; var ATTACHED = 'attached'; var FALLING = 'falling'; // Running enemy sprites var runSprite = self.attachAsset('enemy1', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); var runSpriteMirrored = self.attachAsset('enemy1', { anchorX: 0.5, anchorY: 0.5, scaleX: -1, alpha: 0 }); // Properties self.health = 1; self.rotationSpeed = 0; self.animTick = 0; self.animRate = 30; self.pulseRate = self.animRate * 2; self.state = RUNNING; self.leapSpeed = 9; self.targetX = 0; self.targetY = 0; self.hasExploded = false; // Add this flag self.activate = function (x, y) { self.visible = true; self.x = x; self.y = y; self.state = RUNNING; self.rotation = 0; // Reset rotation self.health = 1; self.rotationSpeed = 0; self.fallSpeed = 0; self.acceleration = 0; self.leapDuration = 0; self.shadow = enemyShadowPool.spawn(x, y, self.scaleX); self.isPulsedOut = false; self.hasExploded = false; // Reset explosion flag // If there was a tween running, stop it if (self.currentTween) { self.currentTween.stop(); self.currentTween = null; } // Reset animation state self.animTick = 0; runSprite.alpha = 1; runSpriteMirrored.alpha = 0; // Set initial scale based on distance from horizon var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); var scale = 0.5 + progress; self.scaleX = scale; self.scaleY = scale; }; function checkLeapDistance() { // Get distance on Y-axis only var dy = Math.abs(self.y - dragonShadow.y); // Get relative scales as depth indicators var enemyDepth = self.scaleX; var shadowDepth = dragonShadow.scaleX; // Leap when on same Y-plane regardless of X position var depthDiff = Math.abs(enemyDepth - shadowDepth); var yThreshold = 10; // Close on the Y-axis // If the enemy is on screen and at the right Y position if (dy < yThreshold && depthDiff < 0.3 && self.x > 0 && self.x < 2048) { startLeap(); return true; } return false; } function startLeap() { self.state = LEAPING; // Store where the dragon was when leap started self.leapTargetX = dragon.x; self.leapTargetY = dragon.y; var dx = self.leapTargetX - self.x; var dy = self.leapTargetY - self.y; self.initialLeapDistance = Math.sqrt(dx * dx + dy * dy); self.initialScale = self.scaleX; self.isPulsedOut = false; runSprite.alpha = 1; runSpriteMirrored.alpha = 0; } self.update = function () { if (!self.visible) { return; } switch (self.state) { case RUNNING: // Existing running code var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); var speed = (flyingBackground.scrollSpeed * 0.05 + flyingBackground.scrollSpeed * (progress * 2.5)) * 0.8; self.y -= speed; // Update scale based on distance var scale = 0.5 + progress; self.scaleX = scale; self.scaleY = scale; // Running animation self.animTick++; if (self.animTick >= self.animRate) { self.animTick = 0; runSprite.alpha = runSprite.alpha === 0 ? 1 : 0; runSpriteMirrored.alpha = runSpriteMirrored.alpha === 0 ? 1 : 0; } // In the Enemy class RUNNING state section, replace the dust spawning code with: if (LK.ticks % 10 === 0 && self.y < 2732) { // More frequent spawning (every 10 ticks) var footY = self.y + 50 * self.scaleY; dustParticlePool.spawn(self.x, footY); } // Check for leap checkLeapDistance(); break; case LEAPING: // Calculate progress towards original leap target var dx = self.leapTargetX - self.x; var dy = self.leapTargetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if we've reached or passed the target Y position OR if we're very close to the peak of the leap if (self.y <= self.leapTargetY || distance < 50 || self.leapDuration > 100) { // Added timeout condition var dragonDx = dragon.x - self.x; var dragonDy = dragon.y - self.y; var dragonDistance = Math.sqrt(dragonDx * dragonDx + dragonDy * dragonDy); if (dragonDistance > 300) { self.state = FALLING; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; } else { self.state = ATTACHED; } break; } // Continue leap motion if (distance > 10) { var dirX = dx / distance; var dirY = dy / distance; var speedMultiplier = Math.min(1.5, distance / 100); self.x += dirX * self.leapSpeed * speedMultiplier; self.y += dirY * self.leapSpeed * speedMultiplier; self.scaleX = self.initialScale * 0.75; self.scaleY = self.initialScale * 0.75; } // Increment leap duration for safety timeout self.leapDuration = (self.leapDuration || 0) + 1; break; case ATTACHED: // Stick to dragon position self.x = dragon.x; self.y = dragon.y; // Check for shake-off if (dragon.currentShakeSpeed > 30) { // Threshold for shake-off self.state = FALLING; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; score += self.type * 5; // Bonus points for shake-off scoreTxt.setText(score); LK.setScore(score); break; } // Pulsing animation self.animTick++; if (self.animTick >= self.pulseRate) { self.animTick = 0; // Pulse between 0.7 and 0.8 scale tween(self, { scaleX: self.initialScale * (self.isPulsedOut ? 0.7 : 0.8), scaleY: self.initialScale * (self.isPulsedOut ? 0.7 : 0.8) }, { duration: 200, easing: tween.easeOut }); self.isPulsedOut = !self.isPulsedOut; dragon.flashDamage(); } break; case FALLING: self.rotation += self.rotationSpeed; self.fallSpeed += self.acceleration; self.y += self.fallSpeed; // Check for shadow collision with explosion flag if (self.y >= dragonShadow.y && !self.hasExploded) { self.hasExploded = true; // Create MUCH bigger explosion with red tint for (var i = 0; i < 20; i++) { defeatParticlePool.spawn(self.x, self.y, 0xff0000); // Red tint (0xff0000) } enemyPool.recycle(self); } break; } if (self.shadow) { self.shadow.update(self); } }; self.hit = function () { self.health--; if (self.health <= 0) { if (self.state !== FALLING) { self.state = FALLING; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; return true; } } return false; }; self.deactivate = function () { self.visible = false; runSprite.alpha = 1; runSpriteMirrored.alpha = 0; if (self.shadow) { enemyShadowPool.recycle(self.shadow); self.shadow = null; } }; return self; }); var EnemyArrow = Container.expand(function () { var self = Container.call(this); var arrow = self.attachAsset('enemyArrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); self.speed = 8; self.update = function () { if (!self.active) { return; } self.x += self.vx; self.y += self.vy; // Point arrow in direction of travel self.rotation = Math.atan2(self.vy, self.vx) + Math.PI / 2; if (self.y < 0 || self.y > 2732 || self.x < 0 || self.x > 2048) { enemyArrowPool.recycle(self); } }; self.activate = function (x, y, targetX, targetY) { self.x = x; self.y = y; self.visible = true; self.active = true; // Calculate direction to target var dx = targetX - x; var dy = targetY - y; var dist = Math.sqrt(dx * dx + dy * dy); self.vx = dx / dist * self.speed; self.vy = dy / dist * self.speed; }; self.deactivate = function () { self.visible = false; self.active = false; }; return self; }); var EnemyShadow = Container.expand(function () { var self = Container.call(this); var shadow = self.attachAsset('enemyShadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, scaleX: 1, scaleY: 0.3 }); // In the EnemyShadow class, update method (around line 1018) self.update = function (enemy) { if (!enemy || !enemy.visible || enemy.hasExploded) { self.visible = false; return; } self.visible = true; // Always follow enemy's X position self.x = enemy.x; // For running enemies, shadow is slightly below them if (enemy.state === 'running') { self.y = enemy.y + 200; // Shadow slightly below enemy } // For leaping enemies, shadow moves to dragon shadow position else if (enemy.state === 'leaping') { self.y = dragonShadow.y + 50; // Position just below dragon shadow } // For attached enemies, follow dragon shadow else if (enemy.state === 'attached') { self.y = dragonShadow.y + 50; // Keep following dragon shadow } // For falling enemies, shadow holds last position else if (enemy.state === 'falling') { // Y position stays the same } // Special case for EnemyTwo type else if (enemy.type === 2) { self.y = enemy.y + 600; // Position higher for enemy type 2 } // Scale shadow based on enemy's scale shadow.scaleX = enemy.scaleX * 1.2; shadow.scaleY = enemy.scaleX * 0.3; }; self.activate = function (x, y, enemyScale) { self.x = x; self.y = y + 200; // Initially position shadow slightly below enemy self.visible = true; shadow.scaleX = (enemyScale || 1) * 1.2; shadow.scaleY = (enemyScale || 1) * 0.3; }; self.deactivate = function () { self.visible = false; }; return self; }); var EnemyThree = Container.expand(function () { var self = Container.call(this); // States var GROUNDED = 'grounded'; var FLYING = 'flying'; // Main sprite var sprite = self.attachAsset('enemy3', { anchorX: 0.5, anchorY: 0.5 }); // Jet flames var leftFlame = new JetFlame(); var rightFlame = new JetFlame(); self.addChild(leftFlame); self.addChild(rightFlame); // Properties self.health = 1; self.state = GROUNDED; self.hasExploded = false; self.activate = function (x, y) { self.x = x; self.y = y; self.visible = true; self.state = GROUNDED; self.health = 1; self.hasExploded = false; // Add this line to create the shadow self.shadow = enemyShadowPool.spawn(x, y, self.scaleX); // Position flames at percentage-based positions var flameY = sprite.height * 0.45; var flameX = sprite.width * 0.15; leftFlame.x = -flameX; leftFlame.y = flameY; rightFlame.x = flameX; rightFlame.y = flameY; // Initial scale based on distance from horizon var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); var scale = 0.5 + progress; self.scaleX = scale; self.scaleY = scale; }; self.startFlying = function () { self.state = FLYING; // Calculate rough trajectory toward dragon var dx = dragon.x - self.x; var dy = dragon.y - self.y; var angle = Math.atan2(dy, dx); // Add intentional inaccuracy (they're bad at this) angle += (Math.random() - 0.5) * 0.5; // ±0.25 radians of error // Set velocities var speed = 8; self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; // Start jet flames leftFlame.startPulse(); rightFlame.startPulse(); }; self.update = function () { if (!self.visible) { return; } switch (self.state) { case GROUNDED: // Move with background scroll var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); var speed = (flyingBackground.scrollSpeed * 0.05 + flyingBackground.scrollSpeed * (progress * 2.5)) * 0.8; self.y -= speed; // Scale with distance var scale = 0.5 + progress; self.scaleX = scale; self.scaleY = scale; // Check for ignition trigger if (self.y <= dragonShadow.y + 50) { self.startFlying(); } break; case FLYING: // Update position self.x += self.vx; self.y += self.vy; // Create jet particles if (Math.random() < 0.3) { var particle = particlePool.spawn(self.x + leftFlame.x, self.y + leftFlame.y); if (particle) { particle.vy = Math.abs(particle.vy); // Force downward } particle = particlePool.spawn(self.x + rightFlame.x, self.y + rightFlame.y); if (particle) { particle.vy = Math.abs(particle.vy); // Force downward } } // Check bounds if (self.y < -100 || self.y > 2732 || self.x < -100 || self.x > 2148) { enemyThreePool.recycle(self); } break; } if (self.shadow) { self.shadow.update(self); } // In EnemyThree class, modify the part of the update method that handles the shadow: // Update shadow position if (self.shadow) { if (self.state === GROUNDED) { self.shadow.y = self.y + 200; // Shadow slightly below enemy self.shadow.x = self.x; } else if (self.state === FLYING) { self.shadow.y = dragonShadow.y + 50; self.shadow.x = self.x; // Don't update Y position to let it stay where it was } // Scale shadow based on enemy's scale var shadowSprite = self.shadow.getChildAt(0); shadowSprite.scaleX = self.scaleX * 1.2; shadowSprite.scaleY = self.scaleX * 0.3; // Update shadow alpha based on height var heightRatio = self.y / 2732; shadowSprite.alpha = 0.3 + heightRatio * 0.2; } }; self.hit = function () { self.health--; if (self.health <= 0 && !self.hasExploded) { self.hasExploded = true; // Create explosion for (var i = 0; i < 20; i++) { defeatParticlePool.spawn(self.x, self.y, 0xff0000); } enemyThreePool.recycle(self); return true; } return false; }; self.deactivate = function () { self.visible = false; if (self.shadow) { enemyShadowPool.recycle(self.shadow); self.shadow = null; } leftFlame.deactivate(); rightFlame.deactivate(); }; return self; }); var EnemyTwo = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('enemy2', { anchorX: 0.5, anchorY: 0.5 }); // Properties self.health = 1; self.type = 2; // For scoring self.state = 'approaching'; // approaching, holding self.holdDistance = 800; self.bobOffset = 0; self.bobSpeed = 0.05; self.lastShotTime = 0; self.shotInterval = 2000; // 2 seconds self.activate = function (x, y) { self.visible = true; self.x = x; self.y = y; self.state = 'approaching'; self.health = 1; self.rotation = 0; self.hasExploded = false; // Make sure this flag is set // Ensure shadow is created and stored if (!self.shadow) { self.shadow = enemyShadowPool.spawn(x, y, self.scaleX); } // Moving direction self.moveRight = x < 1024; // If spawned on left side, move right // Initial scale based on distance from horizon var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); self.scaleX = 0.5 + progress; self.scaleY = 0.5 + progress; }; self.update = function () { if (!self.visible) { return; } switch (self.state) { case 'approaching': // Bobbing motion self.bobOffset = Math.sin(LK.ticks * self.bobSpeed) * 50; // Move towards dragon var dx = dragon.x - self.x; var dy = dragon.y - self.y + self.bobOffset; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.holdDistance) { self.x += dx * 0.01; // Reduced from 0.02 self.y += dy * 0.01; // Reduced from 0.02 // Modified scale calculation to maintain larger size var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); self.scaleX = 0.7 + progress * 0.5; // Modified from 0.5 + progress self.scaleY = 0.7 + progress * 0.5; // Modified from 0.5 + progress } else { self.state = 'holding'; } break; case 'holding': // Maintain position relative to dragon var dx = dragon.x - self.x; var targetX = self.x + dx * 0.01; // Keep on screen targetX = Math.max(100, Math.min(1948, targetX)); self.x = targetX; // Stay below dragon with offset var targetY = dragon.y + 800; // Stay 200 pixels below dragon var currentY = self.y - self.bobOffset; // Remove bobbing to get true position self.y = currentY + (targetY - currentY) * 0.1; // Smooth movement to target // Apply bobbing to final position self.y += self.bobOffset; // Shoot at intervals var now = Date.now(); if (now - self.lastShotTime >= self.shotInterval) { self.shoot(); self.lastShotTime = now; } break; case 'falling': self.rotation += self.rotationSpeed; self.fallSpeed += self.acceleration; self.y += self.fallSpeed; // Check for ground collision if (self.shadow && self.y >= self.shadow.y && !self.hasExploded) { self.hasExploded = true; for (var i = 0; i < 20; i++) { defeatParticlePool.spawn(self.x, self.y, 0xff0000); } enemyTwoPool.recycle(self); } break; } if (self.shadow) { self.shadow.update(self); } }; self.shoot = function () { var arrow = enemyArrowPool.spawn(self.x, self.y); if (arrow) { arrow.activate(self.x, self.y, dragon.x, dragon.y); } }; self.hit = function () { self.health--; if (self.health <= 0) { if (Math.random() < 0.5) { // Mid-air explosion for (var i = 0; i < 20; i++) { defeatParticlePool.spawn(self.x, self.y, 0xff0000); } enemyTwoPool.recycle(self); } else { // Fall and explode self.state = 'falling'; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; } return true; } return false; }; self.deactivate = function () { self.visible = false; if (self.shadow) { enemyShadowPool.recycle(self.shadow); self.shadow = null; } }; return self; }); var FieldElement = Container.expand(function () { var self = Container.call(this); var element = self.attachAsset('fieldElement', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function (x, y, scale, alpha) { self.x = x; self.y = y; self.visible = true; self.speedY = 2 + Math.random() * 3; self.initialX = x; element.scaleX = scale; element.scaleY = scale * 0.8; element.alpha = alpha || 0.9; }; self.deactivate = function () { self.visible = false; }; return self; }); var FireParticle = Container.expand(function () { var self = Container.call(this); var particle = self.attachAsset('fireParticle', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.x += self.vx; self.y += self.vy; self.age++; // Update alpha var lifePercent = self.age / self.lifespan; particle.alpha = 1 - lifePercent; // Recycle if lifetime is over OR particle is off screen if (lifePercent >= 1 || self.y < 0 || self.y > 2732 || // Screen height self.x < 0 || self.x > 2048) { // Screen width particlePool.recycle(self); } }; self.activate = function (x, y) { self.x = x; self.y = y; self.visible = true; self.vx = Math.random() * 8 - 4; // Doubled velocity range self.vy = -(Math.random() * 4 + 2); // Make particles move upward self.lifespan = 45 + Math.random() * 30; // Increased lifespan self.age = 0; particle.alpha = 1; // Add scaling particle.scaleX = 0.5 + Math.random() * 0.5; particle.scaleY = 0.5 + Math.random() * 0.5; }; self.deactivate = function () { self.visible = false; }; return self; }); // In the Fireball class, update the properties and update method var Fireball = Container.expand(function () { var self = Container.call(this); var fireballGraphic = self.attachAsset('fireball', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15; self.vx = 0; // Add these new properties self.weaveDirection = 1; // Will be set to 1 or -1 on activate self.weaveAmplitude = 100; // Adjust this to control width of weave self.weaveFrequency = 0.2; // Adjust this to control speed of weave self.initialY = 0; // Track starting Y position self.update = function () { if (!self.active) { return; } // Update base position along trajectory self.y += self.speed; self.x += self.vx; // Add weaving motion var distanceTraveled = self.y - self.initialY; var weaveOffset = Math.sin(distanceTraveled * self.weaveFrequency) * self.weaveAmplitude * self.weaveDirection; self.x += weaveOffset; // Calculate rotation based only on base trajectory, ignoring weave var angle = Math.atan2(self.speed, self.vx) - Math.PI / 2; self.rotation = angle; // Create fire trail particles if (Math.random() < 0.3) { particlePool.spawn(self.x + (Math.random() * 40 - 20), self.y + 20); } // In Fireball class update method, replace the recycle check with: // In Fireball class update method, replace the recycle check with: var groundImpactY = 2732 * 0.9; // Make impact decision only when first crossing the threshold if (self.y > groundImpactY && self.lastY <= groundImpactY && !self.hasImpacted && self.x > 100 && self.x < 1948) { if (Math.random() < 0.5) { self.hasImpacted = true; flamePool.spawn(self.x, groundImpactY); scorchPool.spawn(self.x, groundImpactY); // Spawn more particles for (var i = 0; i < 15; i++) { // Increased from 8 to 15 var particle = particlePool.spawn(self.x + (Math.random() * 40 - 20), groundImpactY); if (particle) { particle.vy = -(Math.random() * 6 + 3); // More upward velocity particle.vx = (Math.random() - 0.5) * 8; // More spread } } fireballPool.recycle(self); return; } } // Track last Y position self.lastY = self.y; // Only recycle if off screen if (self.y > 2732 || self.x < -100 || self.x > 2148) { fireballPool.recycle(self); } }; self.activate = function (x, y) { self.x = x; self.y = y; self.initialY = y; self.lastY = y; self.visible = true; self.active = true; self.hasImpacted = false; self.vx = 0; self.rotation = 0; fireballGraphic.alpha = 1; }; self.deactivate = function () { self.visible = false; self.active = false; // Clear any references that might cause memory leaks self.currentTween = null; }; return self; }); var FlameEffect = Container.expand(function () { var self = Container.call(this); var flame = self.attachAsset('flame', { anchorX: 0.5, anchorY: 1.0, scaleX: 1, scaleY: 0.5 }); self.activate = function (x, y) { self.x = x; self.y = y; self.visible = true; self.age = 0; flame.scaleX = 0.75; flame.scaleY = 0.5; var initialHeight = flame.height * flame.scaleY; var targetScale = 2.5; if (self.currentTween) { self.currentTween.stop(); } self.currentTween = tween(flame, { scaleY: targetScale, y: -(initialHeight * (targetScale - 0.5)) / 2, // Offset upward to compensate for scaling alpha: 1 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { flamePool.recycle(self); } }); }; self.deactivate = function () { self.visible = false; if (self.currentTween) { self.currentTween.stop(); self.currentTween = null; } flame.y = 0; // Reset position }; return self; }); var FlyingBackground = Container.expand(function () { var self = Container.call(this); // Create bottom container self.bottomContainer = new Container(); self.addChild(self.bottomContainer); // Store the horizon line self.horizonY = 2732 / 2; self.totalHeight = 2732 - self.horizonY; self.fieldStripes = []; // Create field stripes (keep this the same as original) var nbStripes = 30; var lastY = self.horizonY + 10; var stripe = self.bottomContainer.attachAsset('fieldStripes', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: lastY }); var progress = Math.max(0, lastY - self.horizonY) / self.totalHeight; stripe.height = 1 + 400 * progress; self.fieldStripes.push({ sprite: stripe, progress: progress }); for (var i = 1; i < nbStripes; i++) { var spacing = 400 * (lastY - self.horizonY) / self.totalHeight; var y = lastY + spacing; var stripe = self.bottomContainer.attachAsset('fieldStripes', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: y }); var progress = Math.max(0, y - self.horizonY) / self.totalHeight; stripe.height = 1 + 400 * progress; self.fieldStripes.push({ sprite: stripe, progress: progress }); lastY = y; } // Create top container self.topContainer = new Container(); self.addChild(self.topContainer); // Create sky background in top container var sky = self.topContainer.attachAsset('skyBackground', { anchorX: 0.5, anchorY: 1.0, x: 2048 / 2, y: self.horizonY + 10 }); // Field scroll speed self.scrollSpeed = 4; // Create object pools self.cloudPool = new ObjectPool(Cloud, 15); self.fieldElementPool = new ObjectPool(FieldElement, 20); // Add pools to containers self.topContainer.addChild(self.cloudPool); self.bottomContainer.addChild(self.fieldElementPool); // Initialize clouds for (var i = 0; i < 8; i++) { var startY = self.horizonY - 400 - Math.random() * 600; var distanceFromHorizon = Math.abs(startY - self.horizonY); var startScale = 0.5 + distanceFromHorizon / 800; var cloud = self.cloudPool.spawn(Math.random() * 2048, startY, startScale, 0.8); if (cloud) { cloud.horizonY = self.horizonY; } } // Initialize field elements for (var j = 0; j < 12; j++) { var fieldStartY = self.horizonY + Math.random() * (2732 - self.horizonY); var fieldDistanceFromHorizon = Math.abs(fieldStartY - self.horizonY); var fieldStartScale = 0.5 + fieldDistanceFromHorizon / 1000; var fieldElement = self.fieldElementPool.spawn(Math.random() * 2048, fieldStartY, fieldStartScale, 0.9); if (fieldElement) { fieldElement.horizonY = self.horizonY; } } self.update = function () { // Update field stripes (keep original code) for (var i = 0; i < self.fieldStripes.length; i++) { var stripe = self.fieldStripes[i]; stripe.progress = Math.max(0, stripe.sprite.y - self.horizonY) / self.totalHeight; var tempSpeed = self.scrollSpeed * 0.05 + self.scrollSpeed * (stripe.progress * 3); stripe.sprite.y -= tempSpeed; } // Sort stripes by Y position self.fieldStripes.sort(function (a, b) { return a.sprite.y - b.sprite.y; }); // Reset stripes that moved above horizon for (var i = 0; i < self.fieldStripes.length; i++) { if (self.fieldStripes[i].sprite.y < self.horizonY) { var lowestStripe = self.fieldStripes[self.fieldStripes.length - 1]; self.fieldStripes[i].sprite.y = lowestStripe.sprite.y + 400 * (lowestStripe.sprite.y - self.horizonY) / self.totalHeight; self.fieldStripes[i].progress = Math.max(0, self.fieldStripes[i].sprite.y - self.horizonY) / self.totalHeight; } } // Re-sort after repositioning self.fieldStripes.sort(function (a, b) { return a.sprite.y - b.sprite.y; }); // Adjust stripe heights for (var i = 0; i < self.fieldStripes.length - 1; i++) { var currentStripe = self.fieldStripes[i]; var nextStripe = self.fieldStripes[i + 1]; currentStripe.sprite.height = nextStripe.sprite.y - currentStripe.sprite.y; } // Handle last stripe var lastStripe = self.fieldStripes[self.fieldStripes.length - 1]; lastStripe.sprite.height = Math.max(1 + 400 * lastStripe.progress, self.horizonY + self.totalHeight - lastStripe.sprite.y + 100); // Update clouds and field elements updateCloudPool(); updateFieldElementPool(); }; function updateCloudPool() { var activeObjects = self.cloudPool.getActiveObjects(); for (var i = 0; i < activeObjects.length; i++) { var cloud = activeObjects[i]; // Move cloud toward horizon cloud.y += cloud.speedY; cloud.x += cloud.speedX; // Get the cloud's sprite var sprite = cloud.getChildAt(0); // Calculate scale based on distance from horizon var distanceFactor = Math.max(0.05, (cloud.y - self.horizonY) / -500); sprite.scaleX = distanceFactor; sprite.scaleY = distanceFactor * 0.8; // Adjust alpha for fade effect near horizon sprite.alpha = Math.min(0.9, distanceFactor); // Reset cloud if it reaches horizon or gets too small if (cloud.y >= self.horizonY - 10 || sprite.scaleX < 0.1) { // Recycle and spawn new cloud self.cloudPool.recycle(cloud); var newCloud = self.cloudPool.spawn(Math.random() * 2048, self.horizonY - 800 - Math.random() * 400, (self.horizonY - 800 - Math.random() * 400 - self.horizonY) / -500, 0.8); if (newCloud) { newCloud.horizonY = self.horizonY; } } } } function updateFieldElementPool() { var activeObjects = self.fieldElementPool.getActiveObjects(); for (var j = 0; j < activeObjects.length; j++) { var element = activeObjects[j]; // Calculate progress based on distance from horizon var progress = Math.max(0, element.y - self.horizonY) / self.totalHeight; // Calculate speed using same logic as stripes var tempSpeed = self.scrollSpeed * 0.05 + self.scrollSpeed * (progress * 3); // Move element toward horizon element.y -= tempSpeed; // Get the element's sprite var sprite = element.getChildAt(0); // Calculate scale based on distance from horizon var fieldDistanceFactor = Math.max(0.05, (element.y - self.horizonY) / 300); sprite.scaleX = fieldDistanceFactor * 1.2; sprite.scaleY = fieldDistanceFactor * 1.2; // Adjust alpha for fade effect //sprite.alpha = Math.min(0.9, fieldDistanceFactor); // Reset element if it reaches horizon if (element.y <= self.horizonY + 10 || sprite.scaleX < 0.1) { // Recycle and spawn new element self.fieldElementPool.recycle(element); // Calculate position for new element var section = Math.floor(Math.random() * 8); var sectionWidth = 2048 / 8; var newX = section * sectionWidth + Math.random() * sectionWidth; var newY = 2732 + Math.random() * 600; var newScale = (newY - self.horizonY) / 300; var newElement = self.fieldElementPool.spawn(newX, newY, newScale, 0.9); if (newElement) { newElement.horizonY = self.horizonY; newElement.initialX = newX; } } element.zIndex = element.y; } // After updating all elements, sort them by z-index activeObjects.sort(function (a, b) { return a.y - b.y; // Sort by y position (smaller y values first) }); // Remove and re-add in the sorted order for (var k = 0; k < activeObjects.length; k++) { var element = activeObjects[k]; self.bottomContainer.removeChild(element); self.bottomContainer.addChild(element); } } return self; }); var JetFlame = Container.expand(function () { var self = Container.call(this); var flame = self.attachAsset('flame', { anchorX: 0.5, anchorY: 0, // Keep bottom anchor for natural flame shape scaleX: 1, scaleY: 0.5, rotation: Math.PI // 180 degrees to point downward }); self.startPulse = function () { flame.scaleX = 0.5; flame.scaleY = 0.3; flame.alpha = 0.8; self.pulse(); }; self.pulse = function () { if (self.currentTween) { self.currentTween.stop(); } self.currentTween = tween(flame, { scaleX: 0.75, scaleY: 0.6, alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { self.currentTween = tween(flame, { scaleX: 0.5, scaleY: 0.3, alpha: 0.8 }, { duration: 300, easing: tween.easeOut, onFinish: self.pulse }); } }); }; self.deactivate = function () { if (self.currentTween) { self.currentTween.stop(); self.currentTween = null; } flame.scaleY = 0.5; }; return self; }); var ObjectPool = Container.expand(function (ObjectClass, size) { var self = Container.call(this); var objects = []; var activeCount = 0; for (var i = 0; i < size; i++) { var obj = new ObjectClass(); if (typeof obj.update !== 'function') { obj.update = function () {}; // Provide a default update method if not defined } obj.visible = false; obj._poolIndex = i; objects.push(obj); self.addChild(obj); } self.spawn = function (x, y, params) { if (activeCount >= objects.length) { return null; } var obj = objects[activeCount]; activeCount++; // Increment AFTER getting object obj.visible = true; obj.activate(x, y, params); return obj; }; self.recycle = function (obj) { if (!obj || typeof obj._poolIndex !== 'number' || obj._poolIndex >= activeCount) { return; } activeCount--; // Decrement count // Swap with last active object if needed if (obj._poolIndex < activeCount) { var temp = objects[activeCount]; objects[activeCount] = obj; objects[obj._poolIndex] = temp; // Update indices temp._poolIndex = obj._poolIndex; obj._poolIndex = activeCount; } obj.deactivate(); obj.visible = false; }; self.recycleAll = function () { while (activeCount > 0) { self.recycle(objects[0]); } }; self.update = function () { for (var i = 0; i < activeCount; i++) { objects[i].update(); } }; self.getActiveObjects = function () { return objects.slice(0, activeCount); }; return self; }); var ScorchMark = Container.expand(function () { var self = Container.call(this); var mark = self.attachAsset('scorchMark', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.activate = function (x, y) { self.x = x; self.y = y; self.visible = true; self.speedY = flyingBackground.scrollSpeed; // Initial scale based on distance from horizon var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); mark.scaleX = 0.5 + progress; mark.scaleY = (0.5 + progress) * 0.8; }; self.update = function () { var progress = Math.max(0, self.y - flyingBackground.horizonY) / (2732 - flyingBackground.horizonY); // Drastically reduce base speed to match field elements var tempSpeed = 0.2 + progress * 3; self.y -= tempSpeed; mark.scaleX = 0.5 + progress; mark.scaleY = (0.5 + progress) * 0.8; mark.alpha = Math.min(0.8, progress); if (self.y <= flyingBackground.horizonY) { scorchPool.recycle(self); } }; self.deactivate = function () { self.visible = false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ function processCollisions() { var fireballs = fireballPool.getActiveObjects(); var enemies = enemyPool.getActiveObjects(); var enemiesTwo = enemyTwoPool.getActiveObjects(); // Process enemy arrows first var arrows = enemyArrowPool.getActiveObjects(); // Add after arrow collision checks in processCollisions() var enemiesThree = enemyThreePool.getActiveObjects(); for (var i = enemiesThree.length - 1; i >= 0; i--) { var enemy = enemiesThree[i]; var dx = enemy.x - dragon.x; var dy = enemy.y - dragon.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 170 * enemy.scaleX) { // Adjust collision radius based on enemy scale // Create explosion effect for (var j = 0; j < 20; j++) { defeatParticlePool.spawn(enemy.x, enemy.y, 0xff0000); } enemyThreePool.recycle(enemy); dragon.flashDamage(); } } // Replace the existing arrow collision check with: for (var i = arrows.length - 1; i >= 0; i--) { var arrow = arrows[i]; var dx = arrow.x - dragon.x; var dy = arrow.y - dragon.y; // Only check collisions if arrow isn't below dragon if (arrow.y <= dragon.y + 50) { // Allow slight vertical tolerance // Wider horizontal check, original vertical bounds var horizontalDistance = Math.abs(dx); var verticalDistance = Math.abs(dy); // Check if within wing range (wider horizontally) if (horizontalDistance < 300 && verticalDistance < 75) { enemyArrowPool.recycle(arrow); // Flash dragon red (keep existing code) dragon.flashDamage(); } } } // Process fireballs against both enemy types for (var i = fireballs.length - 1; i >= 0; i--) { var fireball = fireballs[i]; // Check against type 1 enemies for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; var dx = fireball.x - enemy.x; var dy = fireball.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 120 * enemy.scaleX) { if (enemy.hit()) { score += enemy.type * 10; scoreTxt.setText(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); } fireballPool.recycle(fireball); break; } } // Check against type 2 enemies for (var j = enemiesTwo.length - 1; j >= 0; j--) { var enemy = enemiesTwo[j]; var dx = fireball.x - enemy.x; var dy = fireball.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 170 * enemy.scaleX) { if (enemy.hit()) { score += enemy.type * 10; scoreTxt.setText(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); } fireballPool.recycle(fireball); break; } } for (var j = enemyThreePool.getActiveObjects().length - 1; j >= 0; j--) { var enemy = enemyThreePool.getActiveObjects()[j]; var dx = fireball.x - enemy.x; var dy = fireball.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 110 * enemy.scaleX) { if (enemy.hit()) { score += 10; scoreTxt.setText(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); } fireballPool.recycle(fireball); break; } } } } // Set dark blue background for sky effect game.setBackgroundColor(0x2c3e50); // Game state variables var score = 0; var fireballPool = new ObjectPool(Fireball, 100); var particlePool = new ObjectPool(FireParticle, 400); var enemyPool = new ObjectPool(Enemy, 25); var defeatParticlePool = new ObjectPool(DefeatParticle, 200); var dustParticlePool = new ObjectPool(DustParticle, 100); var enemyShadowPool = new ObjectPool(EnemyShadow, 50); var enemyArrowPool = new ObjectPool(EnemyArrow, 50); var enemyTwoPool = new ObjectPool(EnemyTwo, 25); var enemyThreePool = new ObjectPool(EnemyThree, 25); var flamePool = new ObjectPool(FlameEffect, 20); var scorchPool = new ObjectPool(ScorchMark, 30); var chainSegmentPool = new ObjectPool(ChainSegment, 30); var bossFistPool = new ObjectPool(BossFist, 2); // Left and right fists var boss = null; // Will be created when needed var bossHealthBar = new BossHealthBar(); bossHealthBar.visible = false; var gameActive = true; var isFiring = false; var lastFireTime = 0; var fireRate = 200; // ms between fireballs var enemyOneSpawnRate = 120; // Regular enemies spawn every 2 seconds (at 60fps) var enemyTwoSpawnRate = 240; var enemyThreeSpawnRate = 180; // Every 3 seconds var difficultyScaling = 0; var flyingBackground = new FlyingBackground(); game.addChild(flyingBackground); // Create dragon head (player character) var dragon = game.addChild(new DragonHead()); dragon.x = 2048 / 2; dragon.y = 2732 * 0.2; // Place dragon at top fifth of screen var dragonShadow = new DragonShadow(); game.addChild(dragonShadow); game.addChild(enemyShadowPool); game.addChild(fireballPool); game.addChild(particlePool); game.addChild(enemyPool); game.addChild(enemyTwoPool); game.addChild(enemyThreePool); game.addChild(chainSegmentPool); game.addChild(bossFistPool); game.addChild(bossHealthBar); game.addChild(defeatParticlePool); game.addChild(dustParticlePool); game.addChild(enemyArrowPool); game.addChild(scorchPool); game.addChild(flamePool); game.cleanupPools = function () { fireballPool.recycleAll(); particlePool.recycleAll(); enemyPool.recycleAll(); enemyTwoPool.recycleAll(); defeatParticlePool.recycleAll(); dustParticlePool.recycleAll(); enemyShadowPool.recycleAll(); enemyArrowPool.recycleAll(); scorchPool.recycleAll(); flamePool.recycleAll(); enemyThreePool.recycleAll(); }; // Score display var scoreTxt = new Text2('0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); scoreTxt.y = 50; LK.gui.top.addChild(scoreTxt); // Create a function to add fire particles function createFireParticle(x, y) { particlePool.spawn(x, y); } // Function to spawn a fireball // Update the spawnFireball function function spawnFireball() { if (!gameActive) { return; } var now = Date.now(); if (now - lastFireTime < fireRate) { return; } lastFireTime = now; // Initialize horizontal velocity components var angleOffset = 0; // Get direction from dragon head facing - match thresholds with DragonHead class if (facekit.leftEye && facekit.rightEye && facekit.noseTip) { var faceCenter = (facekit.leftEye.x + facekit.rightEye.x) / 2; var noseDiff = facekit.noseTip.x - faceCenter; var eyeYDiff = facekit.rightEye.y - facekit.leftEye.y; // Keep the original thresholds but require more pronounced movement // to trigger the sideways shots (higher threshold values) if (noseDiff < -120 && Math.abs(eyeYDiff) < 30 || eyeYDiff > 40) { // Facing left - fireballs go left (maintain strong angle) angleOffset = -8; // Original value for strong effect } else if (noseDiff > 120 && Math.abs(eyeYDiff) < 30 || eyeYDiff < -40) { // Facing right - fireballs go right (maintain strong angle) angleOffset = 8; // Original value for strong effect } } // Keep the original tilt impact for consistent feel if (dragon.rotation) { angleOffset += dragon.rotation * 5; // Original value } // Use the new pool's spawn method var fireball = fireballPool.spawn(dragon.x, dragon.y + 50); if (fireball) { fireball.vx = angleOffset; // Add this line to alternate weave direction fireball.weaveDirection = lastFireTime % (fireRate * 2) === 0 ? 1 : -1; } // Play sound LK.getSound('firebreathSound').play(); } // Game update logic game.update = function () { if (!gameActive) { return; } // Check if mouth is open for fire breathing if (facekit && facekit.mouthOpen) { isFiring = true; spawnFireball(); } else { isFiring = false; } if (LK.ticks === 180 && !boss) { // Create boss at exactly tick 180 if not exists boss = new Boss(); game.addChild(boss); bossHealthBar.visible = true; } if (boss) { boss.update(); } // Uncomment these if you want enemy spawning /* if (LK.ticks % enemyOneSpawnRate === 0) { var enemy = enemyPool.spawn(Math.random() * (2048 - 200) + 100, 2732 + 100); if (enemy) { enemy.visible = true; } } if (LK.ticks % enemyTwoSpawnRate === 0) { var spawnX = Math.random() < 0.5 ? -50 : 2098; var enemy = enemyTwoPool.spawn(spawnX, 2732 - 500); if (enemy) { enemy.visible = true; } } if (LK.ticks % enemyThreeSpawnRate === 0) { var enemy = enemyThreePool.spawn(Math.random() * (2048 - 200) + 100, 2732 + 100); if (enemy) { enemy.visible = true; } } */ dragonShadow.update(dragon.x, dragon.y, dragon.currentScale); // Update all pools fireballPool.update(); enemyPool.update(); particlePool.update(); defeatParticlePool.update(); dustParticlePool.update(); flamePool.update(); scorchPool.update(); enemyThreePool.update(); enemyTwoPool.update(); // Process collisions processCollisions(); }; // Start background music LK.playMusic('gameMusic', { fade: { start: 0, end: 0.8, duration: 1000 } });
===================================================================
--- original.js
+++ change.js
@@ -50,11 +50,11 @@
// Properties
self.health = 100;
self.state = ENTERING;
self.stateDuration = 0;
- self.bobOffset = 0;
self.bobSpeed = 0.02;
- self.lastBobOffset = 0;
+ self.bobOffset = 0;
+ self.bobBaseY = 2732 / 2; // Add base Y position
self.activeFist = null;
self.attackTimer = 0;
self.attackDelay = 120;
// Initial position
A clear blue sky background with no clouds.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a small bush. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a wooden arrow with red feathers and a metal arrow head. Completely vertical orientation. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A small vertical flame. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a black scorch mark on the ground left by a meteor impact. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A bright spark. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a red heart. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
An SVG of the word **BOSS** in sharp red font.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
An SVG of the word “Start” written in fire. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows