Code edit (7 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Replace this section: // Replace the direct state checks with: if (facekit.leftEye && facekit.rightEye && facekit.noseTip) { // Calculate face direction var faceCenter = (facekit.leftEye.x + facekit.rightEye.x) / 2; var noseDiff = facekit.noseTip.x - faceCenter; var eyeYDiff = facekit.rightEye.y - facekit.leftEye.y; let newState = 'forward'; if (noseDiff < -120 && Math.abs(eyeYDiff) < 30 || eyeYDiff > 50) { newState = 'left'; } else if (noseDiff > 120 && Math.abs(eyeYDiff) < 30 || eyeYDiff < -50) { newState = 'right'; } // Add to buffer self.stateBuffer.push(newState); if (self.stateBuffer.length > self.bufferSize) { self.stateBuffer.shift(); } // Only change state if buffer is unanimous if (self.stateBuffer.length === self.bufferSize) { const unanimous = self.stateBuffer.every(state => state === self.stateBuffer[0]); if (unanimous) { self.currentState = self.stateBuffer[0]; } } // Reset visibilities based on current state lookLeft.alpha = 0; lookRight.alpha = 0; lookLeftOpen.alpha = 0; lookRightOpen.alpha = 0; head.alpha = 0; headOpen.alpha = 0; if (self.currentState === 'forward') { if (facekit.mouthOpen) { headOpen.alpha = 1; } else { head.alpha = 1; } } else if (self.currentState === 'left') { if (facekit.mouthOpen) { lookLeftOpen.alpha = 1; } else { lookLeft.alpha = 1; } } else if (self.currentState === 'right') { if (facekit.mouthOpen) { lookRightOpen.alpha = 1; } else { lookRight.alpha = 1; } } } ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
Code edit (3 edits merged)
Please save this source code
User prompt
Update with: case LEAPING: // Calculate current distance to dragon (not original leap target) var dragonDx = dragon.x - self.x; var dragonDy = dragon.y - self.y; var dragonDistance = Math.sqrt(dragonDx * dragonDx + dragonDy * dragonDy); // If we're very close to dragon, attach if (dragonDistance < 50) { self.state = ATTACHED; break; } // Continue homing leap motion if (dragonDistance > 10) { var dirX = dragonDx / dragonDistance; var dirY = dragonDy / dragonDistance; var speedMultiplier = Math.min(1.5, dragonDistance / 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; } // Add safety timeout to prevent infinite leaps self.leapDuration = (self.leapDuration || 0) + 1; if (self.leapDuration > 100) { self.state = FALLING; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; } break;
User prompt
Update as needed with: // In the Boss class, add a new property: self.hasPlayedDeathSound = false; // Then in the DYING state update code, modify it to: if (self.y > 2732 + 200) { game.removeChild(self); bossHealthBar.visible = false; if (!self.hasPlayedDeathSound) { LK.getSound('dragonScream').play(); self.hasPlayedDeathSound = true; } LK.setTimeout(function () { LK.showYouWin(); }, 3000); }
Code edit (4 edits merged)
Please save this source code
User prompt
reduce change of health spawn to 10%
Code edit (1 edits merged)
Please save this source code
User prompt
update with: 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]; // Check if enemy is fully on screen (accounting for scaled size) var enemyHeight = 403 * enemy.scaleY; // Original height * scale var enemyWidth = 200 * enemy.scaleX; // Original width * scale if (enemy.y > enemyHeight/2 && enemy.y < 2732 - enemyHeight/2 && enemy.x > enemyWidth/2 && enemy.x < 2048 - enemyWidth/2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } } // Check against type 2 enemies for (var j = enemiesTwo.length - 1; j >= 0; j--) { var enemy = enemiesTwo[j]; var enemyHeight = 412.5 * enemy.scaleY; var enemyWidth = 550 * enemy.scaleX; if (enemy.y > enemyHeight/2 && enemy.y < 2732 - enemyHeight/2 && enemy.x > enemyWidth/2 && enemy.x < 2048 - enemyWidth/2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } } // Check against type 3 enemies for (var j = enemiesThree.length - 1; j >= 0; j--) { var enemy = enemiesThree[j]; var enemyHeight = 253 * enemy.scaleY; var enemyWidth = 300 * enemy.scaleX; if (enemy.y > enemyHeight/2 && enemy.y < 2732 - enemyHeight/2 && enemy.x > enemyWidth/2 && enemy.x < 2048 - enemyWidth/2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } }
Code edit (1 edits merged)
Please save this source code
User prompt
update with: case DYING: self.y += 1; self.x += Math.sin(self.stateDuration * 0.2) * 5; // More frequent and spread out particles if (self.stateDuration % 10 === 0) { for (var i = 0; i < 5; i++) { defeatParticlePool.spawn( self.x + (Math.random() * 800 - 400), self.y + Math.random() * 800, 0xff0000 ); } } // Random explosions every second if (self.stateDuration % 60 === 0) { var explosionX = self.x + (Math.random() * 600 - 300); var explosionY = self.y + (Math.random() * 600 - 300); var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: explosionX, y: explosionY, scaleX: 0.5, // Start bigger scaleY: 0.5, alpha: 1 }); game.addChild(explosion); LK.getSound('explosion').play(); tween(explosion, { scaleX: 4, // End much bigger scaleY: 4, alpha: 0 }, { duration: 500, // Faster animation easing: tween.easeOut, onFinish: function() { game.removeChild(explosion); } }); } if (self.y > 2732 + 200) { // Reduced height check game.removeChild(self); bossHealthBar.visible = false; LK.getSound('dragonScream').play(); LK.setTimeout(function() { LK.showYouWin(); }, 120); // 2 seconds at 60fps } break; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make health spawn chance 100%
User prompt
make health spawn change 10%
/**** * 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.hasCompletedFirstAttack = false; self.stateDuration = 0; self.bobSpeed = 0.1; self.bobOffset = 0; self.bobBaseY = 2732 / 1.35; // Add base Y position self.activeFist = null; self.attackTimer = 0; self.attackDelay = 120; self.hasPlayedDeathSound = false; // 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 / 1.35) { self.state = WALKING; self.attackTimer = self.attackDelay; } break; 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) { self.state = ARROW_ATTACK; self.hasCompletedFirstAttack = true; } else { var rand = Math.random(); if (rand < 0.6) { self.state = ARROW_ATTACK; } else { self.state = Math.random() < 0.5 ? CHARGING_LEFT : CHARGING_RIGHT; } } self.stateDuration = 0; } break; 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) { LK.getSound('arrow').play(); // Play arrow sound effect 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 < 70) { 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 { if (self.activeFist) { bossFistPool.recycle(self.activeFist); self.activeFist = null; } 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); LK.getSound('bossPunch').play(); } } break; 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; if (self.activeFist) { bossFistPool.recycle(self.activeFist); // Add this line } self.activeFist = null; // Add this line to clear the reference } break; case DYING: self.y += 1; self.x += Math.sin(self.stateDuration * 0.2) * 5; // More frequent and spread out particles if (self.stateDuration % 10 === 0) { for (var i = 0; i < 5; i++) { defeatParticlePool.spawn(self.x + (Math.random() * 800 - 400), self.y + Math.random() * 800, 0xff0000); } } // Random explosions every second if (self.stateDuration % 60 === 0) { var explosionX = self.x + (Math.random() * 600 - 300); var explosionY = self.y + (Math.random() * 600 - 300); var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: explosionX, y: explosionY, scaleX: 0.5, //{39} // Start bigger scaleY: 0.5, alpha: 1 }); game.addChild(explosion); LK.getSound('explosion').play(); tween(explosion, { scaleX: 4, //{3g} // End much bigger scaleY: 4, alpha: 0 }, { duration: 500, //{3k} // Faster animation easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(explosion); } }); } if (self.y > 2732 + 200) { //{3r} // Reduced height check game.removeChild(self); bossHealthBar.visible = false; if (!self.hasPlayedDeathSound) { LK.getSound('dragonScream').play(); self.hasPlayedDeathSound = true; } LK.setTimeout(function () { LK.showYouWin(); }, 3000); } 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; bossHealthBar.hide(); // Add boss defeat score score += 5000; scorePopupPool.spawn(self.x, self.y, 5000); scoreTxt.setText(score); updateScoreMeter(score); LK.setScore(score); tween(game, { x: game.x + 20 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { game.x = 0; } }); } }; return self; }); 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, rotation: -Math.PI / 12 // 15 degrees counterclockwise }); var rightFist = self.attachAsset('boss1rightfist', { anchorX: 0.5, anchorY: 0.5, visible: false, rotation: Math.PI / 12 // 15 degrees clockwise }); 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.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; // Pause briefly at extension LK.setTimeout(function () { self.retract(); }, 30); } 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; leftFist.visible = false; rightFist.visible = false; }; // Initialize properties self.chainSegments = []; self.speed = 15; return self; }); // In the BossHealthBar class, add position setting // Modify the BossHealthBar class 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 scaleX: 0 // Start with zero width }); var healthMeter = self.attachAsset('meter', { anchorX: 0.5, anchorY: 1.0, tint: 0xff0000, // Red for health scaleX: 0 // Start with zero width }); // Position settings self.x = 2048 / 2; // Center horizontally self.y = 2732 - 100; // Position from the top self.show = function () { self.visible = true; // Reset both meters to zero width healthMeter.scaleX = 0; if (self.currentTween) { self.currentTween.stop(); } // First animate the empty meter self.currentTween = tween(emptyMeter, { scaleX: 1 }, { duration: 800, easing: tween.easeOutBack, onFinish: function onFinish() { // Then animate the health meter self.currentTween = tween(healthMeter, { scaleX: 1 }, { duration: 400, easing: tween.easeOut }); } }); }; self.hide = function () { if (self.currentTween) { self.currentTween.stop(); } // First shrink the health meter self.currentTween = tween(healthMeter, { scaleX: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { // Then shrink the empty meter self.currentTween = tween(emptyMeter, { scaleX: 0 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { self.visible = false; } }); } }); }; self.updateHealth = function (percent) { // Only animate the health meter, not the empty meter if (self.healthTween) { self.healthTween.stop(); } self.healthTween = tween(healthMeter, { scaleX: percent }, { duration: 300, easing: tween.easeOut }); }; return self; }); var BossText = Container.expand(function () { var self = Container.call(this); var text = self.attachAsset('bosstext', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.x = 2048 / 2; // Center horizontally self.y = 2732 / 2; // Center vertically self.flashCount = 0; self.maxFlashes = 3; self.flash = function () { self.flashCount = 0; self.doFlash(); }; self.doFlash = function () { if (self.flashCount >= self.maxFlashes) { text.alpha = 0; return; } if (self.currentTween) { self.currentTween.stop(); } LK.getSound('alert').play(); // Fade in self.currentTween = tween(text, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Hold LK.setTimeout(function () { // Fade out self.currentTween = tween(text, { alpha: 0, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { self.flashCount++; // Wait before next flash LK.setTimeout(function () { self.doFlash(); }, 20); } }); }, 40); } }); }; 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.stateBuffer = []; self.bufferSize = 10; // Adjust 3-5 frames for balance between stability and responsiveness self.currentState = 'forward'; // Add smoothing properties self.mouthOpenness = 0; // Track mouth state from 0 (closed) to 1 (open) self.mouthSmoothingSpeed = 0.2; // Adjust this value to change transition speed self.currentScale = 2; // Starting scale var horizontalSpeed = 0; self.invulnerableTime = 0; self.invulnerableDuration = 30; // 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 () { // Add in self.update function if (self.invulnerableTime > 0) { self.invulnerableTime--; } // Position tracking if (facekit.noseTip) { targetX = facekit.noseTip.x; targetY = facekit.noseTip.y - 800; // 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; if (newY > dragonShadow.y - 100) { // -100 for visual offset so dragon stays above shadow newY = dragonShadow.y - 100; } 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 var faceCenter = (facekit.leftEye.x + facekit.rightEye.x) / 2; var noseDiff = facekit.noseTip.x - faceCenter; var eyeYDiff = facekit.rightEye.y - facekit.leftEye.y; var newState = 'forward'; if (noseDiff < -120 && Math.abs(eyeYDiff) < 30 || eyeYDiff > 50) { newState = 'left'; } else if (noseDiff > 120 && Math.abs(eyeYDiff) < 30 || eyeYDiff < -50) { newState = 'right'; } // Add to buffer self.stateBuffer.push(newState); if (self.stateBuffer.length > self.bufferSize) { self.stateBuffer.shift(); } // Only change state if buffer is unanimous if (self.stateBuffer.length === self.bufferSize) { var unanimous = self.stateBuffer.every(function (state) { return state === self.stateBuffer[0]; }); if (unanimous) { self.currentState = self.stateBuffer[0]; } } // Reset visibilities based on current state lookLeft.alpha = 0; lookRight.alpha = 0; lookLeftOpen.alpha = 0; lookRightOpen.alpha = 0; head.alpha = 0; headOpen.alpha = 0; if (self.currentState === 'forward') { if (facekit.mouthOpen) { headOpen.alpha = 1; } else { head.alpha = 1; } } else if (self.currentState === 'left') { if (facekit.mouthOpen) { lookLeftOpen.alpha = 1; } else { lookLeft.alpha = 1; } } else if (self.currentState === 'right') { if (facekit.mouthOpen) { lookRightOpen.alpha = 1; } else { lookRight.alpha = 1; } } } // 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 (damageAmount) { // Show game over after effect if (self.invulnerableTime > 0) { return; } // Set invulnerability self.invulnerableTime = self.invulnerableDuration; // Flash effect var dragonSprites = [body]; 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); }); // Apply damage and check for death dragon.health -= damageAmount; dragonHealthBar.updateHealth(dragon.health / 100); if (dragon.health <= 0 && gameActive) { LK.getSound('dragonScream').play(); gameActive = false; // Explode dragon for (var i = 0; i < 60; i++) { defeatParticlePool.spawn(dragon.x, dragon.y, 0xff0000); } // Hide dragon dragon.visible = false; // Show game over after effect LK.setTimeout(function () { LK.showGameOver(); }, 2000); } }; return self; }); var DragonHealthBar = Container.expand(function () { var self = Container.call(this); var emptyMeter = self.attachAsset('meter', { anchorX: 0.5, anchorY: 1.0, tint: 0x333333, scaleX: 1 }); var healthMeter = self.attachAsset('meter', { anchorX: 0.5, anchorY: 1.0, tint: 0x00ff00, scaleX: 1 }); // Position settings self.x = 2048 / 2; self.y = 220; // 100px from top + some padding self.updateHealth = function (percent) { if (self.healthTween) { self.healthTween.stop(); } self.healthTween = tween(healthMeter, { scaleX: percent }, { duration: 300, easing: tween.easeOut }); }; 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 }); self.x = 2048 / 2; // Match dragon's initial x self.y = 2732 * 0.6; // Positioned below dragon 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; LK.getSound('orcShout').play(); // 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 current distance to dragon (not original leap target) var dragonDx = dragon.x - self.x; var dragonDy = dragon.y - self.y; var dragonDistance = Math.sqrt(dragonDx * dragonDx + dragonDy * dragonDy); // If we're very close to dragon, attach if (dragonDistance < 50) { self.state = ATTACHED; break; } // Continue homing leap motion if (dragonDistance > 10) { var dirX = dragonDx / dragonDistance; var dirY = dragonDy / dragonDistance; var speedMultiplier = Math.min(1.5, dragonDistance / 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; } // Add safety timeout to prevent infinite leaps self.leapDuration = (self.leapDuration || 0) + 1; if (self.leapDuration > 100) { self.state = FALLING; self.rotationSpeed = (Math.random() * 0.1 + 0.05) * (Math.random() < 0.5 ? 1 : -1); self.fallSpeed = 2; self.acceleration = 0.2; } 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; 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(2); } 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) } score += 50; scorePopupPool.spawn(self.x, self.y, 50); scoreTxt.setText(score); updateScoreMeter(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); trySpawnHealthPowerup(self.x, self.y); 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.stateDuration = 0; 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; } if (self.state === FLYING) { self.stateDuration++; } 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: if (self.stateDuration === 1) { LK.getSound('jetPack').play(); // Play jetPack sound effect } // 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); } score += 25; scorePopupPool.spawn(self.x, self.y, 25); scoreTxt.setText(score.toString()); updateScoreMeter(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); trySpawnHealthPowerup(self.x, self.y); 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); } score += 75; scorePopupPool.spawn(self.x, self.y, 75); scoreTxt.setText(score); updateScoreMeter(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); trySpawnHealthPowerup(self.x, self.y); 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); LK.getSound('arrow').play(); // Play arrow sound effect } }; self.hit = function () { self.health--; if (self.health <= 0 && self.state !== 'falling') { // Prevent double-triggers if (Math.random() < 0.5) { // Mid-air explosion for (var i = 0; i < 20; i++) { defeatParticlePool.spawn(self.x, self.y, 0xff0000); } score += 75; scorePopupPool.spawn(self.x, self.y, 75); scoreTxt.setText(score); updateScoreMeter(score); LK.setScore(score); LK.getSound('enemyDefeat').play(); trySpawnHealthPowerup(self.x, self.y); 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 = 10; self.vx = 0; // Add these new properties self.weaveDirection = 1; // Will be set to 1 or -1 on activate self.weaveAmplitude = 50; // Adjust this to control width of weave self.weaveFrequency = 0.1; // 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++) { // Place clouds above the screen var startY = -600 - 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 above screen self.cloudPool.recycle(cloud); var newCloud = self.cloudPool.spawn(Math.random() * 2048, // Random x position -600 - Math.random() * 400, // Above screen 0.5 + 600 / 800, // Scale based on distance 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; }); // Modify the HealthPopup class var HealthPopup = Container.expand(function () { var self = Container.call(this); var text = new Text2('+10', { size: 150, fill: 0x00FF00, // Green stroke: 0x000000, strokeThickness: 3 }); text.anchor.set(0.5, 0.5); self.addChild(text); // Initially hide the popup self.visible = false; self.show = function (x, y) { self.x = x; self.y = y; self.visible = true; text.scaleX = 0.1; text.scaleY = 0.1; text.y = 0; // Reset position before animation if (self.currentTween) { self.currentTween.stop(); } self.currentTween = tween(text, { scaleX: 1.5, scaleY: 1.5, y: -50 }, { duration: 300, easing: tween.easeOutBack, onFinish: function onFinish() { tween(text, { alpha: 0, y: -100 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { self.visible = false; text.alpha = 1; text.y = 0; } }); } }); }; return self; }); var HealthPowerup = Container.expand(function () { var self = Container.call(this); var heart = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); self.activate = function (x, y) { self.x = x; self.y = y; self.visible = true; self.age = 0; heart.scaleX = heart.scaleY = 1; }; self.update = function () { self.age++; // Wave pattern movement self.x += Math.sin(self.age * 0.05) * 2; self.y -= 3; // Float upward // Pulse scale var pulse = 1 + Math.sin(self.age * 0.1) * 0.2; heart.scaleX = heart.scaleY = pulse; // Remove if off screen if (self.y < -50) { healthPowerupPool.recycle(self); } }; self.deactivate = function () { self.visible = false; }; 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 ProgressMeter = Container.expand(function () { var self = Container.call(this); // Create meter background (now horizontal) var meter = self.attachAsset('meter', { anchorX: 0, anchorY: 0.5, scaleX: 1.2, // Adjust width to preference scaleY: 0.6, // Make thinner tint: 0x666666 }); // Progress icons var dragonIcon = self.attachAsset('dragonHead', { anchorX: 0.5, anchorY: 0.7, scaleX: 0.9, scaleY: 0.9 }); var bossIcon = self.attachAsset('skull', { anchorX: 0.5, anchorY: 0.7 }); // Position at top center // Position elements relative to meter center var totalWidth = meter.width * 1.2 + 120; // Include icon spaces self.x = 2048 / 2 - totalWidth / 2; // Center the whole assembly self.y = 80; // Position icons relative to meter edges dragonIcon.x = 0; // Left icon at start dragonIcon.y = meter.height / 2; bossIcon.x = totalWidth; // Right icon at end bossIcon.y = meter.height / 2; // Center meter between icons meter.x = 60; // Space for left icon self.updateProgress = function (progress) { dragonIcon.x = progress * (totalWidth - 60); // Move between start and end positions }; 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; }); var ScorePopup = Container.expand(function () { var self = Container.call(this); // Create text with white color instead of red var scoreTxt = new Text2('', { size: 120, fill: 0xFFFFFF, // White color stroke: 0x000000, strokeThickness: 3 }); scoreTxt.anchor.set(0.5, 0.5); self.addChild(scoreTxt); self.activate = function (x, y, amount, color) { self.x = x; self.y = y; self.visible = true; scoreTxt.setText('+' + amount); // Special case for boss defeat score if (amount === 5000) { scoreTxt.size = 300; // Much larger text color = 0xFFD700; // Gold color } else { scoreTxt.size = 120; // Normal size } // Apply tint if provided if (color !== undefined) { scoreTxt.tint = color; } else { scoreTxt.tint = 0xFF0000; // Default red tint } scoreTxt.scaleX = 0.1; scoreTxt.scaleY = 0.1; if (self.currentTween) { self.currentTween.stop(); } // Special animation for boss defeat if (amount === 5000) { self.currentTween = tween(scoreTxt, { scaleX: 2.5, scaleY: 2.5, y: -150 // Move higher for boss defeat }, { duration: 600, // Longer duration easing: tween.easeOutBack, onFinish: function onFinish() { tween(scoreTxt, { alpha: 0, y: -250 // Float up higher }, { duration: 800, // Longer fade easing: tween.easeIn, onFinish: function onFinish() { scorePopupPool.recycle(self); } }); } }); } else { // Original animation for regular scores self.currentTween = tween(scoreTxt, { scaleX: 1.5, scaleY: 1.5, y: -50 }, { duration: 300, easing: tween.easeOutBack, onFinish: function onFinish() { tween(scoreTxt, { alpha: 0, y: -100 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { scorePopupPool.recycle(self); } }); } }); } }; self.deactivate = function () { self.visible = false; scoreTxt.alpha = 1; scoreTxt.y = 0; scoreTxt.tint = 0xFFFFFF; // Reset to white (no tint) if (self.currentTween) { self.currentTween.stop(); } }; return self; }); var TitleScreen = Container.expand(function () { var self = Container.call(this); var fadeOverlay = self.attachAsset('particle', { anchorX: 0, anchorY: 0, scaleX: 2048, scaleY: 2732, tint: 0x000000, alpha: 1 }); var titleImage = self.attachAsset('titleimage', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); var startButton = self.attachAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, scaleX: 0, scaleY: 0, alpha: 0 }); titleImage.x = 2048 / 2; titleImage.y = 2732 / 3; startButton.x = 2048 / 2; startButton.y = titleImage.y + 1100; self.start = function () { // Fade in title tween(fadeOverlay, { alpha: 0 }, { duration: 1500, easing: tween.easeOut }); tween(titleImage, { alpha: 1, scaleX: 1, // Add scale pulse start scaleY: 1 // Add scale pulse start }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { // Add pulsing animation function pulseTitle() { tween(titleImage, { tint: 0xFFFF00, scaleX: 1.1, scaleY: 1.1 }, { duration: 1100, easing: tween.easeInOut, onFinish: function onFinish() { tween(titleImage, { tint: 0xFFA500, scaleX: 1, scaleY: 1 }, { duration: 1100, easing: tween.easeInOut, onFinish: pulseTitle }); } }); } pulseTitle(); // Grow button tween(startButton, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 800, easing: tween.easeOutBack, onFinish: function onFinish() { // Add pulsing animation function pulse() { tween(startButton, { tint: 0xFFFF00 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(startButton, { tint: 0xFFA500 }, { duration: 1000, easing: tween.easeInOut, onFinish: pulse }); } }); } pulse(); } }); } }); }; startButton.down = function () { self.startGame(); return true; }; self.startGame = function () { // Prevent multiple clicks LK.getSound('dragonScream').play(); startButton.down = null; // Fade out title elements tween(titleImage, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { // Enable gameplay gameActive = true; // Show UI dragonHealthBar.visible = true; scoreTxt.visible = true; scoreMeterBg.visible = true; progressMeter.visible = true; // Enable dragon and shadow dragon.visible = true; dragonShadow.visible = true; // Remove title screen game.removeChild(self); } }); tween(startButton, { alpha: 0 }, { duration: 500 }); }; 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 < 210 * 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(6); } } // Add inside processCollisions() function, after the existing collision checks: // Check boss fist collisions with dragon if (boss && boss.activeFist) { var fist = boss.activeFist; var dx = fist.x - dragon.x; var dy = fist.y - dragon.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use dragon's current scale for collision radius var collisionRadius = 150 * dragon.currentScale; if (distance < collisionRadius) { dragon.flashDamage(12); // Should the fist retract after hitting? Let me know if you want this behavior } } // Replace the existing fireball-boss collision check with this: if (boss && boss.state !== 'dying') { var fireballs = fireballPool.getActiveObjects(); for (var i = fireballs.length - 1; i >= 0; i--) { var fireball = fireballs[i]; var dx = fireball.x - boss.x; var dy = fireball.y - boss.y; // Use elliptical hitbox: wider horizontally, same vertically var horizontalRange = 300; // Wider horizontal hitbox (was effectively 100) var verticalRange = 100; // Keep the same vertical hitbox // Elliptical distance formula var ellipticalDist = Math.sqrt(dx * dx / (horizontalRange * horizontalRange) + dy * dy / (verticalRange * verticalRange)); if (ellipticalDist < 1) { boss.damage(1); fireballPool.recycle(fireball); } } } // 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(7); } } } var fireballs = fireballPool.getActiveObjects(); var arrows = enemyArrowPool.getActiveObjects(); for (var i = fireballs.length - 1; i >= 0; i--) { var fireball = fireballs[i]; for (var j = arrows.length - 1; j >= 0; j--) { var arrow = arrows[j]; var dx = fireball.x - arrow.x; var dy = fireball.y - arrow.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Small collision radius since these are projectiles // Create small explosion for (var k = 0; k < 8; k++) { // Fewer particles than full defeat var particle = defeatParticlePool.spawn(arrow.x + (Math.random() * 20 - 10), // Smaller spread arrow.y + (Math.random() * 20 - 10), 0xff0000 // Red tint ); if (particle) { // Smaller scale for particles var sprite = particle.getChildAt(0); sprite.scaleX = 0.2; sprite.scaleY = 0.2; } } // Remove both projectiles fireballPool.recycle(fireball); enemyArrowPool.recycle(arrow); // Play explosion sound with lower volume LK.getSound('enemyDefeat').play({ volume: 0.5 }); break; // Break inner loop since fireball is gone } } } // 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]; // Check if enemy is fully on screen (accounting for scaled size) var enemyHeight = 403 * enemy.scaleY; // Original height * scale var enemyWidth = 200 * enemy.scaleX; // Original width * scale if (enemy.y > enemyHeight / 2 && enemy.y < 2732 - enemyHeight / 2 && enemy.x > enemyWidth / 2 && enemy.x < 2048 - enemyWidth / 2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } } // Check against type 2 enemies for (var j = enemiesTwo.length - 1; j >= 0; j--) { var enemy = enemiesTwo[j]; var enemyHeight = 412.5 * enemy.scaleY; var enemyWidth = 550 * enemy.scaleX; if (enemy.y > enemyHeight / 2 && enemy.y < 2732 - enemyHeight / 2 && enemy.x > enemyWidth / 2 && enemy.x < 2048 - enemyWidth / 2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } } // Check against type 3 enemies for (var j = enemiesThree.length - 1; j >= 0; j--) { var enemy = enemiesThree[j]; var enemyHeight = 253 * enemy.scaleY; var enemyWidth = 300 * enemy.scaleX; if (enemy.y > enemyHeight / 2 && enemy.y < 2732 - enemyHeight / 2 && enemy.x > enemyWidth / 2 && enemy.x < 2048 - enemyWidth / 2) { 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) { enemy.hit(); fireballPool.recycle(fireball); break; } } } } var powerups = healthPowerupPool.getActiveObjects(); for (var i = powerups.length - 1; i >= 0; i--) { var powerup = powerups[i]; var dx = powerup.x - dragon.x; var dy = powerup.y - dragon.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 240) { dragon.health = Math.min(100, dragon.health + 10); dragonHealthBar.updateHealth(dragon.health / 100); LK.getSound('health').play(); healthPowerupPool.recycle(powerup); healthPopup.show(powerup.x, powerup.y); } } } function trySpawnHealthPowerup(x, y) { if (Math.random() < 0.10) { // 10% chance // 100% chance // 10% chance // 7% chance // 10% chance // 15% chance var powerup = healthPowerupPool.spawn(x, y); if (powerup) { scorePopupPool.spawn(x, y - 50, "HEALTH", 0x00ff00); } } } // 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 scorePopupPool = new ObjectPool(ScorePopup, 20); var healthPowerupPool = new ObjectPool(HealthPowerup, 10); 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; bossHealthBar.updateHealth(1.0); var LEVEL_DURATION = 180 * 60; // 3 minutes at 60fps var levelTimer = 0; var progressMeter = new ProgressMeter(); var isFiring = false; var lastFireTime = 0; var fireRate = 200; // ms between fireballs var enemy1StartTime = 30 * 60; // 30 seconds var enemy2StartTime = 15 * 60; // 15 seconds var enemy3StartTime = 5 * 60; // 5 seconds var enemy1SpawnRate = 300; // ~3 seconds var enemy2SpawnRate = 240; // ~4 seconds var enemy3SpawnRate = 180; // ~5 seconds var ENEMY1_MIN_RATE = 150; // Half of 300 var ENEMY2_MIN_RATE = 120; // Half of 240 var ENEMY3_MIN_RATE = 100; // Half of 180 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(scorePopupPool); game.addChild(defeatParticlePool); game.addChild(dustParticlePool); game.addChild(enemyArrowPool); game.addChild(scorchPool); game.addChild(flamePool); game.addChild(healthPowerupPool); var healthPopup = new HealthPopup(); game.addChild(healthPopup); dragon.health = 100; var dragonHealthBar = new DragonHealthBar(); game.addChild(dragonHealthBar); game.addChild(progressMeter); var bossText = new BossText(); game.addChild(bossText); 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(); }; var scoreMeterBg = game.attachAsset('particle', { anchorX: 0, anchorY: 0.5, tint: 0x333333, alpha: 0.5, scaleY: 1 }); // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3 }); // Position meter and score scoreMeterBg.x = 2048 / 2; // Center X scoreMeterBg.anchorX = 0.5; // Center anchor for background scoreMeterBg.y = 300; scoreTxt.anchor.set(0.5, 0.5); // Center anchor for text scoreTxt.x = 2048 / 2; // Center X scoreTxt.y = 300; game.addChild(scoreMeterBg); game.addChild(scoreTxt); function updateScoreMeter(score) { var minWidth = 200; var scoreStr = score.toString(); var padding = 40; // Total padding (20 each side) // Adjust width per digit based on digit count var widthPerDigit; if (scoreStr.length === 1) { widthPerDigit = 70; } else if (scoreStr.length === 2) { widthPerDigit = 60; } else { widthPerDigit = 70; } var targetWidth = Math.max(minWidth, scoreStr.length * widthPerDigit + padding); scoreMeterBg.scaleX = targetWidth / scoreMeterBg.width; // Text stays centered automatically now } // 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 > 50) { // Facing left - fireballs go left (maintain strong angle) angleOffset = -8; // Original value for strong effect } else if (noseDiff > 120 && Math.abs(eyeYDiff) < 30 || eyeYDiff < -50) { // 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(); } dragonHealthBar.visible = false; scoreTxt.visible = false; scoreMeterBg.visible = false; progressMeter.visible = false; dragon.visible = false; dragonShadow.visible = false; gameActive = false; var titleScreen = new TitleScreen(); game.addChild(titleScreen); titleScreen.start(); // 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 (boss) { boss.update(); } levelTimer++; // Only update progress if boss hasn't spawned yet if (!boss) { progressMeter.updateProgress(levelTimer / LEVEL_DURATION); } // Enemy spawning logic // Enemy spawning logic if (!boss || boss.state !== 'dying') { // Add a spawn rate multiplier that's 2x when boss is present var spawnRateMultiplier = boss ? 2 : 1; if (levelTimer >= enemy3StartTime) { // Scale from initial 180 to minimum 90 over level duration var scaled3Rate = Math.max(ENEMY3_MIN_RATE, enemy3SpawnRate - (enemy3SpawnRate - ENEMY3_MIN_RATE) * (levelTimer / LEVEL_DURATION)); // Apply the multiplier to the spawn rate if (LK.ticks % Math.floor(scaled3Rate * spawnRateMultiplier) === 0) { var enemy = enemyThreePool.spawn(Math.random() * (2048 - 200) + 100, 2732 + 100); } } if (levelTimer >= enemy2StartTime) { var scaled2Rate = Math.max(ENEMY2_MIN_RATE, enemy2SpawnRate - (enemy2SpawnRate - ENEMY2_MIN_RATE) * (levelTimer / LEVEL_DURATION)); // Apply the multiplier to the spawn rate if (LK.ticks % Math.floor(scaled2Rate * spawnRateMultiplier) === 0) { var spawnX = Math.random() < 0.5 ? -50 : 2098; var enemy = enemyTwoPool.spawn(spawnX, 2732 - 500); } } if (levelTimer >= enemy1StartTime) { var scaled1Rate = Math.max(ENEMY1_MIN_RATE, enemy1SpawnRate - (enemy1SpawnRate - ENEMY1_MIN_RATE) * (levelTimer / LEVEL_DURATION)); // Apply the multiplier to the spawn rate if (LK.ticks % Math.floor(scaled1Rate * spawnRateMultiplier) === 0) { var enemy = enemyPool.spawn(Math.random() * (2048 - 200) + 100, 2732 + 100); } } } // Boss spawn check if (levelTimer >= LEVEL_DURATION && !boss) { bossText.flash(); boss = new Boss(); game.addChild(boss); game.addChild(bossHealthBar); bossHealthBar.visible = true; bossHealthBar.show(); } dragonShadow.update(dragon.x, dragon.y, dragon.currentScale); // Update all pools fireballPool.update(); enemyPool.update(); particlePool.update(); defeatParticlePool.update(); dustParticlePool.update(); bossFistPool.update(); flamePool.update(); scorchPool.update(); enemyThreePool.update(); enemyTwoPool.update(); scorePopupPool.update(); healthPowerupPool.update(); // Process collisions processCollisions(); }; // Start background music LK.playMusic('gameMusic', { fade: { start: 0, end: 0.7, duration: 1000 } });
===================================================================
--- original.js
+++ change.js
@@ -684,9 +684,9 @@
scaleY: 2,
alpha: 0
});
self.stateBuffer = [];
- self.bufferSize = 4; // Adjust 3-5 frames for balance between stability and responsiveness
+ self.bufferSize = 10; // Adjust 3-5 frames for balance between stability and responsiveness
self.currentState = 'forward';
// Add smoothing properties
self.mouthOpenness = 0; // Track mouth state from 0 (closed) to 1 (open)
self.mouthSmoothingSpeed = 0.2; // Adjust this value to change transition speed
@@ -2520,9 +2520,9 @@
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 < 185 * enemy.scaleX) {
+ if (distance < 210 * 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);
@@ -2581,8 +2581,44 @@
dragon.flashDamage(7);
}
}
}
+ var fireballs = fireballPool.getActiveObjects();
+ var arrows = enemyArrowPool.getActiveObjects();
+ for (var i = fireballs.length - 1; i >= 0; i--) {
+ var fireball = fireballs[i];
+ for (var j = arrows.length - 1; j >= 0; j--) {
+ var arrow = arrows[j];
+ var dx = fireball.x - arrow.x;
+ var dy = fireball.y - arrow.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < 60) {
+ // Small collision radius since these are projectiles
+ // Create small explosion
+ for (var k = 0; k < 8; k++) {
+ // Fewer particles than full defeat
+ var particle = defeatParticlePool.spawn(arrow.x + (Math.random() * 20 - 10),
+ // Smaller spread
+ arrow.y + (Math.random() * 20 - 10), 0xff0000 // Red tint
+ );
+ if (particle) {
+ // Smaller scale for particles
+ var sprite = particle.getChildAt(0);
+ sprite.scaleX = 0.2;
+ sprite.scaleY = 0.2;
+ }
+ }
+ // Remove both projectiles
+ fireballPool.recycle(fireball);
+ enemyArrowPool.recycle(arrow);
+ // Play explosion sound with lower volume
+ LK.getSound('enemyDefeat').play({
+ volume: 0.5
+ });
+ break; // Break inner loop since fireball is gone
+ }
+ }
+ }
// Process fireballs against both enemy types
for (var i = fireballs.length - 1; i >= 0; i--) {
var fireball = fireballs[i];
// Check against type 1 enemies
@@ -2640,9 +2676,9 @@
var powerup = powerups[i];
var dx = powerup.x - dragon.x;
var dy = powerup.y - dragon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 170) {
+ if (distance < 240) {
dragon.health = Math.min(100, dragon.health + 10);
dragonHealthBar.updateHealth(dragon.health / 100);
LK.getSound('health').play();
healthPowerupPool.recycle(powerup);
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