/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Background = Container.expand(function () { var self = Container.call(this); var backgroundGraphics = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(backgroundGraphics); self.x = 2048 / 2; self.y = 2732 / 2; // Move the down handler to the Background class self.down = function (x, y, obj) { var game_position = game.toLocal(obj.global); target.x = Math.max(corners[0].x, Math.min(game_position.x, corners[3].x)); target.y = Math.max(corners[0].y, Math.min(game_position.y, corners[3].y)); }; }); var Bonus = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; // Create shadow for bonus var shadowBonus = LK.getAsset('bonusHealth', { anchorX: 0.5, anchorY: 0.5 }); shadowBonus.alpha = 0.5; shadowBonus.tint = 0x000000; shadowBonus.x = 10; // Offset shadow position shadowBonus.y = 10; // Offset shadow position self.addChild(shadowBonus); self.active = false; var bonusGraphics = LK.getAsset('bonusHealth', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(bonusGraphics); self.bonusLight = LK.getAsset('bonusHealthLight', { anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00 }); self.addChild(self.bonusLight); self.animLight = function () { function loopTint() { tween(self.bonusLight, { tint: 0xFFFFFF }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.bonusLight, { tint: 0x00FF00 }, { duration: 600, easing: tween.easeInOut, onFinish: loopTint }); } }); } loopTint(); }; self.animLight(); self.animMove = function () { function loopMove() { tween(self, { y: self.y + 10 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { y: self.y - 10 }, { duration: 500, easing: tween.easeInOut, onFinish: loopMove }); } }); } loopMove(); }; self.throwToCorner = function () { // Calculate a random position within range instead of using corners var minDistance = 1024; var maxDistance = 1480; // Generate random angle var randomAngle = Math.random() * Math.PI * 2; // Calculate random distance within range var randomDistance = minDistance + Math.random() * (maxDistance - minDistance); // Calculate target position var targetX = self.x + Math.cos(randomAngle) * randomDistance; var targetY = self.y + Math.sin(randomAngle) * randomDistance; // Ensure target is within the playable area (within corners) targetX = Math.max(borderOffset, Math.min(2048 - borderOffset, targetX)); targetY = Math.max(borderOffset, Math.min(2732 - borderOffset, targetY)); // Calculate control point for parabolic motion var controlX = (self.x + targetX) / 2; var controlY = Math.min(self.y, targetY) - 500; // Control point above the line for a parabolic effect // Initial position var startX = self.x; var startY = self.y; // Create a tween for parabolic motion tween(self, { x: targetX, y: targetY }, { duration: 1200, easing: tween.easeOutQuad, onUpdate: function onUpdate() { // Calculate parabolic path using quadratic Bezier curve formula var t = this.progress; self.x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * targetX; self.y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * targetY; // Add rotation for visual effect self.rotation = t * Math.PI * 3; // 1.5 full rotations during flight }, onFinish: function onFinish() { self.rotation = 0; // Reset rotation self.active = true; // Activate bonus after reaching the corner self.animMove(); } }); }; self.throwToCorner(); // Call the function to start the animation }); var ButtonPunch = Container.expand(function () { var self = Container.call(this); var buttonGraphics = LK.getAsset('buttonPunch', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(buttonGraphics); // Add click/tap handler self.down = function () { // Trigger knight attack when button is pressed if (!isDied && knight && knight.health >= 0 && !knight.isAttacking) { knight.attack(); } }; }); // Import the tween plugin var Drone = Container.expand(function (level) { var self = Container.call(this); self.level = level || 0; // Set level to the passed parameter or default to 0 self.health = (isDebug ? 2 : 4) + self.level * 2; // Add level*2 to drone's health var droneWidth = 200; // Increase size based on level var levelGrowth = self.level * self.level * 80; self.droneScanLaser = LK.getAsset('droneScanLaser', { anchorX: 0, anchorY: 0.5 }); self.droneScanLaser.visible = true; // Always visible self.droneScanLaser.x = droneWidth / 2; self.droneScanLaser.alpha = 0.3; // Set alpha to 0.3 self.droneScanLaser.width += levelGrowth; self.droneScanLaser.height += levelGrowth; self.addChild(self.droneScanLaser); self.droneScanBar = LK.getAsset('droneScanBar', { anchorX: 0, anchorY: 0.5 }); self.droneScanBar.x = droneWidth / 2; self.droneScanBar.blendMode = 3; self.droneScanBar.width = self.droneScanLaser.width; self.addChild(self.droneScanBar); var droneGraphics = LK.getAsset('drone', { anchorX: 0.5, anchorY: 0.5, width: droneWidth, height: droneWidth }); var shadowDrone = LK.getAsset('drone', { anchorX: 0.5, anchorY: 0.5, width: droneWidth, height: droneWidth }); self.shadowOffset = { x: -30, y: 40 }; // Define shadow offset property shadowDrone.alpha = 0.5; shadowDrone.tint = 0x000000; shadowDrone.x = -10; shadowDrone.y = 40; self.addChild(shadowDrone); self.addChild(droneGraphics); // Add health bar under the drone self.healthBar = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 10, color: 0x00FF00 // Green color for health }); self.healthBar.y = droneWidth / 2 + 20; // Position the health bar under the drone self.addChild(self.healthBar); function loopScanLaserTint() { if (self.state !== 'scanning') { return; } tween(self.droneScanLaser, { tint: 0x00FF00 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.droneScanLaser, { tint: 0x00AA00 }, { duration: 1000, easing: tween.easeInOut, onFinish: loopScanLaserTint // Recursively call to loop }); } }); } function animateScanBar() { if (self.state !== 'scanning') { return; } tween(self.droneScanBar, { alpha: 0.3, rotation: Math.PI / 6.75 }, { duration: 600 - self.level * 50, //{V} // Decrease duration to increase frequency with level easing: tween.easeInOut, onFinish: function onFinish() { tween(self.droneScanBar, { alpha: 0.6, rotation: -Math.PI / 6.75 }, { duration: 1000, easing: tween.easeInOut, onFinish: animateScanBar // Recursively call to loop }); // Ensure the tween is started } }); // Ensure the tween is started } self.speed = 5; self.state = ''; // Possible states: scanning, attacking // Add debug text to track drone state self.debugText = new Text2(self.state, { size: 50, fill: 0xFFFFFF }); self.debugText.x = -droneGraphics.width / 2; self.debugText.y = -droneGraphics.height / 2 - 50; self.debugText.visible = isDebug; // Set visibility based on debug mode self.addChild(self.debugText); self.fireRange = 300; // Example fire range value self.fireDelay = 1200; self.currentTargetIndex = -1; self.target = { x: 0, y: 0 }; self.lastKnightCheckTime = 0; // Track when we last checked for knight intersection self.lastFireTime = 0; // Track when we last fired at the knight self.lastRotationTime = 0; // Track the last time we updated rotation self.isRotating = false; // Flag to track if we're currently rotating self.isMoving = false; // Flag to track if we're currently moving self.needsRotation = false; // Flag to track if we need to rotate before moving self.justSwitchedFromAttacking = false; // Flag to track if we just switched from attacking mode // Function to select a new random target self.selectTarget = function () { log("selectTarget...:"); var newTargetIndex; do { newTargetIndex = Math.floor(Math.random() * corners.length); } while (newTargetIndex === self.currentTargetIndex); self.currentTargetIndex = newTargetIndex; self.target = corners[newTargetIndex]; log("New target selected at position:", self.target.x, self.target.y); // Set a flag to indicate the target is new and needs rotation first self.needsRotation = true; // Return the new target (not used currently but could be useful) return self.target; }; // Function to fire a laser beam self.fire = function () { log("Drone firing laser beam..."); LK.getSound('drone-beam').play(); var laserBeam = new LaserBeam(); laserBeam.fire(self.x, self.y, self.rotation); }; // Function to handle state transitions self.switchState = function (newState) { // Don't do anything if the state isn't changing if (self.state === newState) { return; } log("Switching drone state from " + self.state + " to " + newState); // Stop any existing tweens to ensure clean state transition tween.stop(self); // Clear scan sound interval if it exists if (self.scanSoundInterval) { LK.clearInterval(self.scanSoundInterval); self.scanSoundInterval = null; } // Reset movement flags self.isRotating = false; self.isMoving = false; self.justSwitchedFromAttacking = newState === 'scanning' && self.state === 'attacking'; // Update the state self.state = newState; self.debugText.setText(self.state); // Update debug text with current state // Handle entry actions for new state if (newState === 'scanning') { // Play drone-scan sound self.scanSoundInterval = LK.setInterval(function () { LK.getSound('drone-scan').play(); }, 1000); // Entering scanning state self.debugText.tint = 0xFFFFFF; // Reset tint to white for other states self.droneScanLaser.tint = 0x00FF00; // Reset tint to green for scanning mode // Clear current target and set flag to select a new one //self.target = null; //self.needsRotation = true; animateScanBar(); // Select a new target (this will be handled in updateScanning on next update) } else if (newState === 'attacking') { // don't fire directly, consider a part of fire delay self.lastFireTime = Date.now(); // Entering attacking state self.debugText.tint = 0xFFA500; // Change tint to orange when attacking self.droneScanLaser.tint = 0xFFA500; // Change tint to orange when attacking // Start following the knight self.followKnight(); } }; self.init = function () { log("Drone initialized at position:", self.x, self.y); // Reset state flags self.isRotating = false; self.isMoving = false; self.needsRotation = true; // Start the animations animateScanBar(); loopScanLaserTint(); // Switch to scanning state to begin patrol self.switchState('scanning'); }; self.collectBonus = function collectBonus(bonus) { // Increase knight's health by 2 knight.health += 2; // Play knight-bonus sound LK.getSound('knight-bonus').play(); // Destroy the bonus bonus.destroy(); }; self.update = function () { if (self.state === 'scanning') { self.updateScanning(); } else if (self.state === 'attacking') { self.updateAttacking(); } // Update health bar width and color regardless of state self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 4 // Change health bar color based on health var healthPercentage = self.health / 4; // Assuming max health is 4 var red, green; // Transition from green to orange if (healthPercentage > 0.5) { red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255)); green = 255; } else { // Transition from orange to red red = 255; green = Math.min(255, Math.floor(healthPercentage * 2 * 255)); } self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color // Iterate over all bonuses in the middlegroundContainer for (var i = middlegroundContainer.children.length - 1; i >= 0; i--) { var child = middlegroundContainer.children[i]; if (child instanceof Bonus) { // Check if knight's boundingBox intersects with bonusLight if (child.active && knight.boundingBox.intersects(child.bonusLight)) { self.collectBonus(child); } } } // Update shadow position shadowDrone.x = self.shadowOffset.x * Math.cos(self.rotation); shadowDrone.y = self.shadowOffset.y * Math.sin(self.rotation); // Update droneScanBar width based on its rotation self.droneScanBar.width = self.droneScanLaser.width * (1 + 0.1 * Math.abs(Math.sin(self.droneScanBar.rotation))); }; // Function to perform a full 360° scan self.performFullScan = function (callback) { // Calculate duration based on a consistent rotation speed var scanRotationSpeed = 1.0; // Radians per second (slower for scanning) var fullRotationDuration = Math.PI * 2 / scanRotationSpeed * 1000; // ms // Store current rotation var startRotation = self.rotation; // Mark that we're rotating to prevent updateScanning from starting other rotations self.isRotating = true; log("Performing 360° scan"); // Perform a full 360° rotation tween(self, { rotation: startRotation + Math.PI * 2 // Full 360° rotation }, { duration: fullRotationDuration, easing: tween.linear, // Linear easing for constant speed onFinish: function onFinish() { // Clear rotation flag when done self.isRotating = false; log("Full scan complete"); // Call callback if provided if (callback) { callback(); } } }); }; // Handle scanning mode behavior self.updateScanning = function () { // Check for intersection with knight if (!isDied && self.droneScanBar.intersects(knight.boundingBox)) { self.switchState('attacking'); log("Drone switched to attacking mode!"); return; } // If no target is set, select one if (!self.target) { log("No target set, selecting a new one"); // If we just switched from attacking to scanning mode, do a 360° scan first if (self.justSwitchedFromAttacking) { self.justSwitchedFromAttacking = false; self.fire(); // Perform full scan before selecting a new target self.performFullScan(function () { // After scan is complete, select a new target self.selectTarget(); }); return; } // Otherwise just select a new target self.selectTarget(); return; } // Calculate direction and distance to target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If very close to target, select a new one if (distance < 10) { log("Reached target, selecting a new one"); self.selectTarget(); return; } // Calculate target angle var targetAngle = Math.atan2(dy, dx); // Check if rotation is needed var angleDiff = Math.abs(calculateAngleDiff(targetAngle, self.rotation)); // If rotation is needed (more than 0.1 radians difference or flag is set) if (angleDiff > 0.1 || self.needsRotation) { // Only start a new rotation if not already rotating if (!self.isRotating) { log("Rotating to face target"); self.isRotating = true; // Use the updateRotation function with callback to clear isRotating flag self.updateRotation(targetAngle, 600, function () { self.isRotating = false; self.needsRotation = false; log("Rotation complete"); }); } } // If rotation is close enough and we're not currently moving, start movement else if (!self.isMoving) { log("Starting movement to target"); self.isMoving = true; // Calculate duration based on distance var moveSpeed = 0.2; // Adjust this value to control drone speed var duration = distance / moveSpeed; // Start moving toward the target tween(self, { x: self.target.x, y: self.target.y }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { self.isMoving = false; log("Movement complete"); // Don't automatically select a new target, let updateScanning handle it } }); } }; // Handle attacking mode behavior self.updateAttacking = function () { var currentTime = Date.now(); // Fire using separate counter if (currentTime - self.lastFireTime > self.fireDelay) { // Fire every 1.2 seconds self.lastFireTime = currentTime; self.fire(); } var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Drone rotation logic - directly rotate the drone to face the knight // Only do smooth rotation tracking when not already moving to a new position //self.lastRotationTime = currentTime; // Calculate a smoother intermediate angle for natural tracking var currentAngle = self.rotation; var angleDiff = calculateAngleDiff(angle, currentAngle); // Only rotate a portion of the way to the target for smoother tracking // This creates a slight lag effect that looks more natural var partialAngle = currentAngle + angleDiff * 0.3; // Directly set rotation instead of using updateRotation self.rotation = partialAngle; //self.isRotating = false; // No need for callback since we're setting directly // Rotate droneScanBar to point at knight within limits var relativeAngle = calculateAngleDiff(angle, self.rotation); // Clamp the scan bar rotation to the maximum allowed range var maxRotation = Math.PI / 6.5; var clampedRotation = Math.max(-maxRotation, Math.min(maxRotation, relativeAngle)); // Set the scan bar rotation directly self.droneScanBar.rotation = clampedRotation; self.droneScanBar.alpha = 0.1; // First check if knight is still in range - do this every second if (currentTime - self.lastKnightCheckTime > 1000) { self.lastKnightCheckTime = currentTime; // Check if we can still see the knight if (!self.droneScanBar.intersects(knight.boundingBox)) { log("Knight lost! Returning to scanning mode."); self.switchState('scanning'); return; } log("Knight still in range at distance: " + distance); } // Position update logic - if we're not currently moving but need to if (isDied) { log("Knight is dead, returning to scanning mode."); self.switchState('scanning'); return; } if (!self.isMoving && distance > 550) { log("Knight moved - updating drone position"); // Move to a better position self.followKnight(); return; } }; self.followKnight = function () { log("followKnight..."); // Cancel any existing movement tweens tween.stop(self); // Calculate direction to knight var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Set the movement flag self.isMoving = true; self.isRotating = true; // After rotation is complete, start moving toward knight self.moveTowardKnight(); }; // Function to calculate distance to knight self.distanceToKnight = function () { var dx = knight.x - self.x; var dy = knight.y - self.y; return Math.sqrt(dx * dx + dy * dy); }; self.moveTowardKnight = function () { var dx = knight.x - self.x; var dy = knight.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If already within the desired distance, stop moving if (distance <= 500) { return; } // Calculate target position that's 500 units away from knight var ratio = (distance - 500) / distance; var targetX = self.x + dx * ratio; var targetY = self.y + dy * ratio; // Move toward the target tween(self, { x: targetX, y: targetY }, { duration: distance * 2, // Speed based on distance easing: tween.linear, onUpdate: function onUpdate() { // Update rotation to continuously face the knight during movement var currentDx = knight.x - self.x; var currentDy = knight.y - self.y; var currentDistance = Math.sqrt(currentDx * currentDx + currentDy * currentDy); // Only update rotation if not very close to the target if (currentDistance > 10) { var currentAngle = Math.atan2(currentDy, currentDx); // Calculate target rotation with a smaller adjustment for smoothness using the utility function var newRotation = self.rotation + calculateAngleDiff(currentAngle, self.rotation) * 0.1; // Create a separate quick tween for the rotation tween(self, { rotation: newRotation }, { duration: 600, // Very short duration for responsive updates easing: tween.linear }); } }, onFinish: function onFinish() { // Check if we're still in attacking mode before continuing to follow if (self.state === 'attacking') { // Check if knight has moved and we need to follow again if (self.distanceToKnight() > 500) { self.moveTowardKnight(); } } } }); }; self.updateRotation = function (targetAngle, maxDuration, callback) { maxDuration = maxDuration || 500; // Default max duration if not specified // Cancel any existing rotation tweens to prevent conflicts tween.stop(self, 'rotation'); // Normalize angles to be between -PI and PI for proper comparison var normalizedCurrentAngle = self.rotation % (2 * Math.PI); if (normalizedCurrentAngle > Math.PI) { normalizedCurrentAngle -= 2 * Math.PI; } if (normalizedCurrentAngle < -Math.PI) { normalizedCurrentAngle += 2 * Math.PI; } // Normalize target angle var normalizedTargetAngle = targetAngle % (2 * Math.PI); if (normalizedTargetAngle > Math.PI) { normalizedTargetAngle -= 2 * Math.PI; } if (normalizedTargetAngle < -Math.PI) { normalizedTargetAngle += 2 * Math.PI; } // Calculate the angle difference using the normalized angles var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle); var absDiff = Math.abs(angleDiff); // If the angle difference is greater than PI, rotate in the shorter direction if (absDiff > Math.PI) { if (angleDiff > 0) { angleDiff -= 2 * Math.PI; } else { angleDiff += 2 * Math.PI; } } // Calculate duration based on angle difference to achieve constant rotation speed // Use radians per second as the speed unit var rotationSpeed = 6; // Radians per second var duration = Math.min(absDiff / rotationSpeed * 1000, maxDuration); // Ensure a minimum duration for very small rotations duration = Math.max(duration, 50); // Calculate the target rotation by adding the angle difference to the current rotation var targetRotation = self.rotation + angleDiff; // Log the rotation parameters for debugging log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms"); // Perform the rotation tween tween(self, { rotation: targetRotation }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { log("Rotation complete"); if (callback) { callback(); } } }); }; self.takeHit = function (bullet) { // Play drone-hit sound LK.getSound('drone-hit').play(); // Flash the drone red to indicate damage droneGraphics.tint = 0xff0000; // Red tint // After a short delay, restore the original tint LK.setTimeout(function () { droneGraphics.tint = 0xFFFFFF; // Restore original color }, 200); // Log hit for debugging log("Drone hit by attack!"); // Reduce health when hit self.health -= 1; // Check if drone is destroyed if (self.health <= 0) { log("Drone destroyed!"); self.explode(); return; } // Switch to attacking state immediately if not already attacking if (self.state !== 'attacking') { self.switchState('attacking'); } // Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility self.lastKnightCheckTime = Date.now(); // Add knockback effect based on bullet direction var knockbackDistance = 50; var knockbackAngle = bullet.rotation; // Calculate potential new position var newX = self.x + Math.cos(knockbackAngle) * knockbackDistance; var newY = self.y + Math.sin(knockbackAngle) * knockbackDistance; // Check if new position would be too close to a corner var tooCloseToCorner = false; var cornerSafeDistance = 100; // Distance to stay away from corners for (var i = 0; i < corners.length; i++) { var dx = newX - corners[i].x; var dy = newY - corners[i].y; var distToCorner = Math.sqrt(dx * dx + dy * dy); if (distToCorner < cornerSafeDistance) { tooCloseToCorner = true; break; } } // If too close to a corner, adjust the knockback to push away from corner if (tooCloseToCorner) { // Find the center of the playfield var centerX = (corners[0].x + corners[3].x) / 2; var centerY = (corners[0].y + corners[3].y) / 2; // Calculate direction toward center var dxToCenter = centerX - self.x; var dyToCenter = centerY - self.y; var angleToCenter = Math.atan2(dyToCenter, dxToCenter); // Use this angle for knockback instead newX = self.x + Math.cos(angleToCenter) * knockbackDistance; newY = self.y + Math.sin(angleToCenter) * knockbackDistance; } // Clamp new position using corners newX = Math.max(corners[0].x + cornerSafeDistance, Math.min(newX, corners[3].x - cornerSafeDistance)); newY = Math.max(corners[0].y + cornerSafeDistance, Math.min(newY, corners[3].y - cornerSafeDistance)); // Apply knockback using tween tween(self, { x: newX, y: newY }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // After knockback, immediately rotate the drone toward the knight // Calculate angle to knight var dx = knight.x - self.x; var dy = knight.y - self.y; var angleToKnight = Math.atan2(dy, dx); // Calculate the angle difference for proper duration var normalizedCurrentAngle = self.rotation % (2 * Math.PI); if (normalizedCurrentAngle > Math.PI) { normalizedCurrentAngle -= 2 * Math.PI; } if (normalizedCurrentAngle < -Math.PI) { normalizedCurrentAngle += 2 * Math.PI; } var normalizedTargetAngle = angleToKnight % (2 * Math.PI); if (normalizedTargetAngle > Math.PI) { normalizedTargetAngle -= 2 * Math.PI; } if (normalizedTargetAngle < -Math.PI) { normalizedTargetAngle += 2 * Math.PI; } var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle); if (Math.abs(angleDiff) > Math.PI) { if (angleDiff > 0) { angleDiff -= 2 * Math.PI; } else { angleDiff += 2 * Math.PI; } } // Calculate duration based on angle difference var rotationSpeed = 6; // Radians per second var absDiff = Math.abs(angleDiff); var duration = Math.min(absDiff / rotationSpeed * 1000, 500); duration = Math.max(duration, 50); // Ensure minimum duration // Rotate drone to face knight with calculated duration tween(self, { rotation: self.rotation + angleDiff }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { // Switch to attacking state if not already attacking if (self.state !== 'attacking') { self.switchState('attacking'); } // Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility self.lastKnightCheckTime = Date.now(); } }); } }); }; self.explode = function () { // Simply destroy the drone tween.stop(self); // Stop all tweens on the drone var explosion = new Explosion(); explosion.init(self.x, self.y); middlegroundContainer.addChild(explosion); // Clear scan sound interval if it exists if (self.scanSoundInterval) { LK.clearInterval(self.scanSoundInterval); self.scanSoundInterval = null; } LK.getSound('drone-explode').play(); // Spawn a bonus at the drone's position var bonus = new Bonus(self.x, self.y); middlegroundContainer.addChild(bonus); self.destroy(); droneManager.start(); }; }); var Explosion = Container.expand(function () { var self = Container.call(this); for (var i = 0; i < 30; i++) { var particle = LK.getAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0 particle.scale.set(scale); var colors = [0xFF4500, 0xFF6347, 0xFFFFFF, 0xFFFF00, 0x8B0000]; // Array of colors: orange, red, white, yellow, dark red particle.tint = colors[Math.floor(Math.random() * colors.length)]; // Randomly choose a color from the array var angle = Math.random() * Math.PI * 2; // Random direction var speed = Math.random() * 5 + 100; // Random speed between 2 and 7 particle.vx = Math.cos(angle) * speed; particle.vy = Math.sin(angle) * speed; particle.rotation = angle; // Rotate particle in the direction of movement self.addChild(particle); } // Initialize explosion properties self.init = function (x, y) { self.x = x; self.y = y; self.scale.set(0.8); // Start small self.alpha = 1.0; // Fully visible // Animate the explosion var particles = self.children; var duration = 2000; // Duration for particles to move and fade out particles.forEach(function (particle) { tween(particle, { x: particle.x + particle.vx * duration / 1000, y: particle.y + particle.vy * duration / 1000, alpha: 0.0 }, { duration: duration, easing: tween.easeOut }); }); LK.setTimeout(function () { self.destroy(); // Remove explosion after animation }, duration); }; }); var ExplosionKnight = Container.expand(function () { var self = Container.call(this); for (var i = 0; i < 80; i++) { var particle = LK.getAsset('explosionParticle', { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0 particle.scale.set(scale); particle.tint = 0x000000; // Set particle color to black var speed = 100 + Math.random() * 100; // Random speed between 2 and 7 particle.vx = 0; // No horizontal movement particle.vy = -speed; // Negative vertical movement particle.rotation = Math.PI / 2; // Rotate by PI/2 self.addChild(particle); } // Initialize explosion properties self.init = function (x, y) { self.x = x; self.y = y; self.scale.set(0.8); // Start small self.alpha = 1.0; // Fully visible // Animate the explosion var particles = self.children; var duration = 2000; // Duration for particles to move and fade out particles.forEach(function (particle) { particle.x += -40 + Math.random() * 80; particle.y += -40 + Math.random() * 80; tween(particle, { x: particle.x + particle.vx * duration / 500, y: particle.y + particle.vy * duration / 500, alpha: 0.0, scaleY: 0 }, { duration: duration, easing: tween.easeOut }); }); LK.setTimeout(function () { self.destroy(); // Remove explosion after animation }, duration); }; }); var Knight = Container.expand(function () { var self = Container.call(this); self.health = isDebug ? 10 : 3; // Initialize health property with a value of 3 // Add boundingBox for collision detection self.boundingBox = LK.getAsset('boundingBox', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 150, y: -50 }); self.addChild(self.boundingBox); self.boundingBox.alpha = isDebug ? 0.5 : 0; // Set to false if you don't want it visible var directionMapping = { 'down-left': 'dir1', 'left': 'dir2', 'up-left': 'dir3', 'up': 'dir4', 'up-right': 'dir5', 'right': 'dir6', 'down-right': 'dir7', 'down': 'dir8' }; // Pre-create a list of assets for each direction var knightAssets = {}; var shadowAssets = {}; // Shadow assets for the knight var knightIdleAssets = {}; // Idle assets for the knight var shadowIdleAssets = {}; // Shadow idle assets for the knight var knightAttackAssets = {}; // Attack assets for the knight var shadowAttackAssets = {}; // Shadow attack assets for the knight var color = 0xFFFFFF; // Original blue / 0xff4d4d; // Red / 0x9aff9a; // Green // Initialize run animation assets for (var dir in directionMapping) { knightAssets[dir] = []; shadowAssets[dir] = []; // Initialize shadow assets array for each direction knightAttackAssets[dir] = []; // Initialize attack assets array for each direction shadowAttackAssets[dir] = []; // Initialize shadow attack assets array for each direction // Load run animation frames (8 frames) for (var i = 1; i <= 8; i++) { var frameNumber = i.toString().padStart(3, '0'); // Create knight sprite knightAssets[dir].push(LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5, tint: color })); // Create shadow sprite using the same assets but with modifications var shadowSprite = LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowSprite.alpha = 0.5; shadowSprite.tint = 0x000000; shadowSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians) shadowSprite.scale.y = 0.5; // Flatten the shadow vertically shadowAssets[dir].push(shadowSprite); } // Load attack animation frames (15 frames) for (var i = 1; i <= 15; i++) { var frameNumber = i.toString().padStart(3, '0'); // Create attack sprite knightAttackAssets[dir].push(LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5, tint: color })); // Create shadow attack sprite using the same assets but with modifications var shadowAttackSprite = LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowAttackSprite.alpha = 0.5; shadowAttackSprite.tint = 0x000000; shadowAttackSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians) shadowAttackSprite.scale.y = 0.5; // Flatten the shadow vertically shadowAttackAssets[dir].push(shadowAttackSprite); } // Initialize idle animation assets (one frame per direction) knightIdleAssets[dir] = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', { anchorX: 0.5, anchorY: 0.5, tint: color }); // Create shadow for idle animation var shadowIdleSprite = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow properties shadowIdleSprite.alpha = 0.5; shadowIdleSprite.tint = 0x000000; shadowIdleSprite.rotation = Math.PI / 12; shadowIdleSprite.scale.y = 0.5; shadowIdleAssets[dir] = shadowIdleSprite; } var currentFrame = 0; var animationSpeed = 0.2; // Controls how fast the animation plays var frameCounter = 0; var knightGraphics = knightIdleAssets['down']; // Initialize with the 'down' direction idle asset var shadowGraphics = shadowIdleAssets['down']; // Initialize shadow with the 'down' direction idle asset // Add health bar under the knight self.healthBar = LK.getAsset('healthBar', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 10, color: 0x00FF00 // Green color for health }); self.healthBar.y = 60; // Position the health bar under the knight self.addChild(self.healthBar); // Add shadow first (so it appears behind the knight) self.addChild(shadowGraphics); // Then add the knight self.addChild(knightGraphics); knightGraphics.anchor.set(0.5, 0.5); knightGraphics.visible = true; shadowGraphics.visible = true; // Position the shadow slightly offset from the knight var shadowOffsetX = -5; var shadowOffsetY = 20; shadowGraphics.x = shadowOffsetX; shadowGraphics.y = shadowOffsetY; self.lastDirection = 'down'; // Initialize lastDirection to track previous direction self.isMoving = false; // Track if the knight is currently moving self.isAttacking = false; // Track if the knight is currently attacking self.speed = 9; var distanceThreshold = 8; // Define a threshold to avoid small movements var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking self.update = function () { // Update health bar width based on knight's health self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 3 // Change health bar color based on health var healthPercentage = self.health / 3; // Assuming max health is 3 var red, green; // Transition from green to orange if (healthPercentage > 0.5) { red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255)); green = 255; } else { // Transition from orange to red red = 255; green = Math.min(255, Math.floor(healthPercentage * 2 * 255)); } self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color // Don't process movement if attacking if (isDied || self.health < 0 || self.isAttacking) { return; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > distanceThreshold) { // Set knight to moving state if not already moving if (!self.isMoving) { self.isMoving = true; self.switchAsset(self.lastDirection || 'down'); // Update asset to running animation } // Determine the primary direction based on the larger component (dx or dy) if (Math.abs(dx) > Math.abs(dy) + directionThreshold) { // Horizontal movement dominates if (dx > 0) { if (dy > directionThreshold) { self.moveToDirection('down-right'); } else if (dy < -directionThreshold) { self.moveToDirection('up-right'); } else { self.moveToDirection('right'); } } else { if (dy > directionThreshold) { self.moveToDirection('down-left'); } else if (dy < -directionThreshold) { self.moveToDirection('up-left'); } else { self.moveToDirection('left'); } } } else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) { // Vertical movement dominates if (dy > 0) { if (dx > directionThreshold) { self.moveToDirection('down-right'); } else if (dx < -directionThreshold) { self.moveToDirection('down-left'); } else { self.moveToDirection('down'); } } else { if (dx > directionThreshold) { self.moveToDirection('up-right'); } else if (dx < -directionThreshold) { self.moveToDirection('up-left'); } else { self.moveToDirection('up'); } } } else { // The difference between dx and dy is small, use diagonal movement if (dx > 0 && dy > 0) { self.moveToDirection('down-right'); } else if (dx > 0 && dy < 0) { self.moveToDirection('up-right'); } else if (dx < 0 && dy > 0) { self.moveToDirection('down-left'); } else if (dx < 0 && dy < 0) { self.moveToDirection('up-left'); } } } else { // Check if knight is not moving and distance to drone is less than 400 if (!self.isMoving) { if (droneManager && droneManager.drones.length > 0) { var drone = droneManager.drones[0]; // Assuming single drone for simplicity var dx = drone.x - self.x; var dy = drone.y - self.y; var distanceToDrone = Math.sqrt(dx * dx + dy * dy); } if (distanceToDrone < 400) { // Calculate angle to orient knight towards drone var angleToDrone = Math.atan2(dy, dx); var directionToDrone; if (angleToDrone >= -Math.PI / 8 && angleToDrone < Math.PI / 8) { directionToDrone = 'right'; } else if (angleToDrone >= Math.PI / 8 && angleToDrone < 3 * Math.PI / 8) { directionToDrone = 'down-right'; } else if (angleToDrone >= 3 * Math.PI / 8 && angleToDrone < 5 * Math.PI / 8) { directionToDrone = 'down'; } else if (angleToDrone >= 5 * Math.PI / 8 && angleToDrone < 7 * Math.PI / 8) { directionToDrone = 'down-left'; } else if (angleToDrone >= 7 * Math.PI / 8 || angleToDrone < -7 * Math.PI / 8) { directionToDrone = 'left'; } else if (angleToDrone >= -7 * Math.PI / 8 && angleToDrone < -5 * Math.PI / 8) { directionToDrone = 'up-left'; } else if (angleToDrone >= -5 * Math.PI / 8 && angleToDrone < -3 * Math.PI / 8) { directionToDrone = 'up'; } else if (angleToDrone >= -3 * Math.PI / 8 && angleToDrone < -Math.PI / 8) { directionToDrone = 'up-right'; } if (directionToDrone) { self.switchAsset(directionToDrone); } } } // Set knight to idle state if currently moving if (self.isMoving) { self.isMoving = false; self.switchAsset(self.lastDirection || 'down'); // Update asset to idle animation } } // Update knight animation self.updateAnimation(); }; self.switchAsset = function (direction) { if (!knightGraphics) { return; } // If currently attacking, don't switch assets if (isDied || self.health < 0 || self.isAttacking) { return; } // Detach current assets // Hide current assets knightGraphics.visible = false; shadowGraphics.visible = false; // Switch to new assets based on direction and movement state if (self.isMoving) { // Use running animation knightGraphics = knightAssets[direction][currentFrame]; shadowGraphics = shadowAssets[direction][currentFrame]; } else { // Use idle animation knightGraphics = knightIdleAssets[direction]; shadowGraphics = shadowIdleAssets[direction]; } self.addChild(shadowGraphics); self.addChild(knightGraphics); // Show new assets knightGraphics.visible = true; shadowGraphics.visible = true; // Position the shadow slightly offset from the knight shadowGraphics.x = shadowOffsetX; shadowGraphics.y = shadowOffsetY; // Update last direction self.lastDirection = direction; }; self.updateAnimation = function () { // If currently attacking, don't switch assets if (isDied || self.health < 0 || self.isAttacking) { return; } if (self.isMoving) { // Only update animation if moving frameCounter += animationSpeed; if (frameCounter >= 1) { frameCounter = 0; currentFrame = (currentFrame + 1) % 8; // Cycle through 8 frames if (self.lastDirection) { self.switchAsset(self.lastDirection); } } } else if (self.lastDirection) { // Make sure we're showing the idle animation when not moving self.switchAsset(self.lastDirection); } }; self.moveToDirection = function (direction) { if (isDied || self.health < 0 || self.isAttacking) { return; } if (self.lastDirection !== direction) { self.switchAsset(direction); self.lastDirection = direction; } switch (direction) { case 'up': self.y -= self.speed; break; case 'down': self.y += self.speed; break; case 'left': self.x -= self.speed; break; case 'right': self.x += self.speed; break; case 'up-left': self.x -= self.speed / Math.sqrt(2); self.y -= self.speed / Math.sqrt(2); break; case 'up-right': self.x += self.speed / Math.sqrt(2); self.y -= self.speed / Math.sqrt(2); break; case 'down-left': self.x -= self.speed / Math.sqrt(2); self.y += self.speed / Math.sqrt(2); break; case 'down-right': self.x += self.speed / Math.sqrt(2); self.y += self.speed / Math.sqrt(2); break; } }; self.takeHit = function (bullet) { if (isDied) { return; } // Flash the knight red to indicate damage self.tint = 0xff0000; // Red tint // After a short delay, restore the original tint LK.setTimeout(function () { self.tint = color; // Restore original color }, 200); // Optional: Add knockback effect based on bullet direction var knockbackDistance = 30; var knockbackAngle = bullet.rotation; // Apply knockback self.x += Math.cos(knockbackAngle) * knockbackDistance; self.y += Math.sin(knockbackAngle) * knockbackDistance; self.health--; if (self.health < 0) { self.dieAnim(); } else { // Play knight-hit sound LK.getSound('knight-hit').play(); } // Log hit for debugging log("Knight hit by laser beam!"); }; self.dieAnim = function () { // Animate knight tint to black LK.getSound('knight-die').play(); punchButton.visible = false; // Hide attackButton when the knight is dead if (isDied) { return; } isDied = true; tween(self, { tint: 0x000000 }, { duration: 1000, // 1 second duration easing: tween.easeInOut, onFinish: function onFinish() { var explosion = new ExplosionKnight(); explosion.init(self.x, self.y - 50); middlegroundContainer.addChild(explosion); LK.getSound('knight-killed').play(); self.destroy(); // Destroy knight after animation LK.setTimeout(function () { LK.showGameOver(); }, 3000); // Call game over after 2 seconds } }); }; self.attack = function () { // Check if already attacking if (isDied || self.isAttacking) { return; } // Play knight-attack sound LK.getSound('knight-attack').play(); self.isAttacking = true; log("Knight attacking!"); // Store current direction var direction = self.lastDirection || 'down'; // Get attack animation frames for current direction var attackFrames = knightAttackAssets[direction]; var shadowAttackFrames = shadowAttackAssets[direction]; // Hide current graphics // knightGraphics.visible = false; // shadowGraphics.visible = false; // Add all frames but make them invisible initially for (var i = 0; i < attackFrames.length; i++) { self.addChild(shadowAttackFrames[i]); self.addChild(attackFrames[i]); shadowAttackFrames[i].visible = false; attackFrames[i].visible = false; } // Create attack animation var currentFrame = 0; var attackInterval = LK.setInterval(function () { if (self.isDied) { return; } // Hide previous frame if it exists if (currentFrame > 0) { attackFrames[currentFrame - 1].visible = false; shadowAttackFrames[currentFrame - 1].visible = false; } // Hide current graphics knightGraphics.visible = false; shadowGraphics.visible = false; // Show current frame shadowAttackFrames[currentFrame].visible = true; attackFrames[currentFrame].visible = true; // Check for collision with drones (middle of the attack animation) if (currentFrame === Math.floor(attackFrames.length / 2)) { // Calculate attack range and direction var attackRange = 100; // Range of the knight's attack var attackDirection = direction; var attackAngle; // Determine attack angle based on direction if (attackDirection === 'right') { attackAngle = 0; } else if (attackDirection === 'down-right') { attackAngle = Math.PI / 4; } else if (attackDirection === 'down') { attackAngle = Math.PI / 2; } else if (attackDirection === 'down-left') { attackAngle = 3 * Math.PI / 4; } else if (attackDirection === 'left') { attackAngle = Math.PI; } else if (attackDirection === 'up-left') { attackAngle = 5 * Math.PI / 4; } else if (attackDirection === 'up') { attackAngle = 3 * Math.PI / 2; } else if (attackDirection === 'up-right') { attackAngle = 7 * Math.PI / 4; } // Calculate attack point in front of the knight var attackX = self.x + Math.cos(attackAngle) * attackRange; var attackY = self.y + Math.sin(attackAngle) * attackRange; // Check if any drone is within attack range for (var i = 0; i < droneManager.drones.length; i++) { var drone = droneManager.drones[i]; var dx = drone.x - attackX; var dy = drone.y - attackY; var distance = Math.sqrt(dx * dx + dy * dy); // If drone is within attack range, call its takeHit method if (distance < 120) { // Adjust hit radius as needed drone.takeHit({ rotation: attackAngle // Pass attack angle for knockback direction }); } } } // Move to next frame currentFrame++; // Check if animation is complete if (currentFrame >= attackFrames.length) { // Clear interval LK.clearInterval(attackInterval); // Hide all attack frames for (var i = 0; i < attackFrames.length; i++) { attackFrames[i].visible = false; shadowAttackFrames[i].visible = false; } // Show regular graphics again knightGraphics.visible = true; shadowGraphics.visible = true; // Reset attacking flag self.isAttacking = false; // Reset the global target position to knight's current position target.x = self.x; target.y = self.y; } }, 50); // 50ms per frame for a fast attack animation }; }); var LaserBeam = Container.expand(function () { var self = Container.call(this); var laserBeamGraphics = LK.getAsset('droneLaserBeam', { anchorX: 0.5, anchorY: 0.5, blendMode: 1 }); self.addChild(laserBeamGraphics); self.speed = 1500; // Speed of the laser beam in pixels per second self.fire = function (x, y, rotation) { self.x = x; self.y = y; self.rotation = rotation; middlegroundContainer.addChild(self); // Calculate the distance the laser beam will travel var distance = 3000; // Calculate the duration based on speed var duration = distance / self.speed * 1000; // Convert to milliseconds // Tween the laser beam to move forward tween(self, { x: self.x + Math.cos(self.rotation) * distance, y: self.y + Math.sin(self.rotation) * distance }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.destroy(); } }); }; self.update = function () { if (self.intersects(knight.boundingBox)) { knight.takeHit(self); self.destroy(); } }; }); var Room = Container.expand(function () { var self = Container.call(this); var background = LK.getAsset('backgroundWalls', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(background); self.x = 2048 / 2; self.y = 2732 / 2; }); var Target = Container.expand(function () { var self = Container.call(this); self.x = 0; self.y = 0; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 //Init game with black background }); /**** * Game Code ****/ // Utility function to calculate the angle difference function calculateAngleDiff(targetAngle, currentAngle) { var angleDiff = targetAngle - currentAngle; // Normalize angle difference to [-PI, PI] while (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } while (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } return angleDiff; } //<Write entity 'classes' with empty functions for important behavior here> //<Write imports for supported plugins here> //<Assets used in the game will automatically appear here> //<Write game logic code here, including initializing arrays and variables> var DroneManager = function DroneManager() { var self = this; self.droneCounter = 0; // Initialize droneCounter to track the number of drones self.drones = []; self.start = function () { // Clear previous drones from the scene and the drones array self.drones.forEach(function (drone) { drone.destroy(); }); self.drones = []; // Create the drone var drone = new Drone(self.droneCounter); // Pass the current level to the Drone constructor // Increment counter after drone creation so first drone is level 0 self.droneCounter++; // Check win condition after incrementing counter if (self.droneCounter > 3) { LK.getSound('knight-win').play(); // Play knight-win sound LK.setTimeout(function () { LK.showYouWin(); // Show win screen after 2000ms delay }, 2000); return; } // Explicitly set position before adding to the scene drone.x = Math.random() < 0.5 ? -1024 : 3072; drone.y = 1366; // Store the current target index //drone.currentTargetIndex = currentTargetIndex; // Set the initial target to the current corner position drone.target = { x: 1024, y: 1366 }; // Add to container and array middlegroundContainer.addChild(drone); self.drones.push(drone); // This ensures all properties are set before tweening begins LK.setTimeout(function () { drone.init(); }, 100); }; }; /**** * Global variables ****/ var isDebug = false; var isDied = false; // Global flag to track if the knight has died var gameStarted = false; var droneManager = new DroneManager(); var backgroundContainer; var middlegroundContainer; var foregroundContainer; var knight; var background; // Declare a global variable for the background var target; var punchButton; var runSoundInterval; var borderOffset = 380; var corners = [{ x: borderOffset, y: borderOffset }, { x: 2048 - borderOffset, y: borderOffset }, { x: borderOffset, y: 2732 - borderOffset }, { x: 2048 - borderOffset, y: 2732 - borderOffset }]; function log() { if (isDebug) { console.log.apply(console, arguments); } } function initializeGame() { // Create containers backgroundContainer = new Container(); middlegroundContainer = new Container(); foregroundContainer = new Container(); // Add containers to game game.addChild(backgroundContainer); game.addChild(middlegroundContainer); game.addChild(foregroundContainer); // Initialize background background = new Background(); // Use Background class instead of LK.getAsset backgroundContainer.addChild(background); // Initialize room var room = new Room(); backgroundContainer.addChild(room); // Initialize knight knight = new Knight(); knight.x = 2048 / 2; knight.y = 2732 / 2; middlegroundContainer.addChild(knight); // Initialize target target = new Target(); target.x = knight.x; target.y = knight.y; foregroundContainer.addChild(target); // Initialize punch button punchButton = new ButtonPunch(); punchButton.x = 2048 - 220; punchButton.y = 220; foregroundContainer.addChild(punchButton); // Initialize sound runSoundInterval = LK.setInterval(playRunSound, 350); droneManager = new DroneManager(); LK.setTimeout(function () { gameStarted = true; droneManager.start(); }, 1000); } function playRunSound() { if (knight.isMoving) { LK.getSound('knight-run').play(); } } initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Background = Container.expand(function () {
var self = Container.call(this);
var backgroundGraphics = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(backgroundGraphics);
self.x = 2048 / 2;
self.y = 2732 / 2;
// Move the down handler to the Background class
self.down = function (x, y, obj) {
var game_position = game.toLocal(obj.global);
target.x = Math.max(corners[0].x, Math.min(game_position.x, corners[3].x));
target.y = Math.max(corners[0].y, Math.min(game_position.y, corners[3].y));
};
});
var Bonus = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Create shadow for bonus
var shadowBonus = LK.getAsset('bonusHealth', {
anchorX: 0.5,
anchorY: 0.5
});
shadowBonus.alpha = 0.5;
shadowBonus.tint = 0x000000;
shadowBonus.x = 10; // Offset shadow position
shadowBonus.y = 10; // Offset shadow position
self.addChild(shadowBonus);
self.active = false;
var bonusGraphics = LK.getAsset('bonusHealth', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(bonusGraphics);
self.bonusLight = LK.getAsset('bonusHealthLight', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FF00
});
self.addChild(self.bonusLight);
self.animLight = function () {
function loopTint() {
tween(self.bonusLight, {
tint: 0xFFFFFF
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.bonusLight, {
tint: 0x00FF00
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: loopTint
});
}
});
}
loopTint();
};
self.animLight();
self.animMove = function () {
function loopMove() {
tween(self, {
y: self.y + 10
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y - 10
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: loopMove
});
}
});
}
loopMove();
};
self.throwToCorner = function () {
// Calculate a random position within range instead of using corners
var minDistance = 1024;
var maxDistance = 1480;
// Generate random angle
var randomAngle = Math.random() * Math.PI * 2;
// Calculate random distance within range
var randomDistance = minDistance + Math.random() * (maxDistance - minDistance);
// Calculate target position
var targetX = self.x + Math.cos(randomAngle) * randomDistance;
var targetY = self.y + Math.sin(randomAngle) * randomDistance;
// Ensure target is within the playable area (within corners)
targetX = Math.max(borderOffset, Math.min(2048 - borderOffset, targetX));
targetY = Math.max(borderOffset, Math.min(2732 - borderOffset, targetY));
// Calculate control point for parabolic motion
var controlX = (self.x + targetX) / 2;
var controlY = Math.min(self.y, targetY) - 500; // Control point above the line for a parabolic effect
// Initial position
var startX = self.x;
var startY = self.y;
// Create a tween for parabolic motion
tween(self, {
x: targetX,
y: targetY
}, {
duration: 1200,
easing: tween.easeOutQuad,
onUpdate: function onUpdate() {
// Calculate parabolic path using quadratic Bezier curve formula
var t = this.progress;
self.x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * targetX;
self.y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * targetY;
// Add rotation for visual effect
self.rotation = t * Math.PI * 3; // 1.5 full rotations during flight
},
onFinish: function onFinish() {
self.rotation = 0; // Reset rotation
self.active = true; // Activate bonus after reaching the corner
self.animMove();
}
});
};
self.throwToCorner(); // Call the function to start the animation
});
var ButtonPunch = Container.expand(function () {
var self = Container.call(this);
var buttonGraphics = LK.getAsset('buttonPunch', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(buttonGraphics);
// Add click/tap handler
self.down = function () {
// Trigger knight attack when button is pressed
if (!isDied && knight && knight.health >= 0 && !knight.isAttacking) {
knight.attack();
}
};
});
// Import the tween plugin
var Drone = Container.expand(function (level) {
var self = Container.call(this);
self.level = level || 0; // Set level to the passed parameter or default to 0
self.health = (isDebug ? 2 : 4) + self.level * 2; // Add level*2 to drone's health
var droneWidth = 200;
// Increase size based on level
var levelGrowth = self.level * self.level * 80;
self.droneScanLaser = LK.getAsset('droneScanLaser', {
anchorX: 0,
anchorY: 0.5
});
self.droneScanLaser.visible = true; // Always visible
self.droneScanLaser.x = droneWidth / 2;
self.droneScanLaser.alpha = 0.3; // Set alpha to 0.3
self.droneScanLaser.width += levelGrowth;
self.droneScanLaser.height += levelGrowth;
self.addChild(self.droneScanLaser);
self.droneScanBar = LK.getAsset('droneScanBar', {
anchorX: 0,
anchorY: 0.5
});
self.droneScanBar.x = droneWidth / 2;
self.droneScanBar.blendMode = 3;
self.droneScanBar.width = self.droneScanLaser.width;
self.addChild(self.droneScanBar);
var droneGraphics = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5,
width: droneWidth,
height: droneWidth
});
var shadowDrone = LK.getAsset('drone', {
anchorX: 0.5,
anchorY: 0.5,
width: droneWidth,
height: droneWidth
});
self.shadowOffset = {
x: -30,
y: 40
}; // Define shadow offset property
shadowDrone.alpha = 0.5;
shadowDrone.tint = 0x000000;
shadowDrone.x = -10;
shadowDrone.y = 40;
self.addChild(shadowDrone);
self.addChild(droneGraphics);
// Add health bar under the drone
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 10,
color: 0x00FF00 // Green color for health
});
self.healthBar.y = droneWidth / 2 + 20; // Position the health bar under the drone
self.addChild(self.healthBar);
function loopScanLaserTint() {
if (self.state !== 'scanning') {
return;
}
tween(self.droneScanLaser, {
tint: 0x00FF00
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.droneScanLaser, {
tint: 0x00AA00
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: loopScanLaserTint // Recursively call to loop
});
}
});
}
function animateScanBar() {
if (self.state !== 'scanning') {
return;
}
tween(self.droneScanBar, {
alpha: 0.3,
rotation: Math.PI / 6.75
}, {
duration: 600 - self.level * 50,
//{V} // Decrease duration to increase frequency with level
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.droneScanBar, {
alpha: 0.6,
rotation: -Math.PI / 6.75
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: animateScanBar // Recursively call to loop
}); // Ensure the tween is started
}
}); // Ensure the tween is started
}
self.speed = 5;
self.state = ''; // Possible states: scanning, attacking
// Add debug text to track drone state
self.debugText = new Text2(self.state, {
size: 50,
fill: 0xFFFFFF
});
self.debugText.x = -droneGraphics.width / 2;
self.debugText.y = -droneGraphics.height / 2 - 50;
self.debugText.visible = isDebug; // Set visibility based on debug mode
self.addChild(self.debugText);
self.fireRange = 300; // Example fire range value
self.fireDelay = 1200;
self.currentTargetIndex = -1;
self.target = {
x: 0,
y: 0
};
self.lastKnightCheckTime = 0; // Track when we last checked for knight intersection
self.lastFireTime = 0; // Track when we last fired at the knight
self.lastRotationTime = 0; // Track the last time we updated rotation
self.isRotating = false; // Flag to track if we're currently rotating
self.isMoving = false; // Flag to track if we're currently moving
self.needsRotation = false; // Flag to track if we need to rotate before moving
self.justSwitchedFromAttacking = false; // Flag to track if we just switched from attacking mode
// Function to select a new random target
self.selectTarget = function () {
log("selectTarget...:");
var newTargetIndex;
do {
newTargetIndex = Math.floor(Math.random() * corners.length);
} while (newTargetIndex === self.currentTargetIndex);
self.currentTargetIndex = newTargetIndex;
self.target = corners[newTargetIndex];
log("New target selected at position:", self.target.x, self.target.y);
// Set a flag to indicate the target is new and needs rotation first
self.needsRotation = true;
// Return the new target (not used currently but could be useful)
return self.target;
};
// Function to fire a laser beam
self.fire = function () {
log("Drone firing laser beam...");
LK.getSound('drone-beam').play();
var laserBeam = new LaserBeam();
laserBeam.fire(self.x, self.y, self.rotation);
};
// Function to handle state transitions
self.switchState = function (newState) {
// Don't do anything if the state isn't changing
if (self.state === newState) {
return;
}
log("Switching drone state from " + self.state + " to " + newState);
// Stop any existing tweens to ensure clean state transition
tween.stop(self);
// Clear scan sound interval if it exists
if (self.scanSoundInterval) {
LK.clearInterval(self.scanSoundInterval);
self.scanSoundInterval = null;
}
// Reset movement flags
self.isRotating = false;
self.isMoving = false;
self.justSwitchedFromAttacking = newState === 'scanning' && self.state === 'attacking';
// Update the state
self.state = newState;
self.debugText.setText(self.state); // Update debug text with current state
// Handle entry actions for new state
if (newState === 'scanning') {
// Play drone-scan sound
self.scanSoundInterval = LK.setInterval(function () {
LK.getSound('drone-scan').play();
}, 1000);
// Entering scanning state
self.debugText.tint = 0xFFFFFF; // Reset tint to white for other states
self.droneScanLaser.tint = 0x00FF00; // Reset tint to green for scanning mode
// Clear current target and set flag to select a new one
//self.target = null;
//self.needsRotation = true;
animateScanBar();
// Select a new target (this will be handled in updateScanning on next update)
} else if (newState === 'attacking') {
// don't fire directly, consider a part of fire delay
self.lastFireTime = Date.now();
// Entering attacking state
self.debugText.tint = 0xFFA500; // Change tint to orange when attacking
self.droneScanLaser.tint = 0xFFA500; // Change tint to orange when attacking
// Start following the knight
self.followKnight();
}
};
self.init = function () {
log("Drone initialized at position:", self.x, self.y);
// Reset state flags
self.isRotating = false;
self.isMoving = false;
self.needsRotation = true;
// Start the animations
animateScanBar();
loopScanLaserTint();
// Switch to scanning state to begin patrol
self.switchState('scanning');
};
self.collectBonus = function collectBonus(bonus) {
// Increase knight's health by 2
knight.health += 2;
// Play knight-bonus sound
LK.getSound('knight-bonus').play();
// Destroy the bonus
bonus.destroy();
};
self.update = function () {
if (self.state === 'scanning') {
self.updateScanning();
} else if (self.state === 'attacking') {
self.updateAttacking();
}
// Update health bar width and color regardless of state
self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 4
// Change health bar color based on health
var healthPercentage = self.health / 4; // Assuming max health is 4
var red, green;
// Transition from green to orange
if (healthPercentage > 0.5) {
red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
green = 255;
} else {
// Transition from orange to red
red = 255;
green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
}
self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
// Iterate over all bonuses in the middlegroundContainer
for (var i = middlegroundContainer.children.length - 1; i >= 0; i--) {
var child = middlegroundContainer.children[i];
if (child instanceof Bonus) {
// Check if knight's boundingBox intersects with bonusLight
if (child.active && knight.boundingBox.intersects(child.bonusLight)) {
self.collectBonus(child);
}
}
}
// Update shadow position
shadowDrone.x = self.shadowOffset.x * Math.cos(self.rotation);
shadowDrone.y = self.shadowOffset.y * Math.sin(self.rotation);
// Update droneScanBar width based on its rotation
self.droneScanBar.width = self.droneScanLaser.width * (1 + 0.1 * Math.abs(Math.sin(self.droneScanBar.rotation)));
};
// Function to perform a full 360° scan
self.performFullScan = function (callback) {
// Calculate duration based on a consistent rotation speed
var scanRotationSpeed = 1.0; // Radians per second (slower for scanning)
var fullRotationDuration = Math.PI * 2 / scanRotationSpeed * 1000; // ms
// Store current rotation
var startRotation = self.rotation;
// Mark that we're rotating to prevent updateScanning from starting other rotations
self.isRotating = true;
log("Performing 360° scan");
// Perform a full 360° rotation
tween(self, {
rotation: startRotation + Math.PI * 2 // Full 360° rotation
}, {
duration: fullRotationDuration,
easing: tween.linear,
// Linear easing for constant speed
onFinish: function onFinish() {
// Clear rotation flag when done
self.isRotating = false;
log("Full scan complete");
// Call callback if provided
if (callback) {
callback();
}
}
});
};
// Handle scanning mode behavior
self.updateScanning = function () {
// Check for intersection with knight
if (!isDied && self.droneScanBar.intersects(knight.boundingBox)) {
self.switchState('attacking');
log("Drone switched to attacking mode!");
return;
}
// If no target is set, select one
if (!self.target) {
log("No target set, selecting a new one");
// If we just switched from attacking to scanning mode, do a 360° scan first
if (self.justSwitchedFromAttacking) {
self.justSwitchedFromAttacking = false;
self.fire();
// Perform full scan before selecting a new target
self.performFullScan(function () {
// After scan is complete, select a new target
self.selectTarget();
});
return;
}
// Otherwise just select a new target
self.selectTarget();
return;
}
// Calculate direction and distance to target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If very close to target, select a new one
if (distance < 10) {
log("Reached target, selecting a new one");
self.selectTarget();
return;
}
// Calculate target angle
var targetAngle = Math.atan2(dy, dx);
// Check if rotation is needed
var angleDiff = Math.abs(calculateAngleDiff(targetAngle, self.rotation));
// If rotation is needed (more than 0.1 radians difference or flag is set)
if (angleDiff > 0.1 || self.needsRotation) {
// Only start a new rotation if not already rotating
if (!self.isRotating) {
log("Rotating to face target");
self.isRotating = true;
// Use the updateRotation function with callback to clear isRotating flag
self.updateRotation(targetAngle, 600, function () {
self.isRotating = false;
self.needsRotation = false;
log("Rotation complete");
});
}
}
// If rotation is close enough and we're not currently moving, start movement
else if (!self.isMoving) {
log("Starting movement to target");
self.isMoving = true;
// Calculate duration based on distance
var moveSpeed = 0.2; // Adjust this value to control drone speed
var duration = distance / moveSpeed;
// Start moving toward the target
tween(self, {
x: self.target.x,
y: self.target.y
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
log("Movement complete");
// Don't automatically select a new target, let updateScanning handle it
}
});
}
};
// Handle attacking mode behavior
self.updateAttacking = function () {
var currentTime = Date.now();
// Fire using separate counter
if (currentTime - self.lastFireTime > self.fireDelay) {
// Fire every 1.2 seconds
self.lastFireTime = currentTime;
self.fire();
}
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Drone rotation logic - directly rotate the drone to face the knight
// Only do smooth rotation tracking when not already moving to a new position
//self.lastRotationTime = currentTime;
// Calculate a smoother intermediate angle for natural tracking
var currentAngle = self.rotation;
var angleDiff = calculateAngleDiff(angle, currentAngle);
// Only rotate a portion of the way to the target for smoother tracking
// This creates a slight lag effect that looks more natural
var partialAngle = currentAngle + angleDiff * 0.3;
// Directly set rotation instead of using updateRotation
self.rotation = partialAngle;
//self.isRotating = false; // No need for callback since we're setting directly
// Rotate droneScanBar to point at knight within limits
var relativeAngle = calculateAngleDiff(angle, self.rotation);
// Clamp the scan bar rotation to the maximum allowed range
var maxRotation = Math.PI / 6.5;
var clampedRotation = Math.max(-maxRotation, Math.min(maxRotation, relativeAngle));
// Set the scan bar rotation directly
self.droneScanBar.rotation = clampedRotation;
self.droneScanBar.alpha = 0.1;
// First check if knight is still in range - do this every second
if (currentTime - self.lastKnightCheckTime > 1000) {
self.lastKnightCheckTime = currentTime;
// Check if we can still see the knight
if (!self.droneScanBar.intersects(knight.boundingBox)) {
log("Knight lost! Returning to scanning mode.");
self.switchState('scanning');
return;
}
log("Knight still in range at distance: " + distance);
}
// Position update logic - if we're not currently moving but need to
if (isDied) {
log("Knight is dead, returning to scanning mode.");
self.switchState('scanning');
return;
}
if (!self.isMoving && distance > 550) {
log("Knight moved - updating drone position");
// Move to a better position
self.followKnight();
return;
}
};
self.followKnight = function () {
log("followKnight...");
// Cancel any existing movement tweens
tween.stop(self);
// Calculate direction to knight
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Set the movement flag
self.isMoving = true;
self.isRotating = true;
// After rotation is complete, start moving toward knight
self.moveTowardKnight();
};
// Function to calculate distance to knight
self.distanceToKnight = function () {
var dx = knight.x - self.x;
var dy = knight.y - self.y;
return Math.sqrt(dx * dx + dy * dy);
};
self.moveTowardKnight = function () {
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If already within the desired distance, stop moving
if (distance <= 500) {
return;
}
// Calculate target position that's 500 units away from knight
var ratio = (distance - 500) / distance;
var targetX = self.x + dx * ratio;
var targetY = self.y + dy * ratio;
// Move toward the target
tween(self, {
x: targetX,
y: targetY
}, {
duration: distance * 2,
// Speed based on distance
easing: tween.linear,
onUpdate: function onUpdate() {
// Update rotation to continuously face the knight during movement
var currentDx = knight.x - self.x;
var currentDy = knight.y - self.y;
var currentDistance = Math.sqrt(currentDx * currentDx + currentDy * currentDy);
// Only update rotation if not very close to the target
if (currentDistance > 10) {
var currentAngle = Math.atan2(currentDy, currentDx);
// Calculate target rotation with a smaller adjustment for smoothness using the utility function
var newRotation = self.rotation + calculateAngleDiff(currentAngle, self.rotation) * 0.1;
// Create a separate quick tween for the rotation
tween(self, {
rotation: newRotation
}, {
duration: 600,
// Very short duration for responsive updates
easing: tween.linear
});
}
},
onFinish: function onFinish() {
// Check if we're still in attacking mode before continuing to follow
if (self.state === 'attacking') {
// Check if knight has moved and we need to follow again
if (self.distanceToKnight() > 500) {
self.moveTowardKnight();
}
}
}
});
};
self.updateRotation = function (targetAngle, maxDuration, callback) {
maxDuration = maxDuration || 500; // Default max duration if not specified
// Cancel any existing rotation tweens to prevent conflicts
tween.stop(self, 'rotation');
// Normalize angles to be between -PI and PI for proper comparison
var normalizedCurrentAngle = self.rotation % (2 * Math.PI);
if (normalizedCurrentAngle > Math.PI) {
normalizedCurrentAngle -= 2 * Math.PI;
}
if (normalizedCurrentAngle < -Math.PI) {
normalizedCurrentAngle += 2 * Math.PI;
}
// Normalize target angle
var normalizedTargetAngle = targetAngle % (2 * Math.PI);
if (normalizedTargetAngle > Math.PI) {
normalizedTargetAngle -= 2 * Math.PI;
}
if (normalizedTargetAngle < -Math.PI) {
normalizedTargetAngle += 2 * Math.PI;
}
// Calculate the angle difference using the normalized angles
var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle);
var absDiff = Math.abs(angleDiff);
// If the angle difference is greater than PI, rotate in the shorter direction
if (absDiff > Math.PI) {
if (angleDiff > 0) {
angleDiff -= 2 * Math.PI;
} else {
angleDiff += 2 * Math.PI;
}
}
// Calculate duration based on angle difference to achieve constant rotation speed
// Use radians per second as the speed unit
var rotationSpeed = 6; // Radians per second
var duration = Math.min(absDiff / rotationSpeed * 1000, maxDuration);
// Ensure a minimum duration for very small rotations
duration = Math.max(duration, 50);
// Calculate the target rotation by adding the angle difference to the current rotation
var targetRotation = self.rotation + angleDiff;
// Log the rotation parameters for debugging
log("Rotating: angle diff=" + absDiff + ", duration=" + duration + "ms");
// Perform the rotation tween
tween(self, {
rotation: targetRotation
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
log("Rotation complete");
if (callback) {
callback();
}
}
});
};
self.takeHit = function (bullet) {
// Play drone-hit sound
LK.getSound('drone-hit').play();
// Flash the drone red to indicate damage
droneGraphics.tint = 0xff0000; // Red tint
// After a short delay, restore the original tint
LK.setTimeout(function () {
droneGraphics.tint = 0xFFFFFF; // Restore original color
}, 200);
// Log hit for debugging
log("Drone hit by attack!");
// Reduce health when hit
self.health -= 1;
// Check if drone is destroyed
if (self.health <= 0) {
log("Drone destroyed!");
self.explode();
return;
}
// Switch to attacking state immediately if not already attacking
if (self.state !== 'attacking') {
self.switchState('attacking');
}
// Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility
self.lastKnightCheckTime = Date.now();
// Add knockback effect based on bullet direction
var knockbackDistance = 50;
var knockbackAngle = bullet.rotation;
// Calculate potential new position
var newX = self.x + Math.cos(knockbackAngle) * knockbackDistance;
var newY = self.y + Math.sin(knockbackAngle) * knockbackDistance;
// Check if new position would be too close to a corner
var tooCloseToCorner = false;
var cornerSafeDistance = 100; // Distance to stay away from corners
for (var i = 0; i < corners.length; i++) {
var dx = newX - corners[i].x;
var dy = newY - corners[i].y;
var distToCorner = Math.sqrt(dx * dx + dy * dy);
if (distToCorner < cornerSafeDistance) {
tooCloseToCorner = true;
break;
}
}
// If too close to a corner, adjust the knockback to push away from corner
if (tooCloseToCorner) {
// Find the center of the playfield
var centerX = (corners[0].x + corners[3].x) / 2;
var centerY = (corners[0].y + corners[3].y) / 2;
// Calculate direction toward center
var dxToCenter = centerX - self.x;
var dyToCenter = centerY - self.y;
var angleToCenter = Math.atan2(dyToCenter, dxToCenter);
// Use this angle for knockback instead
newX = self.x + Math.cos(angleToCenter) * knockbackDistance;
newY = self.y + Math.sin(angleToCenter) * knockbackDistance;
}
// Clamp new position using corners
newX = Math.max(corners[0].x + cornerSafeDistance, Math.min(newX, corners[3].x - cornerSafeDistance));
newY = Math.max(corners[0].y + cornerSafeDistance, Math.min(newY, corners[3].y - cornerSafeDistance));
// Apply knockback using tween
tween(self, {
x: newX,
y: newY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// After knockback, immediately rotate the drone toward the knight
// Calculate angle to knight
var dx = knight.x - self.x;
var dy = knight.y - self.y;
var angleToKnight = Math.atan2(dy, dx);
// Calculate the angle difference for proper duration
var normalizedCurrentAngle = self.rotation % (2 * Math.PI);
if (normalizedCurrentAngle > Math.PI) {
normalizedCurrentAngle -= 2 * Math.PI;
}
if (normalizedCurrentAngle < -Math.PI) {
normalizedCurrentAngle += 2 * Math.PI;
}
var normalizedTargetAngle = angleToKnight % (2 * Math.PI);
if (normalizedTargetAngle > Math.PI) {
normalizedTargetAngle -= 2 * Math.PI;
}
if (normalizedTargetAngle < -Math.PI) {
normalizedTargetAngle += 2 * Math.PI;
}
var angleDiff = calculateAngleDiff(normalizedTargetAngle, normalizedCurrentAngle);
if (Math.abs(angleDiff) > Math.PI) {
if (angleDiff > 0) {
angleDiff -= 2 * Math.PI;
} else {
angleDiff += 2 * Math.PI;
}
}
// Calculate duration based on angle difference
var rotationSpeed = 6; // Radians per second
var absDiff = Math.abs(angleDiff);
var duration = Math.min(absDiff / rotationSpeed * 1000, 500);
duration = Math.max(duration, 50); // Ensure minimum duration
// Rotate drone to face knight with calculated duration
tween(self, {
rotation: self.rotation + angleDiff
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Switch to attacking state if not already attacking
if (self.state !== 'attacking') {
self.switchState('attacking');
}
// Reset the lastKnightCheckTime to ensure we don't immediately check for knight visibility
self.lastKnightCheckTime = Date.now();
}
});
}
});
};
self.explode = function () {
// Simply destroy the drone
tween.stop(self); // Stop all tweens on the drone
var explosion = new Explosion();
explosion.init(self.x, self.y);
middlegroundContainer.addChild(explosion);
// Clear scan sound interval if it exists
if (self.scanSoundInterval) {
LK.clearInterval(self.scanSoundInterval);
self.scanSoundInterval = null;
}
LK.getSound('drone-explode').play();
// Spawn a bonus at the drone's position
var bonus = new Bonus(self.x, self.y);
middlegroundContainer.addChild(bonus);
self.destroy();
droneManager.start();
};
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
for (var i = 0; i < 30; i++) {
var particle = LK.getAsset('explosionParticle', {
anchorX: 0.5,
anchorY: 0.5
});
var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0
particle.scale.set(scale);
var colors = [0xFF4500, 0xFF6347, 0xFFFFFF, 0xFFFF00, 0x8B0000]; // Array of colors: orange, red, white, yellow, dark red
particle.tint = colors[Math.floor(Math.random() * colors.length)]; // Randomly choose a color from the array
var angle = Math.random() * Math.PI * 2; // Random direction
var speed = Math.random() * 5 + 100; // Random speed between 2 and 7
particle.vx = Math.cos(angle) * speed;
particle.vy = Math.sin(angle) * speed;
particle.rotation = angle; // Rotate particle in the direction of movement
self.addChild(particle);
}
// Initialize explosion properties
self.init = function (x, y) {
self.x = x;
self.y = y;
self.scale.set(0.8); // Start small
self.alpha = 1.0; // Fully visible
// Animate the explosion
var particles = self.children;
var duration = 2000; // Duration for particles to move and fade out
particles.forEach(function (particle) {
tween(particle, {
x: particle.x + particle.vx * duration / 1000,
y: particle.y + particle.vy * duration / 1000,
alpha: 0.0
}, {
duration: duration,
easing: tween.easeOut
});
});
LK.setTimeout(function () {
self.destroy(); // Remove explosion after animation
}, duration);
};
});
var ExplosionKnight = Container.expand(function () {
var self = Container.call(this);
for (var i = 0; i < 80; i++) {
var particle = LK.getAsset('explosionParticle', {
anchorX: 0.5,
anchorY: 0.5
});
var scale = Math.random() * 0.5 + 0.5; // Random scale between 0.5 and 1.0
particle.scale.set(scale);
particle.tint = 0x000000; // Set particle color to black
var speed = 100 + Math.random() * 100; // Random speed between 2 and 7
particle.vx = 0; // No horizontal movement
particle.vy = -speed; // Negative vertical movement
particle.rotation = Math.PI / 2; // Rotate by PI/2
self.addChild(particle);
}
// Initialize explosion properties
self.init = function (x, y) {
self.x = x;
self.y = y;
self.scale.set(0.8); // Start small
self.alpha = 1.0; // Fully visible
// Animate the explosion
var particles = self.children;
var duration = 2000; // Duration for particles to move and fade out
particles.forEach(function (particle) {
particle.x += -40 + Math.random() * 80;
particle.y += -40 + Math.random() * 80;
tween(particle, {
x: particle.x + particle.vx * duration / 500,
y: particle.y + particle.vy * duration / 500,
alpha: 0.0,
scaleY: 0
}, {
duration: duration,
easing: tween.easeOut
});
});
LK.setTimeout(function () {
self.destroy(); // Remove explosion after animation
}, duration);
};
});
var Knight = Container.expand(function () {
var self = Container.call(this);
self.health = isDebug ? 10 : 3; // Initialize health property with a value of 3
// Add boundingBox for collision detection
self.boundingBox = LK.getAsset('boundingBox', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 150,
y: -50
});
self.addChild(self.boundingBox);
self.boundingBox.alpha = isDebug ? 0.5 : 0; // Set to false if you don't want it visible
var directionMapping = {
'down-left': 'dir1',
'left': 'dir2',
'up-left': 'dir3',
'up': 'dir4',
'up-right': 'dir5',
'right': 'dir6',
'down-right': 'dir7',
'down': 'dir8'
};
// Pre-create a list of assets for each direction
var knightAssets = {};
var shadowAssets = {}; // Shadow assets for the knight
var knightIdleAssets = {}; // Idle assets for the knight
var shadowIdleAssets = {}; // Shadow idle assets for the knight
var knightAttackAssets = {}; // Attack assets for the knight
var shadowAttackAssets = {}; // Shadow attack assets for the knight
var color = 0xFFFFFF; // Original blue / 0xff4d4d; // Red / 0x9aff9a; // Green
// Initialize run animation assets
for (var dir in directionMapping) {
knightAssets[dir] = [];
shadowAssets[dir] = []; // Initialize shadow assets array for each direction
knightAttackAssets[dir] = []; // Initialize attack assets array for each direction
shadowAttackAssets[dir] = []; // Initialize shadow attack assets array for each direction
// Load run animation frames (8 frames)
for (var i = 1; i <= 8; i++) {
var frameNumber = i.toString().padStart(3, '0');
// Create knight sprite
knightAssets[dir].push(LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5,
tint: color
}));
// Create shadow sprite using the same assets but with modifications
var shadowSprite = LK.getAsset('knight-run-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowSprite.alpha = 0.5;
shadowSprite.tint = 0x000000;
shadowSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians)
shadowSprite.scale.y = 0.5; // Flatten the shadow vertically
shadowAssets[dir].push(shadowSprite);
}
// Load attack animation frames (15 frames)
for (var i = 1; i <= 15; i++) {
var frameNumber = i.toString().padStart(3, '0');
// Create attack sprite
knightAttackAssets[dir].push(LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5,
tint: color
}));
// Create shadow attack sprite using the same assets but with modifications
var shadowAttackSprite = LK.getAsset('knight-attack-' + directionMapping[dir] + '-' + frameNumber, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowAttackSprite.alpha = 0.5;
shadowAttackSprite.tint = 0x000000;
shadowAttackSprite.rotation = Math.PI / 12; // Rotate by 15 degrees (π/12 radians)
shadowAttackSprite.scale.y = 0.5; // Flatten the shadow vertically
shadowAttackAssets[dir].push(shadowAttackSprite);
}
// Initialize idle animation assets (one frame per direction)
knightIdleAssets[dir] = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', {
anchorX: 0.5,
anchorY: 0.5,
tint: color
});
// Create shadow for idle animation
var shadowIdleSprite = LK.getAsset('knight-idle-' + directionMapping[dir] + '-001', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow properties
shadowIdleSprite.alpha = 0.5;
shadowIdleSprite.tint = 0x000000;
shadowIdleSprite.rotation = Math.PI / 12;
shadowIdleSprite.scale.y = 0.5;
shadowIdleAssets[dir] = shadowIdleSprite;
}
var currentFrame = 0;
var animationSpeed = 0.2; // Controls how fast the animation plays
var frameCounter = 0;
var knightGraphics = knightIdleAssets['down']; // Initialize with the 'down' direction idle asset
var shadowGraphics = shadowIdleAssets['down']; // Initialize shadow with the 'down' direction idle asset
// Add health bar under the knight
self.healthBar = LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 10,
color: 0x00FF00 // Green color for health
});
self.healthBar.y = 60; // Position the health bar under the knight
self.addChild(self.healthBar);
// Add shadow first (so it appears behind the knight)
self.addChild(shadowGraphics);
// Then add the knight
self.addChild(knightGraphics);
knightGraphics.anchor.set(0.5, 0.5);
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Position the shadow slightly offset from the knight
var shadowOffsetX = -5;
var shadowOffsetY = 20;
shadowGraphics.x = shadowOffsetX;
shadowGraphics.y = shadowOffsetY;
self.lastDirection = 'down'; // Initialize lastDirection to track previous direction
self.isMoving = false; // Track if the knight is currently moving
self.isAttacking = false; // Track if the knight is currently attacking
self.speed = 9;
var distanceThreshold = 8; // Define a threshold to avoid small movements
var directionThreshold = 5; // Define a threshold for direction changes to avoid shaking
self.update = function () {
// Update health bar width based on knight's health
self.healthBar.width = Math.max(0, (self.health + 1) / 4 * 100); // Assuming max health is 3
// Change health bar color based on health
var healthPercentage = self.health / 3; // Assuming max health is 3
var red, green;
// Transition from green to orange
if (healthPercentage > 0.5) {
red = Math.min(255, Math.floor((1 - healthPercentage) * 2 * 255));
green = 255;
} else {
// Transition from orange to red
red = 255;
green = Math.min(255, Math.floor(healthPercentage * 2 * 255));
}
self.healthBar.tint = red << 16 | green << 8; // Combine red and green to form the color
// Don't process movement if attacking
if (isDied || self.health < 0 || self.isAttacking) {
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > distanceThreshold) {
// Set knight to moving state if not already moving
if (!self.isMoving) {
self.isMoving = true;
self.switchAsset(self.lastDirection || 'down'); // Update asset to running animation
}
// Determine the primary direction based on the larger component (dx or dy)
if (Math.abs(dx) > Math.abs(dy) + directionThreshold) {
// Horizontal movement dominates
if (dx > 0) {
if (dy > directionThreshold) {
self.moveToDirection('down-right');
} else if (dy < -directionThreshold) {
self.moveToDirection('up-right');
} else {
self.moveToDirection('right');
}
} else {
if (dy > directionThreshold) {
self.moveToDirection('down-left');
} else if (dy < -directionThreshold) {
self.moveToDirection('up-left');
} else {
self.moveToDirection('left');
}
}
} else if (Math.abs(dy) > Math.abs(dx) + directionThreshold) {
// Vertical movement dominates
if (dy > 0) {
if (dx > directionThreshold) {
self.moveToDirection('down-right');
} else if (dx < -directionThreshold) {
self.moveToDirection('down-left');
} else {
self.moveToDirection('down');
}
} else {
if (dx > directionThreshold) {
self.moveToDirection('up-right');
} else if (dx < -directionThreshold) {
self.moveToDirection('up-left');
} else {
self.moveToDirection('up');
}
}
} else {
// The difference between dx and dy is small, use diagonal movement
if (dx > 0 && dy > 0) {
self.moveToDirection('down-right');
} else if (dx > 0 && dy < 0) {
self.moveToDirection('up-right');
} else if (dx < 0 && dy > 0) {
self.moveToDirection('down-left');
} else if (dx < 0 && dy < 0) {
self.moveToDirection('up-left');
}
}
} else {
// Check if knight is not moving and distance to drone is less than 400
if (!self.isMoving) {
if (droneManager && droneManager.drones.length > 0) {
var drone = droneManager.drones[0]; // Assuming single drone for simplicity
var dx = drone.x - self.x;
var dy = drone.y - self.y;
var distanceToDrone = Math.sqrt(dx * dx + dy * dy);
}
if (distanceToDrone < 400) {
// Calculate angle to orient knight towards drone
var angleToDrone = Math.atan2(dy, dx);
var directionToDrone;
if (angleToDrone >= -Math.PI / 8 && angleToDrone < Math.PI / 8) {
directionToDrone = 'right';
} else if (angleToDrone >= Math.PI / 8 && angleToDrone < 3 * Math.PI / 8) {
directionToDrone = 'down-right';
} else if (angleToDrone >= 3 * Math.PI / 8 && angleToDrone < 5 * Math.PI / 8) {
directionToDrone = 'down';
} else if (angleToDrone >= 5 * Math.PI / 8 && angleToDrone < 7 * Math.PI / 8) {
directionToDrone = 'down-left';
} else if (angleToDrone >= 7 * Math.PI / 8 || angleToDrone < -7 * Math.PI / 8) {
directionToDrone = 'left';
} else if (angleToDrone >= -7 * Math.PI / 8 && angleToDrone < -5 * Math.PI / 8) {
directionToDrone = 'up-left';
} else if (angleToDrone >= -5 * Math.PI / 8 && angleToDrone < -3 * Math.PI / 8) {
directionToDrone = 'up';
} else if (angleToDrone >= -3 * Math.PI / 8 && angleToDrone < -Math.PI / 8) {
directionToDrone = 'up-right';
}
if (directionToDrone) {
self.switchAsset(directionToDrone);
}
}
}
// Set knight to idle state if currently moving
if (self.isMoving) {
self.isMoving = false;
self.switchAsset(self.lastDirection || 'down'); // Update asset to idle animation
}
}
// Update knight animation
self.updateAnimation();
};
self.switchAsset = function (direction) {
if (!knightGraphics) {
return;
}
// If currently attacking, don't switch assets
if (isDied || self.health < 0 || self.isAttacking) {
return;
}
// Detach current assets
// Hide current assets
knightGraphics.visible = false;
shadowGraphics.visible = false;
// Switch to new assets based on direction and movement state
if (self.isMoving) {
// Use running animation
knightGraphics = knightAssets[direction][currentFrame];
shadowGraphics = shadowAssets[direction][currentFrame];
} else {
// Use idle animation
knightGraphics = knightIdleAssets[direction];
shadowGraphics = shadowIdleAssets[direction];
}
self.addChild(shadowGraphics);
self.addChild(knightGraphics);
// Show new assets
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Position the shadow slightly offset from the knight
shadowGraphics.x = shadowOffsetX;
shadowGraphics.y = shadowOffsetY;
// Update last direction
self.lastDirection = direction;
};
self.updateAnimation = function () {
// If currently attacking, don't switch assets
if (isDied || self.health < 0 || self.isAttacking) {
return;
}
if (self.isMoving) {
// Only update animation if moving
frameCounter += animationSpeed;
if (frameCounter >= 1) {
frameCounter = 0;
currentFrame = (currentFrame + 1) % 8; // Cycle through 8 frames
if (self.lastDirection) {
self.switchAsset(self.lastDirection);
}
}
} else if (self.lastDirection) {
// Make sure we're showing the idle animation when not moving
self.switchAsset(self.lastDirection);
}
};
self.moveToDirection = function (direction) {
if (isDied || self.health < 0 || self.isAttacking) {
return;
}
if (self.lastDirection !== direction) {
self.switchAsset(direction);
self.lastDirection = direction;
}
switch (direction) {
case 'up':
self.y -= self.speed;
break;
case 'down':
self.y += self.speed;
break;
case 'left':
self.x -= self.speed;
break;
case 'right':
self.x += self.speed;
break;
case 'up-left':
self.x -= self.speed / Math.sqrt(2);
self.y -= self.speed / Math.sqrt(2);
break;
case 'up-right':
self.x += self.speed / Math.sqrt(2);
self.y -= self.speed / Math.sqrt(2);
break;
case 'down-left':
self.x -= self.speed / Math.sqrt(2);
self.y += self.speed / Math.sqrt(2);
break;
case 'down-right':
self.x += self.speed / Math.sqrt(2);
self.y += self.speed / Math.sqrt(2);
break;
}
};
self.takeHit = function (bullet) {
if (isDied) {
return;
}
// Flash the knight red to indicate damage
self.tint = 0xff0000; // Red tint
// After a short delay, restore the original tint
LK.setTimeout(function () {
self.tint = color; // Restore original color
}, 200);
// Optional: Add knockback effect based on bullet direction
var knockbackDistance = 30;
var knockbackAngle = bullet.rotation;
// Apply knockback
self.x += Math.cos(knockbackAngle) * knockbackDistance;
self.y += Math.sin(knockbackAngle) * knockbackDistance;
self.health--;
if (self.health < 0) {
self.dieAnim();
} else {
// Play knight-hit sound
LK.getSound('knight-hit').play();
}
// Log hit for debugging
log("Knight hit by laser beam!");
};
self.dieAnim = function () {
// Animate knight tint to black
LK.getSound('knight-die').play();
punchButton.visible = false; // Hide attackButton when the knight is dead
if (isDied) {
return;
}
isDied = true;
tween(self, {
tint: 0x000000
}, {
duration: 1000,
// 1 second duration
easing: tween.easeInOut,
onFinish: function onFinish() {
var explosion = new ExplosionKnight();
explosion.init(self.x, self.y - 50);
middlegroundContainer.addChild(explosion);
LK.getSound('knight-killed').play();
self.destroy(); // Destroy knight after animation
LK.setTimeout(function () {
LK.showGameOver();
}, 3000); // Call game over after 2 seconds
}
});
};
self.attack = function () {
// Check if already attacking
if (isDied || self.isAttacking) {
return;
}
// Play knight-attack sound
LK.getSound('knight-attack').play();
self.isAttacking = true;
log("Knight attacking!");
// Store current direction
var direction = self.lastDirection || 'down';
// Get attack animation frames for current direction
var attackFrames = knightAttackAssets[direction];
var shadowAttackFrames = shadowAttackAssets[direction];
// Hide current graphics
// knightGraphics.visible = false;
// shadowGraphics.visible = false;
// Add all frames but make them invisible initially
for (var i = 0; i < attackFrames.length; i++) {
self.addChild(shadowAttackFrames[i]);
self.addChild(attackFrames[i]);
shadowAttackFrames[i].visible = false;
attackFrames[i].visible = false;
}
// Create attack animation
var currentFrame = 0;
var attackInterval = LK.setInterval(function () {
if (self.isDied) {
return;
}
// Hide previous frame if it exists
if (currentFrame > 0) {
attackFrames[currentFrame - 1].visible = false;
shadowAttackFrames[currentFrame - 1].visible = false;
}
// Hide current graphics
knightGraphics.visible = false;
shadowGraphics.visible = false;
// Show current frame
shadowAttackFrames[currentFrame].visible = true;
attackFrames[currentFrame].visible = true;
// Check for collision with drones (middle of the attack animation)
if (currentFrame === Math.floor(attackFrames.length / 2)) {
// Calculate attack range and direction
var attackRange = 100; // Range of the knight's attack
var attackDirection = direction;
var attackAngle;
// Determine attack angle based on direction
if (attackDirection === 'right') {
attackAngle = 0;
} else if (attackDirection === 'down-right') {
attackAngle = Math.PI / 4;
} else if (attackDirection === 'down') {
attackAngle = Math.PI / 2;
} else if (attackDirection === 'down-left') {
attackAngle = 3 * Math.PI / 4;
} else if (attackDirection === 'left') {
attackAngle = Math.PI;
} else if (attackDirection === 'up-left') {
attackAngle = 5 * Math.PI / 4;
} else if (attackDirection === 'up') {
attackAngle = 3 * Math.PI / 2;
} else if (attackDirection === 'up-right') {
attackAngle = 7 * Math.PI / 4;
}
// Calculate attack point in front of the knight
var attackX = self.x + Math.cos(attackAngle) * attackRange;
var attackY = self.y + Math.sin(attackAngle) * attackRange;
// Check if any drone is within attack range
for (var i = 0; i < droneManager.drones.length; i++) {
var drone = droneManager.drones[i];
var dx = drone.x - attackX;
var dy = drone.y - attackY;
var distance = Math.sqrt(dx * dx + dy * dy);
// If drone is within attack range, call its takeHit method
if (distance < 120) {
// Adjust hit radius as needed
drone.takeHit({
rotation: attackAngle // Pass attack angle for knockback direction
});
}
}
}
// Move to next frame
currentFrame++;
// Check if animation is complete
if (currentFrame >= attackFrames.length) {
// Clear interval
LK.clearInterval(attackInterval);
// Hide all attack frames
for (var i = 0; i < attackFrames.length; i++) {
attackFrames[i].visible = false;
shadowAttackFrames[i].visible = false;
}
// Show regular graphics again
knightGraphics.visible = true;
shadowGraphics.visible = true;
// Reset attacking flag
self.isAttacking = false;
// Reset the global target position to knight's current position
target.x = self.x;
target.y = self.y;
}
}, 50); // 50ms per frame for a fast attack animation
};
});
var LaserBeam = Container.expand(function () {
var self = Container.call(this);
var laserBeamGraphics = LK.getAsset('droneLaserBeam', {
anchorX: 0.5,
anchorY: 0.5,
blendMode: 1
});
self.addChild(laserBeamGraphics);
self.speed = 1500; // Speed of the laser beam in pixels per second
self.fire = function (x, y, rotation) {
self.x = x;
self.y = y;
self.rotation = rotation;
middlegroundContainer.addChild(self);
// Calculate the distance the laser beam will travel
var distance = 3000;
// Calculate the duration based on speed
var duration = distance / self.speed * 1000; // Convert to milliseconds
// Tween the laser beam to move forward
tween(self, {
x: self.x + Math.cos(self.rotation) * distance,
y: self.y + Math.sin(self.rotation) * distance
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.update = function () {
if (self.intersects(knight.boundingBox)) {
knight.takeHit(self);
self.destroy();
}
};
});
var Room = Container.expand(function () {
var self = Container.call(this);
var background = LK.getAsset('backgroundWalls', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(background);
self.x = 2048 / 2;
self.y = 2732 / 2;
});
var Target = Container.expand(function () {
var self = Container.call(this);
self.x = 0;
self.y = 0;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 //Init game with black background
});
/****
* Game Code
****/
// Utility function to calculate the angle difference
function calculateAngleDiff(targetAngle, currentAngle) {
var angleDiff = targetAngle - currentAngle;
// Normalize angle difference to [-PI, PI]
while (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI;
}
while (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI;
}
return angleDiff;
}
//<Write entity 'classes' with empty functions for important behavior here>
//<Write imports for supported plugins here>
//<Assets used in the game will automatically appear here>
//<Write game logic code here, including initializing arrays and variables>
var DroneManager = function DroneManager() {
var self = this;
self.droneCounter = 0; // Initialize droneCounter to track the number of drones
self.drones = [];
self.start = function () {
// Clear previous drones from the scene and the drones array
self.drones.forEach(function (drone) {
drone.destroy();
});
self.drones = [];
// Create the drone
var drone = new Drone(self.droneCounter); // Pass the current level to the Drone constructor
// Increment counter after drone creation so first drone is level 0
self.droneCounter++;
// Check win condition after incrementing counter
if (self.droneCounter > 3) {
LK.getSound('knight-win').play(); // Play knight-win sound
LK.setTimeout(function () {
LK.showYouWin(); // Show win screen after 2000ms delay
}, 2000);
return;
}
// Explicitly set position before adding to the scene
drone.x = Math.random() < 0.5 ? -1024 : 3072;
drone.y = 1366;
// Store the current target index
//drone.currentTargetIndex = currentTargetIndex;
// Set the initial target to the current corner position
drone.target = {
x: 1024,
y: 1366
};
// Add to container and array
middlegroundContainer.addChild(drone);
self.drones.push(drone);
// This ensures all properties are set before tweening begins
LK.setTimeout(function () {
drone.init();
}, 100);
};
};
/****
* Global variables
****/
var isDebug = false;
var isDied = false; // Global flag to track if the knight has died
var gameStarted = false;
var droneManager = new DroneManager();
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var knight;
var background; // Declare a global variable for the background
var target;
var punchButton;
var runSoundInterval;
var borderOffset = 380;
var corners = [{
x: borderOffset,
y: borderOffset
}, {
x: 2048 - borderOffset,
y: borderOffset
}, {
x: borderOffset,
y: 2732 - borderOffset
}, {
x: 2048 - borderOffset,
y: 2732 - borderOffset
}];
function log() {
if (isDebug) {
console.log.apply(console, arguments);
}
}
function initializeGame() {
// Create containers
backgroundContainer = new Container();
middlegroundContainer = new Container();
foregroundContainer = new Container();
// Add containers to game
game.addChild(backgroundContainer);
game.addChild(middlegroundContainer);
game.addChild(foregroundContainer);
// Initialize background
background = new Background(); // Use Background class instead of LK.getAsset
backgroundContainer.addChild(background);
// Initialize room
var room = new Room();
backgroundContainer.addChild(room);
// Initialize knight
knight = new Knight();
knight.x = 2048 / 2;
knight.y = 2732 / 2;
middlegroundContainer.addChild(knight);
// Initialize target
target = new Target();
target.x = knight.x;
target.y = knight.y;
foregroundContainer.addChild(target);
// Initialize punch button
punchButton = new ButtonPunch();
punchButton.x = 2048 - 220;
punchButton.y = 220;
foregroundContainer.addChild(punchButton);
// Initialize sound
runSoundInterval = LK.setInterval(playRunSound, 350);
droneManager = new DroneManager();
LK.setTimeout(function () {
gameStarted = true;
droneManager.start();
}, 1000);
}
function playRunSound() {
if (knight.isMoving) {
LK.getSound('knight-run').play();
}
}
initializeGame();