Code edit (2 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%
User prompt
when the dragon is defeated, play dragonScream sound effect
Code edit (1 edits merged)
Please save this source code
User prompt
play dragonScream sound effect when startbutton is pressed and when dragon explodes
User prompt
play orcShout sound effect when enemy starts to leap
User prompt
play the jetPack sound effect every time enemy enters flying state
/**** * 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 < 185 * 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); } } } // 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 < 170) { 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
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