/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var ChigiriPlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var opponentGraphics = self.attachAsset('opponent', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && opponentGraphics) { opponentGraphics.tint = colorOverride; } self.speed = 5; self.role = 'chigiri'; self.homeX = 800; self.homeY = 2000; self.maxDistance = 600; self.hasBall = false; self.isSpeedBursting = false; self.speedBurstTarget = null; self.speedBurstCooldown = 0; self.speedBurstCooldownTime = 8000; // 8 seconds self.normalSpeed = 5; self.burstSpeed = 12; self.stamina = 100; self.maxStamina = 100; self.staminaDrainRate = 0.8; self.staminaRegenRate = 0.3; self.lowStaminaSpeed = 1.5; // Very slow when stamina is low self.update = function () { // Update speed burst cooldown if (self.speedBurstCooldown > 0) { self.speedBurstCooldown -= 16; if (self.speedBurstCooldown < 0) self.speedBurstCooldown = 0; } // Steal ball if opponent is inside area var nearestOpponent = null; var nearestDistance = Infinity; for (var i = 0; i < opponents.length; i++) { var opponentDist = Math.sqrt((opponents[i].x - self.x) * (opponents[i].x - self.x) + (opponents[i].y - self.y) * (opponents[i].y - self.y)); if (opponentDist < nearestDistance) { nearestDistance = opponentDist; nearestOpponent = opponents[i]; } } if (nearestOpponent && nearestDistance < 80 && nearestOpponent.hasBall) { // Steal the ball nearestOpponent.hasBall = false; ball.active = true; // Ball moves toward Chigiri var stealDx = self.x - nearestOpponent.x; var stealDy = self.y - nearestOpponent.y; var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy); if (stealDist > 0) { ball.velocityX = stealDx / stealDist * 8; ball.velocityY = stealDy / stealDist * 8; } ballDetachCooldown = ballDetachCooldownTime; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Enhanced ball pursuit for hybrid player if (distance < 500 && !self.isSpeedBursting) { // Chase ball with hybrid intelligence var chaseSpeed = distance < 150 ? self.normalSpeed * 1.4 : self.normalSpeed * 1.1; self.x += dx / distance * chaseSpeed; self.y += dy / distance * chaseSpeed; } // Speed burst mechanic if (!self.isSpeedBursting && self.speedBurstCooldown <= 0 && distance < 100) { // Throw ball forward and start speed burst var throwX = opponentGoal.x + (Math.random() - 0.5) * 200; var throwY = opponentGoal.y + 200; var throwDx = throwX - ball.x; var throwDy = throwY - ball.y; var throwDist = Math.sqrt(throwDx * throwDx + throwDy * throwDy); if (throwDist > 0) { ball.velocityX = throwDx / throwDist * 14; ball.velocityY = throwDy / throwDist * 14; } // Set speed burst target and activate self.speedBurstTarget = { x: throwX, y: throwY }; self.isSpeedBursting = true; self.speedBurstCooldown = self.speedBurstCooldownTime; // Start cooldown // Visual effect for speed burst tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); } if (self.isSpeedBursting && self.speedBurstTarget) { // Move at burst speed toward target var burstDx = self.speedBurstTarget.x - self.x; var burstDy = self.speedBurstTarget.y - self.y; var burstDist = Math.sqrt(burstDx * burstDx + burstDy * burstDy); if (burstDist > 30) { self.x += burstDx / burstDist * self.burstSpeed; self.y += burstDy / burstDist * self.burstSpeed; // Drain stamina during speed burst self.stamina -= self.staminaDrainRate * 2; // Double drain during burst if (self.stamina < 0) self.stamina = 0; } else { // Reached target, stop speed burst self.isSpeedBursting = false; self.speedBurstTarget = null; } } else { // Stamina regeneration when not moving much var currentSpeed = Math.sqrt((self.x - (self.lastMoveX || self.x)) * (self.x - (self.lastMoveX || self.x)) + (self.y - (self.lastMoveY || self.y)) * (self.y - (self.lastMoveY || self.y))); if (currentSpeed < 2 && self.stamina < self.maxStamina) { self.stamina += self.staminaRegenRate; if (self.stamina > self.maxStamina) self.stamina = self.maxStamina; } self.lastMoveX = self.x; self.lastMoveY = self.y; // Determine current speed based on stamina var currentMoveSpeed = self.stamina > 20 ? self.normalSpeed : self.lowStaminaSpeed; // Normal hybrid behavior - enhanced ball pursuit if (distance < 500) { // Move toward ball with improved midfielder intelligence but adjust for stamina var pursuitSpeed = distance < 200 ? currentMoveSpeed * 1.3 : currentMoveSpeed * 1.1; self.x += dx / distance * pursuitSpeed; self.y += dy / distance * pursuitSpeed; // Drain stamina during pursuit self.stamina -= self.staminaDrainRate * 0.5; if (self.stamina < 0) self.stamina = 0; // Attack opponent goal if close to ball if (distance < 80) { var goalDx = opponentGoal.x - ball.x; var goalDy = opponentGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 11; ball.velocityY = goalDy / goalDistance * 11; } } } else { // Return to defensive position var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { // Use stamina-adjusted speed for returning home var currentMoveSpeed = self.stamina > 20 ? self.normalSpeed : self.lowStaminaSpeed; self.x += homeDx / homeDistance * currentMoveSpeed * 0.4; self.y += homeDy / homeDistance * currentMoveSpeed * 0.4; // Light stamina drain when returning to position self.stamina -= self.staminaDrainRate * 0.3; if (self.stamina < 0) self.stamina = 0; } } } }; return self; }); var DefensePlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var opponentGraphics = self.attachAsset('opponent', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && opponentGraphics) { opponentGraphics.tint = colorOverride; } self.speed = 4; self.role = 'defense'; self.homeX = 1024; self.homeY = 600; self.maxDistance = 400; self.hasBall = false; self.update = function () { // Steal ball if player is inside opponent var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist < 80 && player.hasBall) { // Steal the ball player.hasBall = false; ball.active = true; // Ball moves away from player, toward defense var stealDx = self.x - player.x; var stealDy = self.y - player.y; var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy); if (stealDist > 0) { ball.velocityX = stealDx / stealDist * 10; ball.velocityY = stealDy / stealDist * 10; } // Prevent immediate re-attachment ballDetachCooldown = ballDetachCooldownTime; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Defense priority: desperately try to get the ball if (distance < 400) { // Move aggressively toward ball - increased chase range self.x += dx / distance * (self.speed * 1.2); self.y += dy / distance * (self.speed * 1.2); // Sliding tackle if player is close to defense var playerDistance = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDistance < 120 && ball.active && !player.hasBall) { // Slide tackle: dash toward ball and clear it away var slideDx = ball.x - self.x; var slideDy = ball.y - self.y; var slideDist = Math.sqrt(slideDx * slideDx + slideDy * slideDy); if (slideDist > 0) { // Animate slide (quick move) tween(self, { x: self.x + slideDx / slideDist * 60, y: self.y + slideDy / slideDist * 60 }, { duration: 120, easing: tween.cubicOut }); // Clear ball far away from player var clearX = ball.x < 1024 ? ball.x - 400 : ball.x + 400; var clearY = ball.y - 500; var clearDx = clearX - ball.x; var clearDy = clearY - ball.y; var clearDist = Math.sqrt(clearDx * clearDx + clearDy * clearDy); if (clearDist > 0) { ball.velocityX = clearDx / clearDist * 13; ball.velocityY = clearDy / clearDist * 13; } } } // Kick ball away from player goal if close enough if (distance < 80) { // Find nearest forward player var nearestForward = null; var nearestDistance = Infinity; for (var i = 0; i < opponents.length; i++) { if (opponents[i].role === 'forward') { var forwardDx = opponents[i].x - self.x; var forwardDy = opponents[i].y - self.y; var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy); if (forwardDistance < nearestDistance) { nearestDistance = forwardDistance; nearestForward = opponents[i]; } } } // Pass to forward if found, otherwise clear as before if (nearestForward) { var passDx = nearestForward.x - ball.x; var passDy = nearestForward.y - ball.y; var passDistance = Math.sqrt(passDx * passDx + passDy * passDy); if (passDistance > 0) { ball.velocityX = passDx / passDistance * 10; ball.velocityY = passDy / passDistance * 10; } } else { var kickAwayX = ball.x < 1024 ? ball.x - 200 : ball.x + 200; var kickAwayY = ball.y - 300; // Kick upfield var kickDx = kickAwayX - ball.x; var kickDy = kickAwayY - ball.y; var kickDistance = Math.sqrt(kickDx * kickDx + kickDy * kickDy); if (kickDistance > 0) { ball.velocityX = kickDx / kickDistance * 8; ball.velocityY = kickDy / kickDistance * 8; } } } } else { // Return to defensive position var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.5; self.y += homeDy / homeDistance * self.speed * 0.5; } } }; return self; }); var ForwardPlayer = Container.expand(function () { var self = Container.call(this); var opponentGraphics = self.attachAsset('opponent', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3.5; self.role = 'forward'; self.homeX = 1024; self.homeY = 1400; self.hasBall = false; self.directShotCooldown = self.directShotCooldown || 0; self.update = function () { // Steal ball if player is inside opponent var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist < 80 && player.hasBall) { // Steal the ball player.hasBall = false; ball.active = true; // Ball moves away from player, toward forward var stealDx = self.x - player.x; var stealDy = self.y - player.y; var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy); if (stealDist > 0) { ball.velocityX = stealDx / stealDist * 10; ball.velocityY = stealDy / stealDist * 10; } // Prevent immediate re-attachment ballDetachCooldown = ballDetachCooldownTime; } // Handle direct shot cooldown if (self.directShotCooldown > 0) { self.directShotCooldown -= 16; if (self.directShotCooldown < 0) self.directShotCooldown = 0; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Forward: attack and try to score if (distance < 400) { // Move toward ball with aggressive pursuit var pursuitSpeed = distance < 200 ? self.speed * 1.8 : self.speed * 1.3; self.x += dx / distance * pursuitSpeed; self.y += dy / distance * pursuitSpeed; // If close to ball, shoot at goal if (distance < 80) { // Direct Shot every 30s if (self.directShotCooldown <= 0) { var goalDx = playerGoal.x - ball.x; var goalDy = playerGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { // Direct Shot: very fast and straight ball.velocityX = goalDx / goalDistance * 18; ball.velocityY = goalDy / goalDistance * 18; } self.directShotCooldown = 30000; // 30 seconds } else { // Normal shot var goalDx = playerGoal.x - ball.x; var goalDy = playerGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 10; ball.velocityY = goalDy / goalDistance * 10; } } } } else { // Move toward attacking position var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.4; self.y += homeDy / homeDistance * self.speed * 0.4; } } }; return self; }); var Goal = Container.expand(function () { var self = Container.call(this); // Goal area var goalGraphics = self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5 }); goalGraphics.alpha = 0.3; // Left goalpost var leftPost = self.addChild(LK.getAsset('goalpost', { anchorX: 0.5, anchorY: 0.5 })); leftPost.x = -210; leftPost.y = 0; // Right goalpost var rightPost = self.addChild(LK.getAsset('goalpost', { anchorX: 0.5, anchorY: 0.5 })); rightPost.x = 210; rightPost.y = 0; return self; }); var Goalkeeper = Container.expand(function (isPlayer) { var self = Container.call(this); var keeperGraphics = self.attachAsset(isPlayer ? 'player' : 'opponent', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.isPlayer = isPlayer; self.rushCooldown = 0; self.rushCooldownTime = 20000; // 20 seconds self.isRushing = false; self.originalX = 0; self.originalY = 0; self.rushSpeed = 8; self.update = function () { // Update rush cooldown if (self.rushCooldown > 0) { self.rushCooldown -= 16; // Approximately 60 FPS if (self.rushCooldown < 0) self.rushCooldown = 0; } // Check if should rush for ball var ballDistance = Math.sqrt((ball.x - self.x) * (ball.x - self.x) + (ball.y - self.y) * (ball.y - self.y)); if (!self.isRushing && self.rushCooldown <= 0 && ballDistance < 400) { // Start rushing self.isRushing = true; self.rushCooldown = self.rushCooldownTime; } if (self.isRushing) { // Rush toward ball var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0 && distance > 50) { self.x += dx / distance * self.rushSpeed; self.y += dy / distance * self.rushSpeed; } else if (distance <= 50) { // Catch ball if close enough ball.velocityX = 0; ball.velocityY = 0; ball.x = self.x; ball.y = self.y; ball.active = false; player.hasBall = false; // Return to original position first self.isRushing = false; tween(self, { x: self.originalX, y: self.originalY }, { duration: 1000, onFinish: function onFinish() { // After returning to position, throw ball to center field ball.active = true; var centerX = 1024; var centerY = 1366; var throwPower = 15; var throwDx = centerX - ball.x; var throwDy = centerY - ball.y; var throwDistance = Math.sqrt(throwDx * throwDx + throwDy * throwDy); if (throwDistance > 0) { ball.velocityX = throwDx / throwDistance * throwPower; ball.velocityY = throwDy / throwDistance * throwPower; } } }); } } else { // Normal goalkeeper movement - stay near goal line var goalCenterX = 1024; var dx = goalCenterX - self.x; if (Math.abs(dx) > 5) { self.x += dx > 0 ? self.speed : -self.speed; } } }; return self; }); var HioriPlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && playerGraphics) { playerGraphics.tint = colorOverride; } self.speed = 4; self.role = 'hiori'; self.homeX = 1200; self.homeY = 2000; self.hasBall = false; // FLOW SYSTEM self.flowActive = false; self.flowDribbleCount = 0; self.flowDribbleMax = 4; // 4 extra dribbles in Flow self.flowText = null; self.flowTriggerChecked = false; // To avoid retriggering // Zig-zag dribbling variables self.isDribbling = false; self.dribbleStartTime = 0; self.dribbleDuration = 2000; // 2 seconds - slower duration self.dribbleSpeed = 6; self.dribbleDirection = 1; // 1 for right, -1 for left self.dribblePhase = 0; // 0-1 progress through dribble self.dribbleStartX = 0; self.dribbleStartY = 0; self.dribbleTargetX = 0; self.dribbleTargetY = 0; // Zigzag pause variables self.zigzagCount = 0; self.zigzagPaused = false; self.zigzagPauseStart = 0; self.zigzagPauseDuration = 10000; // 10 seconds // Winter Zone variables self.winterZoneActive = false; self.winterZoneRadius = 200; self.winterZoneCooldown = 0; self.winterZoneCooldownTime = 12000; // 12 seconds self.winterZoneDuration = 0; self.winterZoneMaxDuration = 10000; // 10 seconds - increased duration self.slowedOpponents = []; // Winter Zone visual self.winterZoneVisual = null; // Perfect pass variables self.passChargingTime = 0; self.perfectPassCooldown = 0; self.perfectPassCooldownTime = 6000; // 6 seconds self.update = function () { // FLOW TRIGGER: If 2 goals conceded, activate Flow for Hiori (only once) if (!self.flowActive && !self.flowTriggerChecked && typeof opponentScore !== "undefined" && opponentScore >= 2) { self.flowActive = true; self.flowDribbleCount = 0; self.flowTriggerChecked = true; // Show Flow text above Hiori if (!self.flowText) { self.flowText = self.addChild(new Text2('FLOW', { size: 48, fill: 0x00FFFF })); self.flowText.anchor.set(0.5, 0.5); self.flowText.x = 0; self.flowText.y = -100; self.flowText.alpha = 1; tween(self.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { tween(self.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } }); } } // Remove Flow text if not in Flow if (!self.flowActive && self.flowText) { self.flowText.destroy(); self.flowText = null; } // Update cooldowns if (self.winterZoneCooldown > 0) { self.winterZoneCooldown -= 16; if (self.winterZoneCooldown < 0) self.winterZoneCooldown = 0; } if (self.perfectPassCooldown > 0) { self.perfectPassCooldown -= 16; if (self.perfectPassCooldown < 0) self.perfectPassCooldown = 0; } // FLOW: If in Flow, boost Winter Zone and dribbling var flowWinterZoneRadius = self.flowActive ? 420 : 200; var flowWinterZoneSlow = self.flowActive ? 0.12 : 0.3; // 12% speed in Flow var flowWinterZoneDuration = self.flowActive ? 18000 : self.winterZoneMaxDuration; // 18s in Flow var flowWinterZoneCooldown = self.flowActive ? 6000 : self.winterZoneCooldownTime; // 6s in Flow // Winter Zone duration and effect if (self.winterZoneActive) { self.winterZoneDuration -= 16; if (self.winterZoneDuration <= 0) { self.winterZoneActive = false; // Remove Winter Zone visual if (self.winterZoneVisual) { self.winterZoneVisual.destroy(); self.winterZoneVisual = null; } // Restore opponent speeds for (var i = 0; i < self.slowedOpponents.length; i++) { var opponent = self.slowedOpponents[i]; opponent.speed = opponent.originalSpeed || opponent.speed * 2; } self.slowedOpponents = []; } else { // Apply Winter Zone effect to nearby opponents for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (dist <= (self.winterZoneActive ? flowWinterZoneRadius : self.winterZoneRadius)) { if (self.slowedOpponents.indexOf(opponent) === -1) { opponent.originalSpeed = opponent.speed; opponent.speed = opponent.speed * (self.winterZoneActive ? flowWinterZoneSlow : 0.3); // Much slower in Flow self.slowedOpponents.push(opponent); } } } } } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // FLOW: Dribbling logic if (self.isDribbling) { var elapsed = Date.now() - self.dribbleStartTime; var progress = Math.min(elapsed / self.dribbleDuration, 1); // Create zig-zag pattern - slower frequency for better visibility var zigzagAmplitude = 80; var zigzagFrequency = 4; var zigzagOffset = Math.sin(progress * Math.PI * zigzagFrequency) * zigzagAmplitude; // Move toward target with zig-zag motion - slower speed var targetDx = self.dribbleTargetX - self.dribbleStartX; var targetDy = self.dribbleTargetY - self.dribbleStartY; var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy); if (targetDist > 0) { // Calculate perpendicular vector for zig-zag var perpX = -targetDy / targetDist; var perpY = targetDx / targetDist; // Apply zig-zag movement with slower speed var slowSpeed = 0.6; // Reduced from 1.0 to make it slower self.x = self.dribbleStartX + targetDx * progress * slowSpeed + perpX * zigzagOffset; self.y = self.dribbleStartY + targetDy * progress * slowSpeed + perpY * zigzagOffset; // Ball follows during dribble if (distance < 100) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } } // End dribbling if (progress >= 1) { self.isDribbling = false; self.zigzagCount = (self.zigzagCount || 0) + 1; // FLOW: If in Flow, allow up to 4 extra dribbles in a row if (self.flowActive && self.flowDribbleCount < self.flowDribbleMax) { self.flowDribbleCount++; // Start another dribble immediately self.isDribbling = true; self.dribbleStartTime = Date.now(); self.dribbleStartX = self.x; self.dribbleStartY = self.y; // Dribble toward opponent goal, randomize a bit self.dribbleTargetX = opponentGoal.x + (Math.random() - 0.5) * 300; self.dribbleTargetY = opponentGoal.y + 200 + Math.random() * 100; } else { // After 4 zigzags, pause for 10 seconds if (self.zigzagCount >= 4) { self.zigzagPaused = true; self.zigzagPauseStart = Date.now(); self.zigzagCount = 0; } // Reset Flow dribble count after sequence if (self.flowActive) self.flowDribbleCount = 0; } } } else { // Normal ball pursuit if (distance < 400) { var chaseSpeed = distance < 150 ? self.speed * 1.3 : self.speed * 1.1; self.x += dx / distance * chaseSpeed; self.y += dy / distance * chaseSpeed; // Handle zigzag pause if (self.zigzagPaused) { var pauseElapsed = Date.now() - self.zigzagPauseStart; if (pauseElapsed >= self.zigzagPauseDuration) { self.zigzagPaused = false; } } // Start zig-zag dribbling when close to ball (only if not paused) if (distance < 80 && !self.isDribbling && !self.zigzagPaused) { self.isDribbling = true; self.dribbleStartTime = Date.now(); self.dribbleStartX = self.x; self.dribbleStartY = self.y; // Dribble toward opponent goal self.dribbleTargetX = opponentGoal.x + (Math.random() - 0.5) * 300; self.dribbleTargetY = opponentGoal.y + 200; // FLOW: If in Flow, reset dribble count for new sequence if (self.flowActive) self.flowDribbleCount = 0; } } } // FLOW: Winter Zone is much bigger, lasts longer, slows more, and triggers more often if (!self.winterZoneActive && self.winterZoneCooldown <= 0) { var nearbyOpponents = 0; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (dist <= flowWinterZoneRadius + 50) { nearbyOpponents++; } } if (nearbyOpponents >= 2) { // Activate Winter Zone self.winterZoneActive = true; self.winterZoneDuration = flowWinterZoneDuration; self.winterZoneCooldown = flowWinterZoneCooldown; // Create Winter Zone visual area if (self.winterZoneVisual) { self.winterZoneVisual.destroy(); self.winterZoneVisual = null; } self.winterZoneVisual = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: self.flowActive ? 8 : 4, scaleY: self.flowActive ? 8 : 4, tint: 0x87CEEB })); self.winterZoneVisual.alpha = 0.3; // Visual effect tween(self, { scaleX: 1.5, scaleY: 1.5, tint: 0x87CEEB }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); } } // FLOW: If in Flow, perfect pass always goes directly to player, no curve, and is instant if (distance < 80 && self.perfectPassCooldown <= 0) { // Look for player to pass to var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist < 600 && playerDist > 100) { // Execute perfect pass var passDx = player.x - ball.x; var passDy = player.y - ball.y; var passDist = Math.sqrt(passDx * passDx + passDy * passDy); if (passDist > 0) { if (self.flowActive) { // FLOW: Pass is instant and direct ball.x = player.x; ball.y = player.y - 50; ball.velocityX = 0; ball.velocityY = 0; ball.active = false; player.hasBall = true; } else { // Normal perfect pass with slight curve to avoid interception var curveAmount = 3; ball.velocityX = passDx / passDist * 12 + curveAmount; ball.velocityY = passDy / passDist * 12; ball.active = true; } self.perfectPassCooldown = self.perfectPassCooldownTime; // Visual effect for perfect pass tween(ball, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, onFinish: function onFinish() { tween(ball, { scaleX: 1, scaleY: 1 }, { duration: 150 }); } }); } } } // Return to home position when not actively playing if (distance > 500) { var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.4; self.y += homeDy / homeDistance * self.speed * 0.4; } } }; return self; }); var MeguruBachiraPlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && playerGraphics) { playerGraphics.tint = colorOverride; } self.speed = 4.5; self.role = 'bachira'; self.homeX = 1000; self.homeY = 1900; self.hasBall = false; // FLOW SYSTEM for Bachira self.flowActive = false; self.flowTriggerChecked = false; self.flowText = null; // Monster Trans variables (Flow mode transformation) self.monsterTransActive = false; self.monsterTransVisual = null; // Roulette dribbling variables self.rouletteActive = false; self.rouletteStartTime = 0; self.rouletteDuration = 1500; // 1.5 seconds self.rouletteRotations = 3; // 3 full rotations self.rouletteRadius = 80; self.rouletteCooldown = 0; self.rouletteCooldownTime = 4000; // 4 seconds self.rouletteOriginX = 0; self.rouletteOriginY = 0; self.rouletteStunRadius = 120; self.rouletteStunnedOpponents = []; self.rouletteStunEnd = 0; // Monster Dribbling variables self.monsterDribblingActive = false; self.monsterDribblingStartTime = 0; self.monsterDribblingDuration = 2000; // 2 seconds self.monsterDribblingSpeed = 8; self.monsterDribblingCooldown = 0; self.monsterDribblingCooldownTime = 6000; // 6 seconds self.monsterDribblingStartX = 0; self.monsterDribblingStartY = 0; self.monsterDribblingTargetX = 0; self.monsterDribblingTargetY = 0; self.monsterDribblingStunRadius = 150; self.monsterDribblingStunnedOpponents = []; self.monsterDribblingStunEnd = 0; // Rush to ball variables (after Roulette) self.rushingToBall = false; self.rushTargetX = 0; self.rushTargetY = 0; self.rushSpeed = 7; self.readyToShoot = false; self.update = function () { // FLOW TRIGGER: Monster Trans for Bachira, random chance for Hiori, only one Flow at a time if (!self.flowActive && !self.flowTriggerChecked) { // Count Bachira and Hiori on field var bachiraCount = typeof bachira !== "undefined" ? 1 : 0; var hioriCount = typeof hiori !== "undefined" ? 1 : 0; var totalFlowCandidates = bachiraCount + hioriCount; var shouldFlow = false; // If both Bachira and Hiori are present, randomly pick one for Flow if (totalFlowCandidates > 1) { // 50% chance for Bachira, 50% for Hiori var pick = Math.random(); if (pick < 0.5) { shouldFlow = true; // Bachira gets Flow if (typeof hiori !== "undefined") { hiori.flowActive = false; hiori.flowTriggerChecked = true; if (hiori.flowText) { hiori.flowText.destroy(); hiori.flowText = null; } } } else { shouldFlow = false; if (typeof hiori !== "undefined" && !hiori.flowActive && !hiori.flowTriggerChecked) { hiori.flowActive = true; hiori.flowTriggerChecked = true; hiori.flowDribbleCount = 0; // Show Flow text above Hiori if (!hiori.flowText) { hiori.flowText = hiori.addChild(new Text2('FLOW', { size: 48, fill: 0x00FFFF })); hiori.flowText.anchor.set(0.5, 0.5); hiori.flowText.x = 0; hiori.flowText.y = -100; hiori.flowText.alpha = 1; if (hiori.flowText) { // Defensive: only tween if flowText exists tween(hiori.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { if (hiori.flowText) { // Defensive: only tween if flowText still exists tween(hiori.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } } }); } } } } } else { // Only Bachira or only Hiori present shouldFlow = true; } if (shouldFlow) { // Only set flowTriggerChecked, but do NOT activate Monster Trans/Flow yet self.flowTriggerChecked = true; // self.flowActive and self.monsterTransActive will be set when using Roulette or Monster Dribbling } } // Remove Flow text and effects if not in Flow if (!self.flowActive && self.flowText) { self.flowText.destroy(); self.flowText = null; } if (!self.flowActive && self.monsterTransVisual) { self.monsterTransVisual.destroy(); self.monsterTransVisual = null; self.monsterTransActive = false; // Reset player appearance tween(playerGraphics, { tint: colorOverride || 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 500 }); } if (!self.flowActive) { self.monsterTransActive = false; } // Update cooldowns if (self.rouletteCooldown > 0) { self.rouletteCooldown -= 16; if (self.rouletteCooldown < 0) self.rouletteCooldown = 0; } if (self.monsterDribblingCooldown > 0) { // In Flow mode, no cooldowns if (!self.flowActive) { self.monsterDribblingCooldown -= 16; if (self.monsterDribblingCooldown < 0) self.monsterDribblingCooldown = 0; } else { self.monsterDribblingCooldown = 0; // No cooldown in Flow } } // Handle Roulette stun end if (self.rouletteStunEnd > 0 && Date.now() > self.rouletteStunEnd) { // Restore stunned opponents from Roulette for (var i = 0; i < self.rouletteStunnedOpponents.length; i++) { var opponent = self.rouletteStunnedOpponents[i]; opponent.stunned = false; opponent.originalSpeed = opponent.originalSpeed || opponent.speed; opponent.speed = opponent.originalSpeed; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.rouletteStunnedOpponents = []; self.rouletteStunEnd = 0; } // Handle Monster Dribbling stun end if (self.monsterDribblingStunEnd > 0 && Date.now() > self.monsterDribblingStunEnd) { // Restore stunned opponents from Monster Dribbling for (var i = 0; i < self.monsterDribblingStunnedOpponents.length; i++) { var opponent = self.monsterDribblingStunnedOpponents[i]; opponent.stunned = false; opponent.originalSpeed = opponent.originalSpeed || opponent.speed; opponent.speed = opponent.originalSpeed; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.monsterDribblingStunnedOpponents = []; self.monsterDribblingStunEnd = 0; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Roulette dribbling logic if (self.rouletteActive) { var elapsed = Date.now() - self.rouletteStartTime; var t = Math.min(elapsed / self.rouletteDuration, 1); // Create spinning motion around origin point var angle = t * self.rouletteRotations * 2 * Math.PI; self.x = self.rouletteOriginX + Math.cos(angle) * self.rouletteRadius; self.y = self.rouletteOriginY + Math.sin(angle) * self.rouletteRadius; // Ball follows during roulette if (distance < 100) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } // End roulette and apply stun effect if (t >= 1) { self.rouletteActive = false; self.applyRouletteStun(); } } // Monster Dribbling logic if (self.monsterDribblingActive) { var elapsed = Date.now() - self.monsterDribblingStartTime; var t = Math.min(elapsed / self.monsterDribblingDuration, 1); // Fast, erratic movement toward target var chaosAmplitude = 40; var chaosFrequency = 8; var chaosOffsetX = Math.sin(t * Math.PI * chaosFrequency) * chaosAmplitude; var chaosOffsetY = Math.cos(t * Math.PI * chaosFrequency * 1.3) * chaosAmplitude; // Move toward target with chaos var targetDx = self.monsterDribblingTargetX - self.monsterDribblingStartX; var targetDy = self.monsterDribblingTargetY - self.monsterDribblingStartY; self.x = self.monsterDribblingStartX + targetDx * t + chaosOffsetX; self.y = self.monsterDribblingStartY + targetDy * t + chaosOffsetY; // Ball follows during monster dribbling if (distance < 120) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } // Stun all opponents passed by during Monster Dribbling (only in Flow/Monster Trans) if (self.flowActive) { for (var i = 0; i < opponents.length; i++) { var opp = opponents[i]; var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y)); if (oppDist <= self.monsterDribblingStunRadius && !opp.stunned) { opp.stunned = true; opp.originalSpeed = opp.speed; opp.speed = 0; self.monsterDribblingStunnedOpponents.push(opp); tween(opp, { tint: 0xFF0000, scaleX: 1.2, scaleY: 1.2 }, { duration: 250 }); } } if (self.monsterDribblingStunnedOpponents.length > 0) { self.monsterDribblingStunEnd = Date.now() + 4000; } } // End monster dribbling and apply stun effect if (t >= 1) { self.monsterDribblingActive = false; self.applyMonsterDribblingStun(); } } // Rush to ball after Roulette throw if (self.rushingToBall) { var rushDx = self.rushTargetX - self.x; var rushDy = self.rushTargetY - self.y; var rushDist = Math.sqrt(rushDx * rushDx + rushDy * rushDy); if (rushDist > 30) { // Rush toward the thrown ball location self.x += rushDx / rushDist * self.rushSpeed; self.y += rushDy / rushDist * self.rushSpeed; // Stun all opponents passed by during Monster Trans rush (only in Flow) if (self.flowActive) { for (var i = 0; i < opponents.length; i++) { var opp = opponents[i]; var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y)); if (oppDist <= self.monsterDribblingStunRadius && !opp.stunned) { opp.stunned = true; opp.originalSpeed = opp.speed; opp.speed = 0; self.monsterDribblingStunnedOpponents.push(opp); tween(opp, { tint: 0xFF0000, scaleX: 1.2, scaleY: 1.2 }, { duration: 250 }); } } if (self.monsterDribblingStunnedOpponents.length > 0) { self.monsterDribblingStunEnd = Date.now() + 4000; } } } else { // Reached the target area, check if ball is nearby var ballDist = Math.sqrt((ball.x - self.x) * (ball.x - self.x) + (ball.y - self.y) * (ball.y - self.y)); if (ballDist < 100) { // Ball is close, prepare to shoot self.readyToShoot = true; self.rushingToBall = false; // Control the ball briefly ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; // Immediately shoot toward opponent goal var goalDx = opponentGoal.x - self.x; var goalDy = opponentGoal.y - self.y; var goalDist = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDist > 0) { ball.velocityX = goalDx / goalDist * 18; // Powerful shot ball.velocityY = goalDy / goalDist * 18; ball.active = true; // Visual effect for powerful shot tween(self, { scaleX: 1.4, scaleY: 1.4, tint: 0xFF4500 }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: self.flowActive ? 1.2 : 1, scaleY: self.flowActive ? 1.2 : 1, tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF }, { duration: 300 }); } }); } // Reset after shot self.readyToShoot = false; } else { // Ball not found at target, stop rushing self.rushingToBall = false; } } } else if (!self.rouletteActive && !self.monsterDribblingActive && distance < 400) { // Normal ball pursuit var chaseSpeed = distance < 150 ? self.speed * 1.3 : self.speed * 1.1; self.x += dx / distance * chaseSpeed; self.y += dy / distance * chaseSpeed; // Start abilities when close to ball if (distance < 80) { // Roulette (normal cooldown applies) if (!self.rouletteActive && self.rouletteCooldown <= 0) { var shouldUseRoulette = Math.random() < 0.4; // 40% chance if (shouldUseRoulette) { self.rouletteActive = true; self.rouletteStartTime = Date.now(); self.rouletteOriginX = self.x; self.rouletteOriginY = self.y; // If Flow was triggered for Bachira, activate Monster Trans/Flow now if (self.flowTriggerChecked && !self.flowActive) { self.flowActive = true; self.monsterTransActive = true; // Show Flow text above Bachira if (!self.flowText) { self.flowText = self.addChild(new Text2('MONSTER TRANS', { size: 48, fill: 0xFF0000 })); self.flowText.anchor.set(0.5, 0.5); self.flowText.x = 0; self.flowText.y = -100; self.flowText.alpha = 1; tween(self.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { tween(self.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } }); } // Create Monster Trans visual effect if (!self.monsterTransVisual) { self.monsterTransVisual = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0xFF0000 })); self.monsterTransVisual.alpha = 0.3; } // Transform player appearance tween(playerGraphics, { tint: 0xFF4500, scaleX: 1.2, scaleY: 1.2 }, { duration: 500 }); } if (!self.flowActive) { self.rouletteCooldown = self.rouletteCooldownTime; } // Visual effect for roulette start tween(self, { tint: 0x00FFFF }, { duration: 100, onFinish: function onFinish() { tween(self, { tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF }, { duration: 100 }); } }); } } // Monster Dribbling (no cooldown in Flow) if (!self.monsterDribblingActive && (self.flowActive || self.monsterDribblingCooldown <= 0)) { var shouldUseMonsterDribbling = Math.random() < 0.6; // 60% chance if (shouldUseMonsterDribbling) { self.monsterDribblingActive = true; self.monsterDribblingStartTime = Date.now(); self.monsterDribblingStartX = self.x; self.monsterDribblingStartY = self.y; // Target toward opponent goal self.monsterDribblingTargetX = opponentGoal.x + (Math.random() - 0.5) * 200; self.monsterDribblingTargetY = opponentGoal.y + 300; // If Flow was triggered for Bachira, activate Monster Trans/Flow now if (self.flowTriggerChecked && !self.flowActive) { self.flowActive = true; self.monsterTransActive = true; // Show Flow text above Bachira if (!self.flowText) { self.flowText = self.addChild(new Text2('MONSTER TRANS', { size: 48, fill: 0xFF0000 })); self.flowText.anchor.set(0.5, 0.5); self.flowText.x = 0; self.flowText.y = -100; self.flowText.alpha = 1; tween(self.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { tween(self.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } }); } // Create Monster Trans visual effect if (!self.monsterTransVisual) { self.monsterTransVisual = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0xFF0000 })); self.monsterTransVisual.alpha = 0.3; } // Transform player appearance tween(playerGraphics, { tint: 0xFF4500, scaleX: 1.2, scaleY: 1.2 }, { duration: 500 }); } if (!self.flowActive) { self.monsterDribblingCooldown = self.monsterDribblingCooldownTime; } // Visual effect for monster dribbling start tween(self, { tint: 0xFF0000, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, onFinish: function onFinish() { tween(self, { tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF, scaleX: self.flowActive ? 1.2 : 1, scaleY: self.flowActive ? 1.2 : 1 }, { duration: 200 }); } }); } } } } else if (!self.rouletteActive && !self.monsterDribblingActive && distance > 500) { // Return to home position when not actively playing var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.4; self.y += homeDy / homeDistance * self.speed * 0.4; } } }; self.applyRouletteStun = function () { var opponentsInRange = []; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (opponentDist <= self.rouletteStunRadius) { opponentsInRange.push(opponent); } } // Stun opponents in range for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; opponent.stunned = true; opponent.originalSpeed = opponent.speed; opponent.speed = 0; // Completely stop them self.rouletteStunnedOpponents.push(opponent); // Visual effect for stunned opponent tween(opponent, { tint: 0x00FFFF, scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } if (opponentsInRange.length > 0) { self.rouletteStunEnd = Date.now() + 3000; // 3 seconds stun } // After Roulette, throw ball to edge and rush to it self.throwBallToEdgeAndRush(); }; self.applyMonsterDribblingStun = function () { var opponentsInRange = []; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (opponentDist <= self.monsterDribblingStunRadius) { opponentsInRange.push(opponent); } } // Stun opponents in range for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; opponent.stunned = true; opponent.originalSpeed = opponent.speed; opponent.speed = 0; // Completely stop them self.monsterDribblingStunnedOpponents.push(opponent); // Visual effect for stunned opponent tween(opponent, { tint: 0xFF0000, scaleX: 1.2, scaleY: 1.2 }, { duration: 250 }); } if (opponentsInRange.length > 0) { self.monsterDribblingStunEnd = Date.now() + 4000; // 4 seconds stun } }; self.throwBallToEdgeAndRush = function () { // Determine which edge to throw to (left or right side of field) var throwToLeft = self.x < 1024; // If Bachira is on left side, throw left; otherwise right var edgeX = throwToLeft ? 200 : 1848; // Near left or right edge var edgeY = self.y + (Math.random() - 0.5) * 200; // Slightly randomize Y position // Ensure edge Y is within field bounds if (edgeY < 300) edgeY = 300; if (edgeY > 2400) edgeY = 2400; // Throw ball to edge with moderate power var throwDx = edgeX - ball.x; var throwDy = edgeY - ball.y; var throwDist = Math.sqrt(throwDx * throwDx + throwDy * throwDy); if (throwDist > 0) { ball.velocityX = throwDx / throwDist * 10; ball.velocityY = throwDy / throwDist * 10; ball.active = true; } // Set rush target and start rushing self.rushTargetX = edgeX; self.rushTargetY = edgeY; self.rushingToBall = true; self.readyToShoot = false; // Visual effect for ball throw tween(ball, { scaleX: 1.3, scaleY: 1.3, tint: 0x00FFFF }, { duration: 200, onFinish: function onFinish() { tween(ball, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); }; return self; }); var MidfieldPlayer = Container.expand(function () { var self = Container.call(this); var opponentGraphics = self.attachAsset('opponent', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; self.role = 'midfield'; self.homeX = 1024; self.homeY = 1000; self.hasBall = false; // Dribbling state self.isDribbling = false; self.dribbleStartTime = 0; self.dribbleDuration = 1000; self.dribbleRadius = 120; self.dribbleStartAngle = 0; self.dribbleEndAngle = 0; self.dribbleOriginX = 0; self.dribbleOriginY = 0; self.hasDribbled = false; self.update = function () { // Steal ball if player is inside opponent var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist < 80 && player.hasBall) { // Steal the ball player.hasBall = false; ball.active = true; // Ball moves away from player, toward midfield var stealDx = self.x - player.x; var stealDy = self.y - player.y; var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy); if (stealDist > 0) { ball.velocityX = stealDx / stealDist * 10; ball.velocityY = stealDy / stealDist * 10; } // Prevent immediate re-attachment ballDetachCooldown = ballDetachCooldownTime; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Midfield: get ball and pass to forward if (distance < 350) { // Move toward ball with increased speed when chasing var chaseSpeed = distance < 150 ? self.speed * 1.5 : self.speed * 1.2; self.x += dx / distance * chaseSpeed; self.y += dy / distance * chaseSpeed; // If close to ball, run away from player and pass to forward if (distance < 80) { // Move away from player for a moment var awayDx = self.x - player.x; var awayDy = self.y - player.y; var awayDist = Math.sqrt(awayDx * awayDx + awayDy * awayDy); if (awayDist > 0) { tween(self, { x: self.x + awayDx / awayDist * 120, y: self.y + awayDy / awayDist * 120 }, { duration: 200, easing: tween.cubicOut }); } // Find nearest forward player var nearestForward = null; var nearestDistance = Infinity; for (var i = 0; i < opponents.length; i++) { if (opponents[i].role === 'forward') { var forwardDx = opponents[i].x - self.x; var forwardDy = opponents[i].y - self.y; var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy); if (forwardDistance < nearestDistance) { nearestDistance = forwardDistance; nearestForward = opponents[i]; } } } // Pass to forward if found if (nearestForward) { // Perfect pass: ball goes directly to forward with good speed var passDx = nearestForward.x - ball.x; var passDy = nearestForward.y - ball.y; var passDistance = Math.sqrt(passDx * passDx + passDy * passDy); if (passDistance > 0) { ball.velocityX = passDx / passDistance * 10; ball.velocityY = passDy / passDistance * 10; } } else { // No forward to pass to: perform dribbling and curved shot if (!self.isDribbling) { // Start dribbling: move in a half-circle around the player self.isDribbling = true; self.dribbleStartTime = Date.now(); self.dribbleDuration = 1000; // 1 second self.dribbleRadius = 120; // Calculate angle from midfield to player var angleToPlayer = Math.atan2(player.y - self.y, player.x - self.x); self.dribbleStartAngle = angleToPlayer - Math.PI / 2; self.dribbleEndAngle = angleToPlayer + Math.PI / 2; self.dribbleOriginX = self.x; self.dribbleOriginY = self.y; self.hasDribbled = false; } if (self.isDribbling) { var elapsed = Date.now() - self.dribbleStartTime; var t = Math.min(elapsed / self.dribbleDuration, 1); // Interpolate angle for half-circle var angle = self.dribbleStartAngle + (self.dribbleEndAngle - self.dribbleStartAngle) * t; self.x = self.dribbleOriginX + Math.cos(angle) * self.dribbleRadius; self.y = self.dribbleOriginY + Math.sin(angle) * self.dribbleRadius; // Ball follows midfield during dribble if (distance < 80) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } // When dribble completes, shoot with curve if (t >= 1 && !self.hasDribbled) { self.hasDribbled = true; self.isDribbling = false; // Falsolu şut: apply curve by adding to X velocity var goalDx = opponentGoal.x - ball.x; var goalDy = opponentGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { // Add curve: right-footed, so curve to the right (positive X) var curveAmount = 7; ball.velocityX = goalDx / goalDistance * 12 + curveAmount; ball.velocityY = goalDy / goalDistance * 12; ball.active = true; } } } } } } else { // Return to midfield position var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.3; self.y += homeDy / homeDistance * self.speed * 0.3; } } }; return self; }); var NagiPlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && playerGraphics) { playerGraphics.tint = colorOverride; } self.speed = 4; self.role = 'nagi'; self.homeX = 1400; self.homeY = 2000; self.hasBall = false; // --- FLOW SYSTEM for Nagi --- self.flowActive = false; self.flowTriggerChecked = false; self.flowVolleyCount = 0; self.flowVolleyMax = 5; // 5 Revolver Fake Volleys in Flow self.flowText = null; // Fake Volley variables self.fakeVolleyState = 0; // 0: ready, 1: fake shot done, 2: real shot done self.fakeVolleyTarget = null; self.fakeVolleyStunRadius = 120; self.fakeVolleyStunDuration = 2000; // 2 seconds self.fakeVolleyStunnedOpponents = []; self.fakeVolleyStunEnd = 0; // Fake Attack variables self.fakeAttackCooldown = 0; self.fakeAttackCooldownTime = 5000; // Base 5 seconds, will be randomized self.fakeAttackStunRadius = 140; self.fakeAttackStunDuration = 0; // Will be randomized 5-10 seconds self.fakeAttackStunnedOpponents = []; self.fakeAttackStunEnd = 0; self.fakeAttackChance = 0.3; // 30% chance for shots to be fake self.fakeVolleyVisual = null; // Ball pull variables self.ballPullRange = 150; self.ballPullActive = false; self.ballPullVisual = null; self.ballPullCooldown = 0; self.ballPullCooldownTime = 5000; // 5 seconds // Ball control text self.ballControlText = null; self.update = function () { // --- FLOW TRIGGER: If 2 goals scored by Nagi's team, activate Flow for random teammate (including Nagi) --- if (!self.flowActive && !self.flowTriggerChecked && typeof playerScore !== "undefined" && playerScore >= 2) { self.flowTriggerChecked = true; // Pick a random teammate (Nagi, Hiori, Chigiri, Reo) to enter Flow var teammates = []; if (typeof nagi !== "undefined") teammates.push(nagi); if (typeof hiori !== "undefined") teammates.push(hiori); if (typeof chigiri !== "undefined") teammates.push(chigiri); if (typeof reo !== "undefined") teammates.push(reo); if (teammates.length > 0) { var idx = Math.floor(Math.random() * teammates.length); var chosen = teammates[idx]; if (chosen && typeof chosen.flowActive !== "undefined") { chosen.flowActive = true; chosen.flowVolleyCount = 0; // Show Flow text above chosen if (!chosen.flowText) { chosen.flowText = chosen.addChild(new Text2('FLOW', { size: 48, fill: 0x00FFFF })); chosen.flowText.anchor.set(0.5, 0.5); chosen.flowText.x = 0; chosen.flowText.y = -100; chosen.flowText.alpha = 1; tween(chosen.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { tween(chosen.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } }); } } } } // Remove Flow text if not in Flow if (!self.flowActive && self.flowText) { self.flowText.destroy(); self.flowText = null; } // Update cooldowns if (self.fakeVolleyStunEnd > 0 && Date.now() > self.fakeVolleyStunEnd) { // Restore stunned opponents for (var i = 0; i < self.fakeVolleyStunnedOpponents.length; i++) { var opponent = self.fakeVolleyStunnedOpponents[i]; opponent.stunned = false; opponent.originalSpeed = opponent.originalSpeed || opponent.speed; opponent.speed = opponent.originalSpeed; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.fakeVolleyStunnedOpponents = []; self.fakeVolleyStunEnd = 0; } if (self.ballPullCooldown > 0) { self.ballPullCooldown -= 16; if (self.ballPullCooldown < 0) self.ballPullCooldown = 0; } // Update Fake Attack cooldown if (self.fakeAttackCooldown > 0) { self.fakeAttackCooldown -= 16; if (self.fakeAttackCooldown < 0) self.fakeAttackCooldown = 0; } // Handle Fake Attack stun end if (self.fakeAttackStunEnd > 0 && Date.now() > self.fakeAttackStunEnd) { // Restore stunned opponents from Fake Attack for (var i = 0; i < self.fakeAttackStunnedOpponents.length; i++) { var opponent = self.fakeAttackStunnedOpponents[i]; opponent.stunned = false; opponent.originalSpeed = opponent.originalSpeed || opponent.speed; opponent.speed = opponent.originalSpeed; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.fakeAttackStunnedOpponents = []; self.fakeAttackStunEnd = 0; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Nagi Ball Control: Steal from opponent if close and not immune if (!self.nagiStealImmunity) self.nagiStealImmunity = 0; var canSteal = true; if (self.nagiStealImmunity > Date.now()) canSteal = false; // Check for opponent with ball in Ball Control range var stoleFromOpponent = false; if (canSteal) { for (var i = 0; i < opponents.length; i++) { var opp = opponents[i]; var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y)); if (opp.hasBall && oppDist < 120) { // Steal the ball from opponent opp.hasBall = false; ball.active = true; // Move ball to Nagi's feet ball.velocityX = 0; ball.velocityY = 0; tween(ball, { x: self.x, y: self.y + 40 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { ball.active = false; self.hasBall = true; } }); // Show "Ball Control" text if (!self.ballControlText) { self.ballControlText = self.addChild(new Text2('Ball Control', { size: 40, fill: 0xFFFFFF })); self.ballControlText.anchor.set(0.5, 0.5); self.ballControlText.x = 0; self.ballControlText.y = -80; tween(self.ballControlText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { LK.setTimeout(function () { tween(self.ballControlText, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, onFinish: function onFinish() { if (self.ballControlText) { self.ballControlText.destroy(); self.ballControlText = null; } } }); }, 2000); } }); } // Set 3s immunity for Nagi after stealing self.nagiStealImmunity = Date.now() + 3000; stoleFromOpponent = true; break; } } } // If not stealing from opponent, allow normal ball control from loose ball if (!stoleFromOpponent && distance < 120 && ball.active && !self.hasBall && canSteal) { ball.velocityX = 0; ball.velocityY = 0; tween(ball, { x: self.x, y: self.y + 40 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { ball.active = false; self.hasBall = true; } }); if (!self.ballControlText) { self.ballControlText = self.addChild(new Text2('Ball Control', { size: 40, fill: 0xFFFFFF })); self.ballControlText.anchor.set(0.5, 0.5); self.ballControlText.x = 0; self.ballControlText.y = -80; tween(self.ballControlText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { LK.setTimeout(function () { tween(self.ballControlText, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, onFinish: function onFinish() { if (self.ballControlText) { self.ballControlText.destroy(); self.ballControlText = null; } } }); }, 2000); } }); } } // --- FLOW: 5 Revolver Fake Volley logic --- if (self.flowActive) { // If close to ball, perform up to 5 chained fake volleys (fake+real) in a row if (distance < 80 && self.flowVolleyCount < self.flowVolleyMax && self.fakeVolleyState === 0) { self.fakeVolleyState = 1; self.flowVolleyCount++; // Stun nearby opponents var opponentsInRange = []; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (opponentDist <= self.fakeVolleyStunRadius) { opponentsInRange.push(opponent); } } for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; opponent.stunned = true; opponent.originalSpeed = opponent.speed; opponent.speed = 0; self.fakeVolleyStunnedOpponents.push(opponent); tween(opponent, { tint: 0xFFFF00, scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } self.fakeVolleyStunEnd = Date.now() + self.fakeVolleyStunDuration; // Fake shot - ball barely moves ball.velocityX = (Math.random() - 0.5) * 2; ball.velocityY = (Math.random() - 0.5) * 2; ball.active = true; tween(self, { scaleX: 1.3, scaleY: 1.3, tint: 0xFFFF00 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); } else if (self.flowActive && self.fakeVolleyState === 1) { // After fake, do real volley self.fakeVolleyState = 2; var goalDx = opponentGoal.x - ball.x; var goalDy = opponentGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 16; ball.velocityY = goalDy / goalDistance * 16; ball.active = true; } tween(self, { scaleX: 1.4, scaleY: 1.4, tint: 0xFF0000 }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); } }); // Reset fake volley state after a short delay, and chain next if not reached max LK.setTimeout(function () { self.fakeVolleyState = 0; // If not reached 5, chain next volley automatically (simulate rapid-fire) if (self.flowActive && self.flowVolleyCount < self.flowVolleyMax) { // Move Nagi slightly forward for next volley self.x += (opponentGoal.x - self.x) * 0.08; self.y += (opponentGoal.y - self.y) * 0.08; } else if (self.flowActive && self.flowVolleyCount >= self.flowVolleyMax) { // End Flow after 5 volleys self.flowActive = false; self.flowVolleyCount = 0; if (self.flowText) { self.flowText.destroy(); self.flowText = null; } } }, 500); } // Prevent normal volley logic during Flow return; } // After stealing, for 3s, Nagi cannot be stolen from and focuses on goal if (self.hasBall && self.nagiStealImmunity > Date.now()) { // Nagi focuses on goal: move toward opponent goal and shoot if close var goalDx = opponentGoal.x - self.x; var goalDy = opponentGoal.y - self.y; var goalDist = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDist > 0) { self.x += goalDx / goalDist * self.speed * 1.2; self.y += goalDy / goalDist * self.speed * 1.2; // If close to goal, shoot if (goalDist < 200) { ball.active = true; ball.velocityX = goalDx / goalDist * 16; ball.velocityY = goalDy / goalDist * 16; self.hasBall = false; // Remove immunity after shot self.nagiStealImmunity = 0; } } } else { // If Nagi doesn't have ball, always move to a good attacking position near the opponent goal var attackTargetX = opponentGoal.x + 200 * Math.sin(Date.now() / 1200); // oscillate a bit for realism var attackTargetY = opponentGoal.y + 250; var attackDx = attackTargetX - self.x; var attackDy = attackTargetY - self.y; var attackDist = Math.sqrt(attackDx * attackDx + attackDy * attackDy); if (attackDist > 10) { self.x += attackDx / attackDist * self.speed * 0.8; self.y += attackDy / attackDist * self.speed * 0.8; } } // Ball pull ability - show range when ball is within pull range if (distance <= self.ballPullRange && !self.ballPullActive && self.ballPullCooldown <= 0) { if (!self.ballPullVisual) { // Create ball pull range visual self.ballPullVisual = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3, tint: 0x00FF00 })); self.ballPullVisual.alpha = 0.2; } // Pull ball towards self if (distance > 30) { self.ballPullActive = true; var pullForce = 8; ball.velocityX = -dx / distance * pullForce; ball.velocityY = -dy / distance * pullForce; ball.active = true; self.ballPullCooldown = self.ballPullCooldownTime; // Visual effect for ball pull tween(self, { scaleX: 1.2, scaleY: 1.2, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 150 }); } }); } } else { // Remove ball pull visual if ball is out of range if (self.ballPullVisual) { self.ballPullVisual.destroy(); self.ballPullVisual = null; } self.ballPullActive = false; } // Normal ball pursuit if (distance < 400) { var chaseSpeed = distance < 150 ? self.speed * 1.2 : self.speed * 1.0; self.x += dx / distance * chaseSpeed; self.y += dy / distance * chaseSpeed; // Volley ability when close to ball with Fake Attack chance if (distance < 80) { // Check if this should be a Fake Attack var shouldFakeAttack = self.fakeAttackCooldown <= 0 && Math.random() < self.fakeAttackChance; if (shouldFakeAttack) { // FAKE ATTACK - stun opponents and barely move ball // Find opponents within Fake Attack stun radius var opponentsInRange = []; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (opponentDist <= self.fakeAttackStunRadius) { opponentsInRange.push(opponent); } } // Stun opponents in range for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; opponent.stunned = true; opponent.originalSpeed = opponent.speed; opponent.speed = 0; // Completely stop them self.fakeAttackStunnedOpponents.push(opponent); // Visual effect for stunned opponent tween(opponent, { tint: 0xFF6600, // Orange tint for Fake Attack scaleX: 1.2, scaleY: 1.2 }, { duration: 250 }); } // Randomize stun duration (5-10 seconds) self.fakeAttackStunDuration = 5000 + Math.random() * 5000; self.fakeAttackStunEnd = Date.now() + self.fakeAttackStunDuration; // Randomize next cooldown (5-10 seconds) self.fakeAttackCooldownTime = 5000 + Math.random() * 5000; self.fakeAttackCooldown = self.fakeAttackCooldownTime; // Fake attack - ball barely moves ball.velocityX = (Math.random() - 0.5) * 3; ball.velocityY = (Math.random() - 0.5) * 3; ball.active = true; // Visual effect for Fake Attack tween(self, { scaleX: 1.4, scaleY: 1.4, tint: 0xFF6600 // Orange for Fake Attack }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); } }); } else if (self.fakeVolleyState === 0) { // First shot - FAKE VOLLEY self.fakeVolleyState = 1; // Find opponents within stun radius var opponentsInRange = []; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (opponentDist <= self.fakeVolleyStunRadius) { opponentsInRange.push(opponent); } } // Stun opponents in range for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; opponent.stunned = true; opponent.originalSpeed = opponent.speed; opponent.speed = 0; // Completely stop them self.fakeVolleyStunnedOpponents.push(opponent); // Visual effect for stunned opponent tween(opponent, { tint: 0xFFFF00, scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } self.fakeVolleyStunEnd = Date.now() + self.fakeVolleyStunDuration; // Fake shot - ball barely moves to show it's fake ball.velocityX = (Math.random() - 0.5) * 2; ball.velocityY = (Math.random() - 0.5) * 2; ball.active = true; // Visual effect for fake shot tween(self, { scaleX: 1.3, scaleY: 1.3, tint: 0xFFFF00 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); } else if (self.fakeVolleyState === 1) { // Second shot - REAL VOLLEY self.fakeVolleyState = 2; // Real powerful shot toward opponent goal var goalDx = opponentGoal.x - ball.x; var goalDy = opponentGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 16; ball.velocityY = goalDy / goalDistance * 16; ball.active = true; } // Visual effect for real shot tween(self, { scaleX: 1.4, scaleY: 1.4, tint: 0xFF0000 }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); } }); // Reset fake volley state after a delay LK.setTimeout(function () { self.fakeVolleyState = 0; }, 3000); } } } else { // Return to home position when not actively playing var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.4; self.y += homeDy / homeDistance * self.speed * 0.4; } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; self.hasBall = false; return self; }); var ReoPlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && playerGraphics) { playerGraphics.tint = colorOverride; } self.speed = 4; self.role = 'reo'; self.homeX = 600; self.homeY = 1800; self.hasBall = false; // Mode switching variables self.isDefensiveMode = true; self.defensiveColor = 0x000000; // Black self.attackingColor = 0xffa500; // Orange self.modeSwitch = 0; self.modeSwitchInterval = 5000; // 5 seconds // Chameleon ability variables self.chameleonCooldown = 0; self.chameleonInterval = 30000; // 30 seconds self.copiedStats = null; self.originalSpeed = 4; self.originalDribbling = 1; self.originalShot = 1; // Copied ability variables self.copiedWinterZone = false; self.copiedWinterZoneRadius = 200; self.copiedWinterZoneCooldown = 0; self.copiedWinterZoneActive = false; self.copiedWinterZoneDuration = 0; self.copiedWinterZoneMaxDuration = 8000; self.copiedSlowedOpponents = []; self.copiedWinterZoneVisual = null; self.copiedSpeedBurst = false; self.copiedSpeedBurstCooldown = 0; self.copiedSpeedBurstActive = false; self.copiedBurstSpeed = 10; self.copiedNormalSpeed = 4; self.update = function () { // Update mode switching self.modeSwitch += 16; if (self.modeSwitch >= self.modeSwitchInterval) { self.modeSwitch = 0; self.isDefensiveMode = !self.isDefensiveMode; // Change color based on mode if (self.isDefensiveMode) { tween(playerGraphics, { tint: self.defensiveColor }, { duration: 300 }); } else { tween(playerGraphics, { tint: self.attackingColor }, { duration: 300 }); } } // Update Chameleon cooldown self.chameleonCooldown += 16; if (self.chameleonCooldown >= self.chameleonInterval) { self.chameleonCooldown = 0; self.activateChameleon(); } // Update copied abilities if (self.copiedWinterZone) { // Update Winter Zone cooldown if (self.copiedWinterZoneCooldown > 0) { self.copiedWinterZoneCooldown -= 16; if (self.copiedWinterZoneCooldown < 0) self.copiedWinterZoneCooldown = 0; } // Winter Zone duration and effect if (self.copiedWinterZoneActive) { self.copiedWinterZoneDuration -= 16; if (self.copiedWinterZoneDuration <= 0) { self.copiedWinterZoneActive = false; // Remove Winter Zone visual if (self.copiedWinterZoneVisual) { self.copiedWinterZoneVisual.destroy(); self.copiedWinterZoneVisual = null; } // Restore opponent speeds for (var i = 0; i < self.copiedSlowedOpponents.length; i++) { var opponent = self.copiedSlowedOpponents[i]; opponent.speed = opponent.originalSpeed || opponent.speed * 2; } self.copiedSlowedOpponents = []; } else { // Apply Winter Zone effect to nearby opponents for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (dist <= self.copiedWinterZoneRadius) { if (self.copiedSlowedOpponents.indexOf(opponent) === -1) { opponent.originalSpeed = opponent.speed; opponent.speed = opponent.speed * 0.3; // Slow to 30% speed self.copiedSlowedOpponents.push(opponent); } } } } } // Activate Winter Zone when opponents get close if (!self.copiedWinterZoneActive && self.copiedWinterZoneCooldown <= 0) { var nearbyOpponents = 0; for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y)); if (dist <= self.copiedWinterZoneRadius + 50) { nearbyOpponents++; } } if (nearbyOpponents >= 2) { // Activate Winter Zone self.copiedWinterZoneActive = true; self.copiedWinterZoneDuration = self.copiedWinterZoneMaxDuration; self.copiedWinterZoneCooldown = 12000; // 12 seconds cooldown // Create Winter Zone visual area self.copiedWinterZoneVisual = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 4, tint: 0x87CEEB })); self.copiedWinterZoneVisual.alpha = 0.3; // Visual effect tween(self, { scaleX: 1.5, scaleY: 1.5, tint: 0x87CEEB }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: self.isDefensiveMode ? self.defensiveColor : self.attackingColor }, { duration: 200 }); } }); } } } if (self.copiedSpeedBurst) { // Update Speed Burst cooldown if (self.copiedSpeedBurstCooldown > 0) { self.copiedSpeedBurstCooldown -= 16; if (self.copiedSpeedBurstCooldown < 0) self.copiedSpeedBurstCooldown = 0; } // Speed burst when close to ball if (!self.copiedSpeedBurstActive && self.copiedSpeedBurstCooldown <= 0 && distance < 100) { self.copiedSpeedBurstActive = true; self.copiedSpeedBurstCooldown = 8000; // 8 seconds cooldown self.speed = self.copiedBurstSpeed; // Visual effect for speed burst tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); // Speed burst lasts for 2 seconds LK.setTimeout(function () { self.copiedSpeedBurstActive = false; self.speed = self.copiedNormalSpeed; }, 2000); } } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Mode-based behavior if (self.isDefensiveMode) { // Defensive mode: more active interception and ball pursuit if (distance < 500) { var defensiveSpeed = self.speed * 1.1; self.x += dx / distance * defensiveSpeed; self.y += dy / distance * defensiveSpeed; // Defensive tackle if (distance < 80) { var clearX = ball.x < 1024 ? ball.x - 300 : ball.x + 300; var clearY = ball.y - 200; var clearDx = clearX - ball.x; var clearDy = clearY - ball.y; var clearDist = Math.sqrt(clearDx * clearDx + clearDy * clearDy); if (clearDist > 0) { ball.velocityX = clearDx / clearDist * 8; ball.velocityY = clearDy / clearDist * 8; } } } else { // Return to defensive position, but move more actively var homeDx = self.homeX - self.x; var homeDy = self.homeY - self.y; var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy); if (homeDistance > 50) { self.x += homeDx / homeDistance * self.speed * 0.7; self.y += homeDy / homeDistance * self.speed * 0.7; } } } else { // Attacking mode: chase ball very aggressively and support attack if (distance < 700) { var attackSpeed = self.speed * 1.7; self.x += dx / distance * attackSpeed; self.y += dy / distance * attackSpeed; // Attack shot if (distance < 80) { var goalDx = opponentGoal.x - ball.x; var goalDy = opponentGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 14; ball.velocityY = goalDy / goalDistance * 14; } } } else { // If not near ball, move up to a supporting attacking position var supportX = opponentGoal.x - 200; var supportY = opponentGoal.y + 400; var supportDx = supportX - self.x; var supportDy = supportY - self.y; var supportDist = Math.sqrt(supportDx * supportDx + supportDy * supportDy); if (supportDist > 30) { self.x += supportDx / supportDist * self.speed * 0.7; self.y += supportDy / supportDist * self.speed * 0.7; } } } }; self.activateChameleon = function () { // Find random NPC to copy stats from (including player team members) var availableNPCs = []; for (var i = 0; i < opponents.length; i++) { availableNPCs.push(opponents[i]); } // Also add player team members with special abilities if (typeof hiori !== 'undefined') availableNPCs.push(hiori); if (typeof chigiri !== 'undefined') availableNPCs.push(chigiri); if (typeof nagi !== 'undefined') availableNPCs.push(nagi); if (availableNPCs.length > 0) { var randomNPC = availableNPCs[Math.floor(Math.random() * availableNPCs.length)]; // Copy stats from random NPC self.copiedStats = { speed: randomNPC.speed || self.originalSpeed, dribbling: randomNPC.dribbleSpeed || self.originalDribbling, shot: randomNPC.burstSpeed || self.originalShot, role: randomNPC.role || 'reo' }; // Apply copied stats self.speed = self.copiedStats.speed; // Copy special abilities based on role if (self.copiedStats.role === 'hiori') { // Copy Winter Zone ability self.copiedWinterZone = true; self.copiedWinterZoneRadius = 200; self.copiedWinterZoneCooldown = 0; self.copiedWinterZoneActive = false; self.copiedWinterZoneDuration = 0; self.copiedWinterZoneMaxDuration = 8000; // Slightly shorter than original self.copiedSlowedOpponents = []; self.copiedWinterZoneVisual = null; } else if (self.copiedStats.role === 'chigiri') { // Copy Speed Burst ability self.copiedSpeedBurst = true; self.copiedSpeedBurstCooldown = 0; self.copiedSpeedBurstActive = false; self.copiedBurstSpeed = 10; self.copiedNormalSpeed = self.speed; } else { // Reset copied abilities self.copiedWinterZone = false; self.copiedSpeedBurst = false; } // Visual effect for Chameleon activation tween(self, { scaleX: 1.3, scaleY: 1.3, tint: 0xff00ff }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: self.isDefensiveMode ? self.defensiveColor : self.attackingColor }, { duration: 200 }); } }); } }; return self; }); var SaePlayer = Container.expand(function (colorOverride) { var self = Container.call(this); var playerGraphics = self.attachAsset('opponent', { anchorX: 0.5, anchorY: 0.5 }); if (typeof colorOverride === "number" && playerGraphics) { playerGraphics.tint = colorOverride; } self.speed = 4.5; self.role = 'sae'; self.homeX = 1024; self.homeY = 800; self.hasBall = false; // Perfect Pass variables self.perfectPassRange = 800; self.perfectPassCooldown = 0; self.perfectPassCooldownTime = 8000; // 8 seconds // Half-circle dribbling variables self.isDribbling = false; self.dribbleStartTime = 0; self.dribbleDuration = 1500; // 1.5 seconds self.dribbleRadius = 100; self.dribbleStartAngle = 0; self.dribbleEndAngle = 0; self.dribbleOriginX = 0; self.dribbleOriginY = 0; self.dribbleStunRadius = 150; self.dribbleStunDuration = 2000; // 2 seconds self.dribbleStunnedOpponents = []; self.dribbleStunEnd = 0; // Zigzag dribbling variables self.isZigzagDribbling = false; self.zigzagStartTime = 0; self.zigzagDuration = 2000; // 2 seconds self.zigzagStartX = 0; self.zigzagStartY = 0; self.zigzagTargetX = 0; self.zigzagTargetY = 0; self.zigzagDevourRadius = 120; // Zigzag chain variables self.zigzagCount = 0; self.zigzagPaused = false; self.zigzagPauseStart = 0; self.zigzagPauseDuration = 10000; // 10 seconds pause after 6 zigzags // Chop dribbling variables self.isChopDribbling = false; self.chopStartTime = 0; self.chopDuration = 1000; // 1 second self.chopStartX = 0; self.chopStartY = 0; self.chopTargetX = 0; self.chopTargetY = 0; self.chopDevourRadius = 100; // Devoured effect variables self.devouredOpponents = []; self.devouredEndTime = 0; self.devouredDuration = 3000; // 3 seconds // Dash ability variables self.dashCooldown = 0; self.dashCooldownTime = 10000; // 10 seconds self.dashSpeed = 10; self.dashDuration = 800; // 0.8 seconds self.dashActive = false; self.dashEndTime = 0; // False shot variables self.falseShotCooldown = 0; self.falseShotCooldownTime = 12000; // 12 seconds self.falseShotActive = false; // FLOW SYSTEM for Sae if (typeof self.flowActive === "undefined") { self.flowActive = false; self.flowTriggerChecked = false; self.flowDribbleCount = 0; self.flowDribbleMax = 999; // Unlimited while in Flow self.flowText = null; self.flowHilalDribbling = false; self.flowHilalStartTime = 0; self.flowHilalDuration = 1200; // ms per crescent self.flowHilalOriginX = 0; self.flowHilalOriginY = 0; self.flowHilalRadius = 180; self.flowHilalStartAngle = 0; self.flowHilalEndAngle = 0; self.flowHilalPhase = 0; self.flowHilalCount = 0; self.flowHilalMax = 4; // Number of crescents before shot self.flowHilalShotDone = false; } // FLOW TRIGGER: If 2 goals scored by Sae, activate Flow (only once) if (!self.flowActive && !self.flowTriggerChecked && typeof opponentScore !== "undefined" && opponentScore >= 2) { self.flowActive = true; self.flowDribbleCount = 0; self.flowTriggerChecked = true; self.flowHilalDribbling = false; self.flowHilalCount = 0; self.flowHilalShotDone = false; // Show Flow text above Sae if (!self.flowText) { self.flowText = self.addChild(new Text2('FLOW', { size: 48, fill: 0xFF6600 })); self.flowText.anchor.set(0.5, 0.5); self.flowText.x = 0; self.flowText.y = -100; self.flowText.alpha = 1; tween(self.flowText, { scaleX: 1.5, scaleY: 1.5, alpha: 1 }, { duration: 300, onFinish: function onFinish() { tween(self.flowText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300 }); } }); } } // Remove Flow text if not in Flow if (!self.flowActive && self.flowText) { self.flowText.destroy(); self.flowText = null; } self.update = function () { // Update cooldowns if (self.perfectPassCooldown > 0) { self.perfectPassCooldown -= 16; if (self.perfectPassCooldown < 0) self.perfectPassCooldown = 0; } if (self.dashCooldown > 0) { self.dashCooldown -= 16; if (self.dashCooldown < 0) self.dashCooldown = 0; } if (self.falseShotCooldown > 0) { self.falseShotCooldown -= 16; if (self.falseShotCooldown < 0) self.falseShotCooldown = 0; } // Handle dribble stun end if (self.dribbleStunEnd > 0 && Date.now() > self.dribbleStunEnd) { // Restore stunned opponents for (var i = 0; i < self.dribbleStunnedOpponents.length; i++) { var opponent = self.dribbleStunnedOpponents[i]; opponent.stunned = false; opponent.originalSpeed = opponent.originalSpeed || opponent.speed; opponent.speed = opponent.originalSpeed; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.dribbleStunnedOpponents = []; self.dribbleStunEnd = 0; } // Handle dash duration if (self.dashActive && Date.now() > self.dashEndTime) { self.dashActive = false; self.speed = 4.5; // Return to normal speed } // Steal ball if player is inside opponent var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist < 80 && player.hasBall) { // Steal the ball player.hasBall = false; ball.active = true; // Ball moves away from player, toward Sae var stealDx = self.x - player.x; var stealDy = self.y - player.y; var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy); if (stealDist > 0) { ball.velocityX = stealDx / stealDist * 10; ball.velocityY = stealDy / stealDist * 10; } // Prevent immediate re-attachment ballDetachCooldown = ballDetachCooldownTime; } var dx = ball.x - self.x; var dy = ball.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Handle devoured opponents restoration if (self.devouredEndTime > 0 && Date.now() > self.devouredEndTime) { // Restore devoured opponents for (var i = 0; i < self.devouredOpponents.length; i++) { var opponent = self.devouredOpponents[i]; opponent.speed = opponent.originalSpeed || opponent.speed * 2; opponent.devoured = false; tween(opponent, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } self.devouredOpponents = []; self.devouredEndTime = 0; } // Half-circle dribbling logic if (self.isDribbling) { var elapsed = Date.now() - self.dribbleStartTime; var t = Math.min(elapsed / self.dribbleDuration, 1); // Interpolate angle for half-circle var angle = self.dribbleStartAngle + (self.dribbleEndAngle - self.dribbleStartAngle) * t; self.x = self.dribbleOriginX + Math.cos(angle) * self.dribbleRadius; self.y = self.dribbleOriginY + Math.sin(angle) * self.dribbleRadius; // Ball follows during dribble if (distance < 80) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } // When dribble completes, apply Devoured effect if (t >= 1) { self.isDribbling = false; self.applyDevouredEffect(self.dribbleStunRadius); } } // Zigzag dribbling logic if (self.isZigzagDribbling) { var elapsed = Date.now() - self.zigzagStartTime; var progress = Math.min(elapsed / self.zigzagDuration, 1); // Create zigzag pattern var zigzagAmplitude = 60; var zigzagFrequency = 6; var zigzagOffset = Math.sin(progress * Math.PI * zigzagFrequency) * zigzagAmplitude; // Move toward target with zigzag motion var targetDx = self.zigzagTargetX - self.zigzagStartX; var targetDy = self.zigzagTargetY - self.zigzagStartY; var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy); if (targetDist > 0) { // Calculate perpendicular vector for zigzag var perpX = -targetDy / targetDist; var perpY = targetDx / targetDist; // Apply zigzag movement self.x = self.zigzagStartX + targetDx * progress + perpX * zigzagOffset; self.y = self.zigzagStartY + targetDy * progress + perpY * zigzagOffset; // Ball follows during dribble if (distance < 100) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } } // End zigzag dribbling and apply Devoured effect if (progress >= 1) { self.isZigzagDribbling = false; self.applyDevouredEffect(self.zigzagDevourRadius); // Chain up to 6 zigzags, then pause self.zigzagCount = (self.zigzagCount || 0) + 1; if (self.zigzagCount < 6) { // Start another zigzag immediately self.isZigzagDribbling = true; self.zigzagStartTime = Date.now(); self.zigzagStartX = self.x; self.zigzagStartY = self.y; // Dribble toward player goal with reduced distance self.zigzagTargetX = self.x + (playerGoal.x - self.x) * 0.3 + (Math.random() - 0.5) * 80; self.zigzagTargetY = self.y + (playerGoal.y - self.y) * 0.3 + 50; } else { // Pause after 6 zigzags self.zigzagPaused = true; self.zigzagPauseStart = Date.now(); self.zigzagCount = 0; } } } // Zigzag pause logic if (self.zigzagPaused) { var pauseElapsed = Date.now() - self.zigzagPauseStart; if (pauseElapsed >= self.zigzagPauseDuration) { self.zigzagPaused = false; } } // Chop dribbling logic if (self.isChopDribbling) { var elapsed = Date.now() - self.chopStartTime; var progress = Math.min(elapsed / self.chopDuration, 1); // Quick chop movement - sharp direction change var chopPhase = progress < 0.5 ? progress * 2 : 1 - (progress - 0.5) * 2; var chopIntensity = Math.sin(chopPhase * Math.PI) * 80; // Move with chop motion var targetDx = self.chopTargetX - self.chopStartX; var targetDy = self.chopTargetY - self.chopStartY; var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy); if (targetDist > 0) { // Calculate perpendicular vector for chop var perpX = -targetDy / targetDist; var perpY = targetDx / targetDist; // Apply chop movement self.x = self.chopStartX + targetDx * progress + perpX * chopIntensity; self.y = self.chopStartY + targetDy * progress + perpY * chopIntensity; // Ball follows during dribble if (distance < 100) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } } // End chop dribbling and apply Devoured effect if (progress >= 1) { self.isChopDribbling = false; self.applyDevouredEffect(self.chopDevourRadius); } } // FLOW: If Sae is in Flow, perform continuous crescent (hilal) dribble and finish with a striker shot if (self.flowActive) { // Start Hilal dribble if not already started and not shot yet if (!self.flowHilalDribbling && !self.flowHilalShotDone && distance < 120) { self.flowHilalDribbling = true; self.flowHilalStartTime = Date.now(); self.flowHilalOriginX = self.x; self.flowHilalOriginY = self.y; // Each crescent alternates direction for visual effect var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x); var dir = self.flowHilalCount % 2 === 0 ? 1 : -1; self.flowHilalStartAngle = angleToGoal - dir * Math.PI / 2; self.flowHilalEndAngle = angleToGoal + dir * Math.PI / 2; self.flowHilalPhase = 0; } // Hilal dribble logic if (self.flowHilalDribbling && !self.flowHilalShotDone) { var elapsed = Date.now() - self.flowHilalStartTime; var t = Math.min(elapsed / self.flowHilalDuration, 1); var angle = self.flowHilalStartAngle + (self.flowHilalEndAngle - self.flowHilalStartAngle) * t; self.x = self.flowHilalOriginX + Math.cos(angle) * self.flowHilalRadius; self.y = self.flowHilalOriginY + Math.sin(angle) * self.flowHilalRadius; // Ball follows during dribble if (distance < 120) { ball.x = self.x; ball.y = self.y; ball.velocityX = 0; ball.velocityY = 0; } // When crescent completes, start next or shoot if (t >= 1) { self.flowHilalDribbling = false; self.flowHilalCount++; // Visual effect for each crescent tween(self, { scaleX: 1.15, scaleY: 1.15, tint: 0xFF6600 }, { duration: 120, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 120 }); } }); // After enough crescents, shoot at goal if (self.flowHilalCount >= self.flowHilalMax) { // Striker shot: powerful, straight, with curve var goalDx = playerGoal.x - self.x; var goalDy = playerGoal.y - self.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { var curveAmount = 18 + Math.random() * 8; ball.velocityX = goalDx / goalDistance * 20 + curveAmount; ball.velocityY = goalDy / goalDistance * 20; ball.active = true; // Visual effect for shot tween(self, { scaleX: 1.3, scaleY: 1.3, tint: 0xFF6600 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); } self.flowHilalShotDone = true; // Reset after a short delay for next Flow LK.setTimeout(function () { self.flowActive = false; self.flowHilalCount = 0; self.flowHilalShotDone = false; }, 1200); } else { // Start next crescent immediately self.flowHilalDribbling = true; self.flowHilalStartTime = Date.now(); self.flowHilalOriginX = self.x; self.flowHilalOriginY = self.y; var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x); var dir = self.flowHilalCount % 2 === 0 ? 1 : -1; self.flowHilalStartAngle = angleToGoal - dir * Math.PI / 2; self.flowHilalEndAngle = angleToGoal + dir * Math.PI / 2; self.flowHilalPhase = 0; } } // Prevent other dribbling/shot logic during Flow return; } } // (Original code follows) // Sae behavior: pursue ball aggressively but stay in attacking areas if (distance < 500) { // Use dash if available and close to ball if (!self.dashActive && self.dashCooldown <= 0 && distance < 150) { self.dashActive = true; self.dashCooldown = self.dashCooldownTime; self.dashEndTime = Date.now() + self.dashDuration; self.speed = self.dashSpeed; // Visual effect for dash tween(self, { scaleX: 1.3, scaleY: 1.3, tint: 0x00FFFF }, { duration: 150, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 150 }); } }); } // Move toward ball but avoid going too deep into player's half var pursuitSpeed = distance < 200 ? self.speed * 1.4 : self.speed * 1.2; // Limit Sae's movement to stay in upper half of field (y < 1500) var targetX = self.x + dx / distance * pursuitSpeed; var targetY = self.y + dy / distance * pursuitSpeed; if (targetY > 1500) { // Don't go too deep, stay in attacking position targetY = Math.min(targetY, 1500); } self.x = targetX; self.y = targetY; // Actions when close to ball if (distance < 80) { // Perfect Pass - find forward player to pass to if (self.perfectPassCooldown <= 0) { var nearestForward = null; var nearestDistance = Infinity; for (var i = 0; i < opponents.length; i++) { if (opponents[i].role === 'forward') { var forwardDx = opponents[i].x - self.x; var forwardDy = opponents[i].y - self.y; var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy); if (forwardDistance < nearestDistance && forwardDistance < self.perfectPassRange) { nearestDistance = forwardDistance; nearestForward = opponents[i]; } } } if (nearestForward) { // Execute Perfect Pass var passDx = nearestForward.x - ball.x; var passDy = nearestForward.y - ball.y; var passDist = Math.sqrt(passDx * passDx + passDy * passDy); if (passDist > 0) { ball.velocityX = passDx / passDist * 15; ball.velocityY = passDy / passDist * 15; ball.active = true; } self.perfectPassCooldown = self.perfectPassCooldownTime; // Visual effect for Perfect Pass tween(ball, { scaleX: 1.4, scaleY: 1.4, tint: 0x00FF00 }, { duration: 200, onFinish: function onFinish() { tween(ball, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); } } // Choose dribbling type randomly if no pass available if (!self.isDribbling && !self.isZigzagDribbling && !self.isChopDribbling && self.perfectPassCooldown > 0) { var dribbleType = Math.random(); if (dribbleType < 0.33) { // Half-circle dribbling self.isDribbling = true; self.dribbleStartTime = Date.now(); self.dribbleRadius = 100; // Calculate angle from Sae to player goal var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x); self.dribbleStartAngle = angleToGoal - Math.PI / 2; self.dribbleEndAngle = angleToGoal + Math.PI / 2; self.dribbleOriginX = self.x; self.dribbleOriginY = self.y; } else if (dribbleType < 0.66) { // Zigzag dribbling self.isZigzagDribbling = true; self.zigzagStartTime = Date.now(); self.zigzagStartX = self.x; self.zigzagStartY = self.y; // Dribble toward player goal with reduced distance self.zigzagTargetX = self.x + (playerGoal.x - self.x) * 0.3 + (Math.random() - 0.5) * 80; self.zigzagTargetY = self.y + (playerGoal.y - self.y) * 0.3 + 50; // Visual effect for zigzag start tween(self, { tint: 0xFF4500 }, { duration: 200, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 200 }); } }); } else { // Chop dribbling self.isChopDribbling = true; self.chopStartTime = Date.now(); self.chopStartX = self.x; self.chopStartY = self.y; // Chop toward player goal (even shorter range) self.chopTargetX = self.x + (playerGoal.x - self.x) * 0.2 + (Math.random() - 0.5) * 30; self.chopTargetY = self.y + (playerGoal.y - self.y) * 0.2 + 20; // Visual effect for chop start tween(self, { tint: 0x32CD32 }, { duration: 150, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 150 }); } }); } } // False shot ability if (self.falseShotCooldown <= 0 && !self.falseShotActive) { self.falseShotActive = true; self.falseShotCooldown = self.falseShotCooldownTime; // Fake shot motion - ball barely moves ball.velocityX = (Math.random() - 0.5) * 3; ball.velocityY = (Math.random() - 0.5) * 3; ball.active = true; // Visual effect for false shot tween(self, { scaleX: 1.2, scaleY: 1.2, tint: 0xFF6600 }, { duration: 300, onFinish: function onFinish() { // After fake, real shot toward goal var goalDx = playerGoal.x - ball.x; var goalDy = playerGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { ball.velocityX = goalDx / goalDistance * 14; ball.velocityY = goalDy / goalDistance * 14; ball.active = true; } tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); self.falseShotActive = false; } }); } } } else { // Sae is always active: maintain attacking midfield position var attackingMidfieldX = 1024 + (Math.random() - 0.5) * 300; var attackingMidfieldY = 1000 + Math.random() * 200; // Stay in upper midfield area var shotDx = attackingMidfieldX - self.x; var shotDy = attackingMidfieldY - self.y; var shotDist = Math.sqrt(shotDx * shotDx + shotDy * shotDy); if (shotDist > 50) { self.x += shotDx / shotDist * self.speed * 0.5; self.y += shotDy / shotDist * self.speed * 0.5; } // If far from ball but in shooting range, attempt long-range curved shot if (distance > 200 && distance < 700 && !self.isDribbling && !self.isZigzagDribbling && !self.isChopDribbling && !self.falseShotActive) { // Falsolu şut: apply curve by adding to X velocity var goalDx = playerGoal.x - ball.x; var goalDy = playerGoal.y - ball.y; var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy); if (goalDistance > 0) { var curveAmount = 12 + Math.random() * 8; // Stronger curve for long shot ball.velocityX = goalDx / goalDistance * 16 + curveAmount; ball.velocityY = goalDy / goalDistance * 16; ball.active = true; // Visual effect for long-range shot tween(self, { scaleX: 1.2, scaleY: 1.2, tint: 0xFF1493 }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); } }); } } } // Apply Devoured effect to nearby opponents self.applyDevouredEffect = function (radius) { var opponentsInRange = []; // Check all player team members if (typeof player !== 'undefined') { var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y)); if (playerDist <= radius) { opponentsInRange.push(player); } } if (typeof chigiri !== 'undefined') { var chigiriDist = Math.sqrt((chigiri.x - self.x) * (chigiri.x - self.x) + (chigiri.y - self.y) * (chigiri.y - self.y)); if (chigiriDist <= radius) { opponentsInRange.push(chigiri); } } if (typeof hiori !== 'undefined') { var hioriDist = Math.sqrt((hiori.x - self.x) * (hiori.x - self.x) + (hiori.y - self.y) * (hiori.y - self.y)); if (hioriDist <= radius) { opponentsInRange.push(hiori); } } if (typeof reo !== 'undefined') { var reoDist = Math.sqrt((reo.x - self.x) * (reo.x - self.x) + (reo.y - self.y) * (reo.y - self.y)); if (reoDist <= radius) { opponentsInRange.push(reo); } } if (typeof nagi !== 'undefined') { var nagiDist = Math.sqrt((nagi.x - self.x) * (nagi.x - self.x) + (nagi.y - self.y) * (nagi.y - self.y)); if (nagiDist <= radius) { opponentsInRange.push(nagi); } } // Apply Devoured effect to found opponents for (var i = 0; i < opponentsInRange.length; i++) { var opponent = opponentsInRange[i]; // 30% chance to apply full effect, otherwise just stun var isFullDevoured = Math.random() < 0.3; if (isFullDevoured) { // Full Devoured effect: severely reduce speed opponent.originalSpeed = opponent.speed; opponent.speed = opponent.speed * 0.2; // 20% speed opponent.devoured = true; self.devouredOpponents.push(opponent); // Visual effect for devoured opponent tween(opponent, { tint: 0x800080, // Purple tint for devoured scaleX: 0.8, scaleY: 0.8 }, { duration: 300 }); } else { // Just stun effect opponent.originalSpeed = opponent.speed; opponent.speed = 0; opponent.stunned = true; self.dribbleStunnedOpponents.push(opponent); // Visual effect for stunned opponent tween(opponent, { tint: 0xFFFF00, scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } } if (opponentsInRange.length > 0) { self.devouredEndTime = Date.now() + self.devouredDuration; if (self.dribbleStunnedOpponents.length > 0) { self.dribbleStunEnd = Date.now() + self.dribbleStunDuration; } } }; }; return self; }); var SoccerBall = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.friction = 0.98; self.active = true; self.update = function () { if (self.active) { self.x += self.velocityX; self.y += self.velocityY; self.velocityX *= self.friction; self.velocityY *= self.friction; // Keep ball in bounds with bounce effect if (self.x < 30) { self.x = 30; self.velocityX = Math.abs(self.velocityX) * 0.8; } if (self.x > 2018) { self.x = 2018; self.velocityX = -Math.abs(self.velocityX) * 0.8; } if (self.y < 30) { self.y = 30; self.velocityY = Math.abs(self.velocityY) * 0.8; } if (self.y > 2702) { self.y = 2702; self.velocityY = -Math.abs(self.velocityY) * 0.8; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ game.setBackgroundColor(0x228B22); // Game variables var opponents = []; var playerScore = 0; var opponentScore = 0; var dragNode = null; var maxStamina = 100; var currentStamina = 100; var staminaRegenRate = 0.2; var staminaDrainRate = 0.5; var lastPlayerX = 0; var lastPlayerY = 0; var shotChargeStart = 0; var isChargingShot = false; var shotPower = 0; var maxShotPower = 20; var minChargeTime = 2000; // 2 seconds minimum charge time var lastTouchX = 0; var lastTouchY = 0; var ballDetachCooldown = 0; var ballDetachCooldownTime = 1000; // 1 second cooldown // UI Elements var scoreTxt = new Text2('Player: 0 - Opponent: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Stamina UI var staminaTxt = new Text2('Stamina: 100', { size: 60, fill: 0x00FF00 }); staminaTxt.anchor.set(0, 0); staminaTxt.x = 50; staminaTxt.y = 50; LK.gui.topLeft.addChild(staminaTxt); // Shot Power UI var shotPowerTxt = new Text2('Shot Power: 0%', { size: 60, fill: 0xFFFFFF }); shotPowerTxt.anchor.set(0, 0); shotPowerTxt.x = 50; shotPowerTxt.y = 130; LK.gui.topLeft.addChild(shotPowerTxt); // Game objects var player = game.addChild(new Player()); player.x = 1024; player.y = 2200; var ball = game.addChild(new SoccerBall()); ball.x = 1024; ball.y = 1366; // Player's goal (bottom) var playerGoal = game.addChild(new Goal()); playerGoal.x = 1024; playerGoal.y = 2600; // Opponent's goal (top) var opponentGoal = game.addChild(new Goal()); opponentGoal.x = 1024; opponentGoal.y = 300; // Create opponents with specific roles // Defense player (opponent) var defensePlayer = game.addChild(new DefensePlayer()); defensePlayer.x = 1024; defensePlayer.y = 600; defensePlayer.homeX = 1024; defensePlayer.homeY = 600; opponents.push(defensePlayer); // Chigiri - player team hybrid defender var chigiri = game.addChild(new ChigiriPlayer(0x3399ff)); chigiri.x = 800; chigiri.y = 2000; chigiri.homeX = 800; chigiri.homeY = 2000; // Hiori - player team creative midfielder var hiori = game.addChild(new HioriPlayer(0x6699ff)); hiori.x = 1200; hiori.y = 2000; hiori.homeX = 1200; hiori.homeY = 2000; // Reo - player team adaptive defender/attacker var reo = game.addChild(new ReoPlayer(0x000000)); reo.x = 600; reo.y = 1800; reo.homeX = 600; reo.homeY = 1800; // Nagi - player team striker with Fake Volley ability var nagi = game.addChild(new NagiPlayer(0x9966ff)); nagi.x = 1400; nagi.y = 2000; nagi.homeX = 1400; nagi.homeY = 2000; // Nagi starts without Flow // Meguru Bachira - player team creative winger with Roulette and Monster abilities var bachira = game.addChild(new MeguruBachiraPlayer(0x20B2AA)); bachira.x = 1000; bachira.y = 1900; bachira.homeX = 1000; bachira.homeY = 1900; // Midfield player var midfieldPlayer = game.addChild(new MidfieldPlayer()); midfieldPlayer.x = 1024; midfieldPlayer.y = 1000; midfieldPlayer.homeX = 1024; midfieldPlayer.homeY = 1000; opponents.push(midfieldPlayer); // Forward player var forwardPlayer = game.addChild(new ForwardPlayer()); forwardPlayer.x = 1024; forwardPlayer.y = 1400; forwardPlayer.homeX = 1024; forwardPlayer.homeY = 1400; opponents.push(forwardPlayer); // Sae - opponent team attacking midfielder with Perfect Pass and special abilities var sae = game.addChild(new SaePlayer(0xff6600)); sae.x = 1024; sae.y = 800; sae.homeX = 1024; sae.homeY = 800; opponents.push(sae); // Create goalkeepers var playerGoalkeeper = game.addChild(new Goalkeeper(true)); // Blue goalkeeper for player team playerGoalkeeper.x = 1024; playerGoalkeeper.y = 2500; // In front of player's goal playerGoalkeeper.originalX = 1024; playerGoalkeeper.originalY = 2500; var opponentGoalkeeper = game.addChild(new Goalkeeper(false)); // Red goalkeeper for opponent team opponentGoalkeeper.x = 1024; opponentGoalkeeper.y = 400; // In front of opponent's goal opponentGoalkeeper.originalX = 1024; opponentGoalkeeper.originalY = 400; // Ball kicking mechanics function kickBall(targetX, targetY, power) { var dx = targetX - ball.x; var dy = targetY - ball.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var finalPower = power || Math.min(distance / 100, 15); // Properly detach ball from player first player.hasBall = false; ball.active = true; // Set cooldown to prevent immediate re-attachment ballDetachCooldown = ballDetachCooldownTime; // Apply velocity after detachment ball.velocityX = dx / distance * finalPower; ball.velocityY = dy / distance * finalPower; LK.getSound('kick').play(); } } // Touch controls for player movement and ball kicking game.down = function (x, y, obj) { // Always update last touch position for shot direction lastTouchX = x; lastTouchY = y; var playerDistance = Math.sqrt((x - player.x) * (x - player.x) + (y - player.y) * (y - player.y)); var ballDistance = Math.sqrt((x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y)); if (playerDistance < ballDistance) { // Move player dragNode = player; } else { // Start charging shot if player has ball if (player.hasBall) { shotChargeStart = Date.now(); isChargingShot = true; shotPower = 0; } } }; game.move = function (x, y, obj) { // Always update last touch position for shot direction lastTouchX = x; lastTouchY = y; if (dragNode && currentStamina > 0) { var dx = x - dragNode.x; var dy = y - dragNode.y; var distance = Math.sqrt(dx * dx + dy * dy); // Slow down movement and consume stamina - even slower when having ball var moveSpeed = player.hasBall ? 0.15 : 0.2; dragNode.x += dx * moveSpeed; dragNode.y += dy * moveSpeed; // Consume stamina based on movement if (distance > 5) { currentStamina -= staminaDrainRate; if (currentStamina < 0) currentStamina = 0; } } }; game.up = function (x, y, obj) { dragNode = null; // Handle shot release if (isChargingShot && player.hasBall) { var chargeTime = Date.now() - shotChargeStart; if (chargeTime >= minChargeTime) { // Release charged shot - use last touch position for direction kickBall(lastTouchX, lastTouchY, shotPower); } else { // Not charged enough, no shot } isChargingShot = false; shotPower = 0; } }; // Goal scoring function function scoreGoal(isPlayer) { if (isPlayer) { playerScore++; } else { opponentScore++; } scoreTxt.setText('Player: ' + playerScore + ' - Opponent: ' + opponentScore); LK.getSound('goal').play(); // Reset ball position ball.x = 1024; ball.y = 1366; ball.velocityX = 0; ball.velocityY = 0; } // Game update loop game.update = function () { // Update ball detach cooldown if (ballDetachCooldown > 0) { ballDetachCooldown -= 16; // Approximately 60 FPS if (ballDetachCooldown < 0) ballDetachCooldown = 0; } // Check if player is close to ball for interaction (only if cooldown expired) var playerToBallDistance = Math.sqrt((player.x - ball.x) * (player.x - ball.x) + (player.y - ball.y) * (player.y - ball.y)); if (playerToBallDistance < 80 && !player.hasBall && ballDetachCooldown <= 0) { player.hasBall = true; ball.active = false; ball.velocityX = 0; ball.velocityY = 0; } // If player has ball, make it stick to player if (player.hasBall && ball.active === false) { ball.x = player.x; ball.y = player.y - 50; } // Stamina regeneration when not moving var playerMoving = Math.abs(player.x - lastPlayerX) > 1 || Math.abs(player.y - lastPlayerY) > 1; if (!playerMoving && currentStamina < maxStamina) { currentStamina += staminaRegenRate; if (currentStamina > maxStamina) currentStamina = maxStamina; } // Update last position lastPlayerX = player.x; lastPlayerY = player.y; // Update stamina UI var staminaPercent = Math.round(currentStamina / maxStamina * 100); staminaTxt.setText('Stamina: ' + staminaPercent); // Change color based on stamina level if (staminaPercent > 50) { staminaTxt.fill = 0x00FF00; } else if (staminaPercent > 25) { staminaTxt.fill = 0xFFFF00; } else { staminaTxt.fill = 0xFF0000; } // Update shot charging if (isChargingShot && player.hasBall) { var chargeTime = Date.now() - shotChargeStart; if (chargeTime >= minChargeTime) { // Calculate shot power based on charge time var chargeProgress = Math.min((chargeTime - minChargeTime) / 1000, 1); // 1 second after minimum for max power shotPower = 5 + chargeProgress * maxShotPower; // Minimum 5, maximum 25 power var powerPercent = Math.round(chargeProgress * 100); shotPowerTxt.setText('Shot Power: ' + powerPercent + '%'); shotPowerTxt.fill = 0x00FF00; // Automatically kick ball forward when shot power reaches 100% if (powerPercent >= 100) { shotPower = 5 + maxShotPower; // Keep at maximum power shotPowerTxt.setText('Shot Power: 100%'); shotPowerTxt.fill = 0x00FF00; // Auto-kick ball forward when power reaches 100% var forwardX = player.x; var forwardY = player.y - 300; // Kick forward (up the field) kickBall(forwardX, forwardY, shotPower); isChargingShot = false; shotPower = 0; } } else { // Not charged enough yet var timeLeft = Math.ceil((minChargeTime - chargeTime) / 1000); shotPowerTxt.setText('Hold for ' + timeLeft + 's'); shotPowerTxt.fill = 0xFFFF00; } } else { shotPowerTxt.setText('Shot Power: 0%'); shotPowerTxt.fill = 0xFFFFFF; } // Check player goal scoring (opponent scores) if (ball.y > playerGoal.y - 50 && ball.x > playerGoal.x - 200 && ball.x < playerGoal.x + 200) { scoreGoal(false); LK.effects.flashObject(playerGoal, 0xFF0000, 500); } // Check opponent goal scoring (player scores) if (ball.y < opponentGoal.y + 50 && ball.x > opponentGoal.x - 200 && ball.x < opponentGoal.x + 200) { scoreGoal(true); LK.effects.flashObject(opponentGoal, 0x00FF00, 500); } // Check win condition if (playerScore >= 5) { LK.showYouWin(); } if (opponentScore >= 5) { LK.showGameOver(); } // Enhanced AI positioning based on ball location for (var i = 0; i < opponents.length; i++) { var opponent = opponents[i]; var ballThreatLevel = Math.sqrt((ball.x - 1024) * (ball.x - 1024) + (ball.y - 1366) * (ball.y - 1366)); // Adjust opponent aggression based on ball threat if (ballThreatLevel < 300) { // Ball is in center - all opponents become more aggressive if (opponent.role === 'defense') { opponent.speed = 4.5; opponent.maxDistance = 500; } else if (opponent.role === 'midfield') { opponent.speed = 3.5; } else if (opponent.role === 'forward') { opponent.speed = 4.2; } } else { // Ball is far - return to normal behavior if (opponent.role === 'defense') { opponent.speed = 4; opponent.maxDistance = 400; } else if (opponent.role === 'midfield') { opponent.speed = 3; } else if (opponent.role === 'forward') { opponent.speed = 3.5; } } } // Role-based AI is now handled in each player's update method // Defense, Midfield, and Forward players have their own specialized behaviors };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var ChigiriPlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var opponentGraphics = self.attachAsset('opponent', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && opponentGraphics) {
opponentGraphics.tint = colorOverride;
}
self.speed = 5;
self.role = 'chigiri';
self.homeX = 800;
self.homeY = 2000;
self.maxDistance = 600;
self.hasBall = false;
self.isSpeedBursting = false;
self.speedBurstTarget = null;
self.speedBurstCooldown = 0;
self.speedBurstCooldownTime = 8000; // 8 seconds
self.normalSpeed = 5;
self.burstSpeed = 12;
self.stamina = 100;
self.maxStamina = 100;
self.staminaDrainRate = 0.8;
self.staminaRegenRate = 0.3;
self.lowStaminaSpeed = 1.5; // Very slow when stamina is low
self.update = function () {
// Update speed burst cooldown
if (self.speedBurstCooldown > 0) {
self.speedBurstCooldown -= 16;
if (self.speedBurstCooldown < 0) self.speedBurstCooldown = 0;
}
// Steal ball if opponent is inside area
var nearestOpponent = null;
var nearestDistance = Infinity;
for (var i = 0; i < opponents.length; i++) {
var opponentDist = Math.sqrt((opponents[i].x - self.x) * (opponents[i].x - self.x) + (opponents[i].y - self.y) * (opponents[i].y - self.y));
if (opponentDist < nearestDistance) {
nearestDistance = opponentDist;
nearestOpponent = opponents[i];
}
}
if (nearestOpponent && nearestDistance < 80 && nearestOpponent.hasBall) {
// Steal the ball
nearestOpponent.hasBall = false;
ball.active = true;
// Ball moves toward Chigiri
var stealDx = self.x - nearestOpponent.x;
var stealDy = self.y - nearestOpponent.y;
var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy);
if (stealDist > 0) {
ball.velocityX = stealDx / stealDist * 8;
ball.velocityY = stealDy / stealDist * 8;
}
ballDetachCooldown = ballDetachCooldownTime;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Enhanced ball pursuit for hybrid player
if (distance < 500 && !self.isSpeedBursting) {
// Chase ball with hybrid intelligence
var chaseSpeed = distance < 150 ? self.normalSpeed * 1.4 : self.normalSpeed * 1.1;
self.x += dx / distance * chaseSpeed;
self.y += dy / distance * chaseSpeed;
}
// Speed burst mechanic
if (!self.isSpeedBursting && self.speedBurstCooldown <= 0 && distance < 100) {
// Throw ball forward and start speed burst
var throwX = opponentGoal.x + (Math.random() - 0.5) * 200;
var throwY = opponentGoal.y + 200;
var throwDx = throwX - ball.x;
var throwDy = throwY - ball.y;
var throwDist = Math.sqrt(throwDx * throwDx + throwDy * throwDy);
if (throwDist > 0) {
ball.velocityX = throwDx / throwDist * 14;
ball.velocityY = throwDy / throwDist * 14;
}
// Set speed burst target and activate
self.speedBurstTarget = {
x: throwX,
y: throwY
};
self.isSpeedBursting = true;
self.speedBurstCooldown = self.speedBurstCooldownTime; // Start cooldown
// Visual effect for speed burst
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
}
if (self.isSpeedBursting && self.speedBurstTarget) {
// Move at burst speed toward target
var burstDx = self.speedBurstTarget.x - self.x;
var burstDy = self.speedBurstTarget.y - self.y;
var burstDist = Math.sqrt(burstDx * burstDx + burstDy * burstDy);
if (burstDist > 30) {
self.x += burstDx / burstDist * self.burstSpeed;
self.y += burstDy / burstDist * self.burstSpeed;
// Drain stamina during speed burst
self.stamina -= self.staminaDrainRate * 2; // Double drain during burst
if (self.stamina < 0) self.stamina = 0;
} else {
// Reached target, stop speed burst
self.isSpeedBursting = false;
self.speedBurstTarget = null;
}
} else {
// Stamina regeneration when not moving much
var currentSpeed = Math.sqrt((self.x - (self.lastMoveX || self.x)) * (self.x - (self.lastMoveX || self.x)) + (self.y - (self.lastMoveY || self.y)) * (self.y - (self.lastMoveY || self.y)));
if (currentSpeed < 2 && self.stamina < self.maxStamina) {
self.stamina += self.staminaRegenRate;
if (self.stamina > self.maxStamina) self.stamina = self.maxStamina;
}
self.lastMoveX = self.x;
self.lastMoveY = self.y;
// Determine current speed based on stamina
var currentMoveSpeed = self.stamina > 20 ? self.normalSpeed : self.lowStaminaSpeed;
// Normal hybrid behavior - enhanced ball pursuit
if (distance < 500) {
// Move toward ball with improved midfielder intelligence but adjust for stamina
var pursuitSpeed = distance < 200 ? currentMoveSpeed * 1.3 : currentMoveSpeed * 1.1;
self.x += dx / distance * pursuitSpeed;
self.y += dy / distance * pursuitSpeed;
// Drain stamina during pursuit
self.stamina -= self.staminaDrainRate * 0.5;
if (self.stamina < 0) self.stamina = 0;
// Attack opponent goal if close to ball
if (distance < 80) {
var goalDx = opponentGoal.x - ball.x;
var goalDy = opponentGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 11;
ball.velocityY = goalDy / goalDistance * 11;
}
}
} else {
// Return to defensive position
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
// Use stamina-adjusted speed for returning home
var currentMoveSpeed = self.stamina > 20 ? self.normalSpeed : self.lowStaminaSpeed;
self.x += homeDx / homeDistance * currentMoveSpeed * 0.4;
self.y += homeDy / homeDistance * currentMoveSpeed * 0.4;
// Light stamina drain when returning to position
self.stamina -= self.staminaDrainRate * 0.3;
if (self.stamina < 0) self.stamina = 0;
}
}
}
};
return self;
});
var DefensePlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var opponentGraphics = self.attachAsset('opponent', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && opponentGraphics) {
opponentGraphics.tint = colorOverride;
}
self.speed = 4;
self.role = 'defense';
self.homeX = 1024;
self.homeY = 600;
self.maxDistance = 400;
self.hasBall = false;
self.update = function () {
// Steal ball if player is inside opponent
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist < 80 && player.hasBall) {
// Steal the ball
player.hasBall = false;
ball.active = true;
// Ball moves away from player, toward defense
var stealDx = self.x - player.x;
var stealDy = self.y - player.y;
var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy);
if (stealDist > 0) {
ball.velocityX = stealDx / stealDist * 10;
ball.velocityY = stealDy / stealDist * 10;
}
// Prevent immediate re-attachment
ballDetachCooldown = ballDetachCooldownTime;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Defense priority: desperately try to get the ball
if (distance < 400) {
// Move aggressively toward ball - increased chase range
self.x += dx / distance * (self.speed * 1.2);
self.y += dy / distance * (self.speed * 1.2);
// Sliding tackle if player is close to defense
var playerDistance = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDistance < 120 && ball.active && !player.hasBall) {
// Slide tackle: dash toward ball and clear it away
var slideDx = ball.x - self.x;
var slideDy = ball.y - self.y;
var slideDist = Math.sqrt(slideDx * slideDx + slideDy * slideDy);
if (slideDist > 0) {
// Animate slide (quick move)
tween(self, {
x: self.x + slideDx / slideDist * 60,
y: self.y + slideDy / slideDist * 60
}, {
duration: 120,
easing: tween.cubicOut
});
// Clear ball far away from player
var clearX = ball.x < 1024 ? ball.x - 400 : ball.x + 400;
var clearY = ball.y - 500;
var clearDx = clearX - ball.x;
var clearDy = clearY - ball.y;
var clearDist = Math.sqrt(clearDx * clearDx + clearDy * clearDy);
if (clearDist > 0) {
ball.velocityX = clearDx / clearDist * 13;
ball.velocityY = clearDy / clearDist * 13;
}
}
}
// Kick ball away from player goal if close enough
if (distance < 80) {
// Find nearest forward player
var nearestForward = null;
var nearestDistance = Infinity;
for (var i = 0; i < opponents.length; i++) {
if (opponents[i].role === 'forward') {
var forwardDx = opponents[i].x - self.x;
var forwardDy = opponents[i].y - self.y;
var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy);
if (forwardDistance < nearestDistance) {
nearestDistance = forwardDistance;
nearestForward = opponents[i];
}
}
}
// Pass to forward if found, otherwise clear as before
if (nearestForward) {
var passDx = nearestForward.x - ball.x;
var passDy = nearestForward.y - ball.y;
var passDistance = Math.sqrt(passDx * passDx + passDy * passDy);
if (passDistance > 0) {
ball.velocityX = passDx / passDistance * 10;
ball.velocityY = passDy / passDistance * 10;
}
} else {
var kickAwayX = ball.x < 1024 ? ball.x - 200 : ball.x + 200;
var kickAwayY = ball.y - 300; // Kick upfield
var kickDx = kickAwayX - ball.x;
var kickDy = kickAwayY - ball.y;
var kickDistance = Math.sqrt(kickDx * kickDx + kickDy * kickDy);
if (kickDistance > 0) {
ball.velocityX = kickDx / kickDistance * 8;
ball.velocityY = kickDy / kickDistance * 8;
}
}
}
} else {
// Return to defensive position
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.5;
self.y += homeDy / homeDistance * self.speed * 0.5;
}
}
};
return self;
});
var ForwardPlayer = Container.expand(function () {
var self = Container.call(this);
var opponentGraphics = self.attachAsset('opponent', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3.5;
self.role = 'forward';
self.homeX = 1024;
self.homeY = 1400;
self.hasBall = false;
self.directShotCooldown = self.directShotCooldown || 0;
self.update = function () {
// Steal ball if player is inside opponent
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist < 80 && player.hasBall) {
// Steal the ball
player.hasBall = false;
ball.active = true;
// Ball moves away from player, toward forward
var stealDx = self.x - player.x;
var stealDy = self.y - player.y;
var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy);
if (stealDist > 0) {
ball.velocityX = stealDx / stealDist * 10;
ball.velocityY = stealDy / stealDist * 10;
}
// Prevent immediate re-attachment
ballDetachCooldown = ballDetachCooldownTime;
}
// Handle direct shot cooldown
if (self.directShotCooldown > 0) {
self.directShotCooldown -= 16;
if (self.directShotCooldown < 0) self.directShotCooldown = 0;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Forward: attack and try to score
if (distance < 400) {
// Move toward ball with aggressive pursuit
var pursuitSpeed = distance < 200 ? self.speed * 1.8 : self.speed * 1.3;
self.x += dx / distance * pursuitSpeed;
self.y += dy / distance * pursuitSpeed;
// If close to ball, shoot at goal
if (distance < 80) {
// Direct Shot every 30s
if (self.directShotCooldown <= 0) {
var goalDx = playerGoal.x - ball.x;
var goalDy = playerGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
// Direct Shot: very fast and straight
ball.velocityX = goalDx / goalDistance * 18;
ball.velocityY = goalDy / goalDistance * 18;
}
self.directShotCooldown = 30000; // 30 seconds
} else {
// Normal shot
var goalDx = playerGoal.x - ball.x;
var goalDy = playerGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 10;
ball.velocityY = goalDy / goalDistance * 10;
}
}
}
} else {
// Move toward attacking position
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.4;
self.y += homeDy / homeDistance * self.speed * 0.4;
}
}
};
return self;
});
var Goal = Container.expand(function () {
var self = Container.call(this);
// Goal area
var goalGraphics = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5
});
goalGraphics.alpha = 0.3;
// Left goalpost
var leftPost = self.addChild(LK.getAsset('goalpost', {
anchorX: 0.5,
anchorY: 0.5
}));
leftPost.x = -210;
leftPost.y = 0;
// Right goalpost
var rightPost = self.addChild(LK.getAsset('goalpost', {
anchorX: 0.5,
anchorY: 0.5
}));
rightPost.x = 210;
rightPost.y = 0;
return self;
});
var Goalkeeper = Container.expand(function (isPlayer) {
var self = Container.call(this);
var keeperGraphics = self.attachAsset(isPlayer ? 'player' : 'opponent', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.isPlayer = isPlayer;
self.rushCooldown = 0;
self.rushCooldownTime = 20000; // 20 seconds
self.isRushing = false;
self.originalX = 0;
self.originalY = 0;
self.rushSpeed = 8;
self.update = function () {
// Update rush cooldown
if (self.rushCooldown > 0) {
self.rushCooldown -= 16; // Approximately 60 FPS
if (self.rushCooldown < 0) self.rushCooldown = 0;
}
// Check if should rush for ball
var ballDistance = Math.sqrt((ball.x - self.x) * (ball.x - self.x) + (ball.y - self.y) * (ball.y - self.y));
if (!self.isRushing && self.rushCooldown <= 0 && ballDistance < 400) {
// Start rushing
self.isRushing = true;
self.rushCooldown = self.rushCooldownTime;
}
if (self.isRushing) {
// Rush toward ball
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0 && distance > 50) {
self.x += dx / distance * self.rushSpeed;
self.y += dy / distance * self.rushSpeed;
} else if (distance <= 50) {
// Catch ball if close enough
ball.velocityX = 0;
ball.velocityY = 0;
ball.x = self.x;
ball.y = self.y;
ball.active = false;
player.hasBall = false;
// Return to original position first
self.isRushing = false;
tween(self, {
x: self.originalX,
y: self.originalY
}, {
duration: 1000,
onFinish: function onFinish() {
// After returning to position, throw ball to center field
ball.active = true;
var centerX = 1024;
var centerY = 1366;
var throwPower = 15;
var throwDx = centerX - ball.x;
var throwDy = centerY - ball.y;
var throwDistance = Math.sqrt(throwDx * throwDx + throwDy * throwDy);
if (throwDistance > 0) {
ball.velocityX = throwDx / throwDistance * throwPower;
ball.velocityY = throwDy / throwDistance * throwPower;
}
}
});
}
} else {
// Normal goalkeeper movement - stay near goal line
var goalCenterX = 1024;
var dx = goalCenterX - self.x;
if (Math.abs(dx) > 5) {
self.x += dx > 0 ? self.speed : -self.speed;
}
}
};
return self;
});
var HioriPlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && playerGraphics) {
playerGraphics.tint = colorOverride;
}
self.speed = 4;
self.role = 'hiori';
self.homeX = 1200;
self.homeY = 2000;
self.hasBall = false;
// FLOW SYSTEM
self.flowActive = false;
self.flowDribbleCount = 0;
self.flowDribbleMax = 4; // 4 extra dribbles in Flow
self.flowText = null;
self.flowTriggerChecked = false; // To avoid retriggering
// Zig-zag dribbling variables
self.isDribbling = false;
self.dribbleStartTime = 0;
self.dribbleDuration = 2000; // 2 seconds - slower duration
self.dribbleSpeed = 6;
self.dribbleDirection = 1; // 1 for right, -1 for left
self.dribblePhase = 0; // 0-1 progress through dribble
self.dribbleStartX = 0;
self.dribbleStartY = 0;
self.dribbleTargetX = 0;
self.dribbleTargetY = 0;
// Zigzag pause variables
self.zigzagCount = 0;
self.zigzagPaused = false;
self.zigzagPauseStart = 0;
self.zigzagPauseDuration = 10000; // 10 seconds
// Winter Zone variables
self.winterZoneActive = false;
self.winterZoneRadius = 200;
self.winterZoneCooldown = 0;
self.winterZoneCooldownTime = 12000; // 12 seconds
self.winterZoneDuration = 0;
self.winterZoneMaxDuration = 10000; // 10 seconds - increased duration
self.slowedOpponents = [];
// Winter Zone visual
self.winterZoneVisual = null;
// Perfect pass variables
self.passChargingTime = 0;
self.perfectPassCooldown = 0;
self.perfectPassCooldownTime = 6000; // 6 seconds
self.update = function () {
// FLOW TRIGGER: If 2 goals conceded, activate Flow for Hiori (only once)
if (!self.flowActive && !self.flowTriggerChecked && typeof opponentScore !== "undefined" && opponentScore >= 2) {
self.flowActive = true;
self.flowDribbleCount = 0;
self.flowTriggerChecked = true;
// Show Flow text above Hiori
if (!self.flowText) {
self.flowText = self.addChild(new Text2('FLOW', {
size: 48,
fill: 0x00FFFF
}));
self.flowText.anchor.set(0.5, 0.5);
self.flowText.x = 0;
self.flowText.y = -100;
self.flowText.alpha = 1;
tween(self.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
});
}
}
// Remove Flow text if not in Flow
if (!self.flowActive && self.flowText) {
self.flowText.destroy();
self.flowText = null;
}
// Update cooldowns
if (self.winterZoneCooldown > 0) {
self.winterZoneCooldown -= 16;
if (self.winterZoneCooldown < 0) self.winterZoneCooldown = 0;
}
if (self.perfectPassCooldown > 0) {
self.perfectPassCooldown -= 16;
if (self.perfectPassCooldown < 0) self.perfectPassCooldown = 0;
}
// FLOW: If in Flow, boost Winter Zone and dribbling
var flowWinterZoneRadius = self.flowActive ? 420 : 200;
var flowWinterZoneSlow = self.flowActive ? 0.12 : 0.3; // 12% speed in Flow
var flowWinterZoneDuration = self.flowActive ? 18000 : self.winterZoneMaxDuration; // 18s in Flow
var flowWinterZoneCooldown = self.flowActive ? 6000 : self.winterZoneCooldownTime; // 6s in Flow
// Winter Zone duration and effect
if (self.winterZoneActive) {
self.winterZoneDuration -= 16;
if (self.winterZoneDuration <= 0) {
self.winterZoneActive = false;
// Remove Winter Zone visual
if (self.winterZoneVisual) {
self.winterZoneVisual.destroy();
self.winterZoneVisual = null;
}
// Restore opponent speeds
for (var i = 0; i < self.slowedOpponents.length; i++) {
var opponent = self.slowedOpponents[i];
opponent.speed = opponent.originalSpeed || opponent.speed * 2;
}
self.slowedOpponents = [];
} else {
// Apply Winter Zone effect to nearby opponents
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (dist <= (self.winterZoneActive ? flowWinterZoneRadius : self.winterZoneRadius)) {
if (self.slowedOpponents.indexOf(opponent) === -1) {
opponent.originalSpeed = opponent.speed;
opponent.speed = opponent.speed * (self.winterZoneActive ? flowWinterZoneSlow : 0.3); // Much slower in Flow
self.slowedOpponents.push(opponent);
}
}
}
}
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// FLOW: Dribbling logic
if (self.isDribbling) {
var elapsed = Date.now() - self.dribbleStartTime;
var progress = Math.min(elapsed / self.dribbleDuration, 1);
// Create zig-zag pattern - slower frequency for better visibility
var zigzagAmplitude = 80;
var zigzagFrequency = 4;
var zigzagOffset = Math.sin(progress * Math.PI * zigzagFrequency) * zigzagAmplitude;
// Move toward target with zig-zag motion - slower speed
var targetDx = self.dribbleTargetX - self.dribbleStartX;
var targetDy = self.dribbleTargetY - self.dribbleStartY;
var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy);
if (targetDist > 0) {
// Calculate perpendicular vector for zig-zag
var perpX = -targetDy / targetDist;
var perpY = targetDx / targetDist;
// Apply zig-zag movement with slower speed
var slowSpeed = 0.6; // Reduced from 1.0 to make it slower
self.x = self.dribbleStartX + targetDx * progress * slowSpeed + perpX * zigzagOffset;
self.y = self.dribbleStartY + targetDy * progress * slowSpeed + perpY * zigzagOffset;
// Ball follows during dribble
if (distance < 100) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
}
// End dribbling
if (progress >= 1) {
self.isDribbling = false;
self.zigzagCount = (self.zigzagCount || 0) + 1;
// FLOW: If in Flow, allow up to 4 extra dribbles in a row
if (self.flowActive && self.flowDribbleCount < self.flowDribbleMax) {
self.flowDribbleCount++;
// Start another dribble immediately
self.isDribbling = true;
self.dribbleStartTime = Date.now();
self.dribbleStartX = self.x;
self.dribbleStartY = self.y;
// Dribble toward opponent goal, randomize a bit
self.dribbleTargetX = opponentGoal.x + (Math.random() - 0.5) * 300;
self.dribbleTargetY = opponentGoal.y + 200 + Math.random() * 100;
} else {
// After 4 zigzags, pause for 10 seconds
if (self.zigzagCount >= 4) {
self.zigzagPaused = true;
self.zigzagPauseStart = Date.now();
self.zigzagCount = 0;
}
// Reset Flow dribble count after sequence
if (self.flowActive) self.flowDribbleCount = 0;
}
}
} else {
// Normal ball pursuit
if (distance < 400) {
var chaseSpeed = distance < 150 ? self.speed * 1.3 : self.speed * 1.1;
self.x += dx / distance * chaseSpeed;
self.y += dy / distance * chaseSpeed;
// Handle zigzag pause
if (self.zigzagPaused) {
var pauseElapsed = Date.now() - self.zigzagPauseStart;
if (pauseElapsed >= self.zigzagPauseDuration) {
self.zigzagPaused = false;
}
}
// Start zig-zag dribbling when close to ball (only if not paused)
if (distance < 80 && !self.isDribbling && !self.zigzagPaused) {
self.isDribbling = true;
self.dribbleStartTime = Date.now();
self.dribbleStartX = self.x;
self.dribbleStartY = self.y;
// Dribble toward opponent goal
self.dribbleTargetX = opponentGoal.x + (Math.random() - 0.5) * 300;
self.dribbleTargetY = opponentGoal.y + 200;
// FLOW: If in Flow, reset dribble count for new sequence
if (self.flowActive) self.flowDribbleCount = 0;
}
}
}
// FLOW: Winter Zone is much bigger, lasts longer, slows more, and triggers more often
if (!self.winterZoneActive && self.winterZoneCooldown <= 0) {
var nearbyOpponents = 0;
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (dist <= flowWinterZoneRadius + 50) {
nearbyOpponents++;
}
}
if (nearbyOpponents >= 2) {
// Activate Winter Zone
self.winterZoneActive = true;
self.winterZoneDuration = flowWinterZoneDuration;
self.winterZoneCooldown = flowWinterZoneCooldown;
// Create Winter Zone visual area
if (self.winterZoneVisual) {
self.winterZoneVisual.destroy();
self.winterZoneVisual = null;
}
self.winterZoneVisual = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.flowActive ? 8 : 4,
scaleY: self.flowActive ? 8 : 4,
tint: 0x87CEEB
}));
self.winterZoneVisual.alpha = 0.3;
// Visual effect
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0x87CEEB
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
}
// FLOW: If in Flow, perfect pass always goes directly to player, no curve, and is instant
if (distance < 80 && self.perfectPassCooldown <= 0) {
// Look for player to pass to
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist < 600 && playerDist > 100) {
// Execute perfect pass
var passDx = player.x - ball.x;
var passDy = player.y - ball.y;
var passDist = Math.sqrt(passDx * passDx + passDy * passDy);
if (passDist > 0) {
if (self.flowActive) {
// FLOW: Pass is instant and direct
ball.x = player.x;
ball.y = player.y - 50;
ball.velocityX = 0;
ball.velocityY = 0;
ball.active = false;
player.hasBall = true;
} else {
// Normal perfect pass with slight curve to avoid interception
var curveAmount = 3;
ball.velocityX = passDx / passDist * 12 + curveAmount;
ball.velocityY = passDy / passDist * 12;
ball.active = true;
}
self.perfectPassCooldown = self.perfectPassCooldownTime;
// Visual effect for perfect pass
tween(ball, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
onFinish: function onFinish() {
tween(ball, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
}
});
}
}
}
// Return to home position when not actively playing
if (distance > 500) {
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.4;
self.y += homeDy / homeDistance * self.speed * 0.4;
}
}
};
return self;
});
var MeguruBachiraPlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && playerGraphics) {
playerGraphics.tint = colorOverride;
}
self.speed = 4.5;
self.role = 'bachira';
self.homeX = 1000;
self.homeY = 1900;
self.hasBall = false;
// FLOW SYSTEM for Bachira
self.flowActive = false;
self.flowTriggerChecked = false;
self.flowText = null;
// Monster Trans variables (Flow mode transformation)
self.monsterTransActive = false;
self.monsterTransVisual = null;
// Roulette dribbling variables
self.rouletteActive = false;
self.rouletteStartTime = 0;
self.rouletteDuration = 1500; // 1.5 seconds
self.rouletteRotations = 3; // 3 full rotations
self.rouletteRadius = 80;
self.rouletteCooldown = 0;
self.rouletteCooldownTime = 4000; // 4 seconds
self.rouletteOriginX = 0;
self.rouletteOriginY = 0;
self.rouletteStunRadius = 120;
self.rouletteStunnedOpponents = [];
self.rouletteStunEnd = 0;
// Monster Dribbling variables
self.monsterDribblingActive = false;
self.monsterDribblingStartTime = 0;
self.monsterDribblingDuration = 2000; // 2 seconds
self.monsterDribblingSpeed = 8;
self.monsterDribblingCooldown = 0;
self.monsterDribblingCooldownTime = 6000; // 6 seconds
self.monsterDribblingStartX = 0;
self.monsterDribblingStartY = 0;
self.monsterDribblingTargetX = 0;
self.monsterDribblingTargetY = 0;
self.monsterDribblingStunRadius = 150;
self.monsterDribblingStunnedOpponents = [];
self.monsterDribblingStunEnd = 0;
// Rush to ball variables (after Roulette)
self.rushingToBall = false;
self.rushTargetX = 0;
self.rushTargetY = 0;
self.rushSpeed = 7;
self.readyToShoot = false;
self.update = function () {
// FLOW TRIGGER: Monster Trans for Bachira, random chance for Hiori, only one Flow at a time
if (!self.flowActive && !self.flowTriggerChecked) {
// Count Bachira and Hiori on field
var bachiraCount = typeof bachira !== "undefined" ? 1 : 0;
var hioriCount = typeof hiori !== "undefined" ? 1 : 0;
var totalFlowCandidates = bachiraCount + hioriCount;
var shouldFlow = false;
// If both Bachira and Hiori are present, randomly pick one for Flow
if (totalFlowCandidates > 1) {
// 50% chance for Bachira, 50% for Hiori
var pick = Math.random();
if (pick < 0.5) {
shouldFlow = true; // Bachira gets Flow
if (typeof hiori !== "undefined") {
hiori.flowActive = false;
hiori.flowTriggerChecked = true;
if (hiori.flowText) {
hiori.flowText.destroy();
hiori.flowText = null;
}
}
} else {
shouldFlow = false;
if (typeof hiori !== "undefined" && !hiori.flowActive && !hiori.flowTriggerChecked) {
hiori.flowActive = true;
hiori.flowTriggerChecked = true;
hiori.flowDribbleCount = 0;
// Show Flow text above Hiori
if (!hiori.flowText) {
hiori.flowText = hiori.addChild(new Text2('FLOW', {
size: 48,
fill: 0x00FFFF
}));
hiori.flowText.anchor.set(0.5, 0.5);
hiori.flowText.x = 0;
hiori.flowText.y = -100;
hiori.flowText.alpha = 1;
if (hiori.flowText) {
// Defensive: only tween if flowText exists
tween(hiori.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
if (hiori.flowText) {
// Defensive: only tween if flowText still exists
tween(hiori.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
}
});
}
}
}
}
} else {
// Only Bachira or only Hiori present
shouldFlow = true;
}
if (shouldFlow) {
// Only set flowTriggerChecked, but do NOT activate Monster Trans/Flow yet
self.flowTriggerChecked = true;
// self.flowActive and self.monsterTransActive will be set when using Roulette or Monster Dribbling
}
}
// Remove Flow text and effects if not in Flow
if (!self.flowActive && self.flowText) {
self.flowText.destroy();
self.flowText = null;
}
if (!self.flowActive && self.monsterTransVisual) {
self.monsterTransVisual.destroy();
self.monsterTransVisual = null;
self.monsterTransActive = false;
// Reset player appearance
tween(playerGraphics, {
tint: colorOverride || 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
}
if (!self.flowActive) {
self.monsterTransActive = false;
}
// Update cooldowns
if (self.rouletteCooldown > 0) {
self.rouletteCooldown -= 16;
if (self.rouletteCooldown < 0) self.rouletteCooldown = 0;
}
if (self.monsterDribblingCooldown > 0) {
// In Flow mode, no cooldowns
if (!self.flowActive) {
self.monsterDribblingCooldown -= 16;
if (self.monsterDribblingCooldown < 0) self.monsterDribblingCooldown = 0;
} else {
self.monsterDribblingCooldown = 0; // No cooldown in Flow
}
}
// Handle Roulette stun end
if (self.rouletteStunEnd > 0 && Date.now() > self.rouletteStunEnd) {
// Restore stunned opponents from Roulette
for (var i = 0; i < self.rouletteStunnedOpponents.length; i++) {
var opponent = self.rouletteStunnedOpponents[i];
opponent.stunned = false;
opponent.originalSpeed = opponent.originalSpeed || opponent.speed;
opponent.speed = opponent.originalSpeed;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.rouletteStunnedOpponents = [];
self.rouletteStunEnd = 0;
}
// Handle Monster Dribbling stun end
if (self.monsterDribblingStunEnd > 0 && Date.now() > self.monsterDribblingStunEnd) {
// Restore stunned opponents from Monster Dribbling
for (var i = 0; i < self.monsterDribblingStunnedOpponents.length; i++) {
var opponent = self.monsterDribblingStunnedOpponents[i];
opponent.stunned = false;
opponent.originalSpeed = opponent.originalSpeed || opponent.speed;
opponent.speed = opponent.originalSpeed;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.monsterDribblingStunnedOpponents = [];
self.monsterDribblingStunEnd = 0;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Roulette dribbling logic
if (self.rouletteActive) {
var elapsed = Date.now() - self.rouletteStartTime;
var t = Math.min(elapsed / self.rouletteDuration, 1);
// Create spinning motion around origin point
var angle = t * self.rouletteRotations * 2 * Math.PI;
self.x = self.rouletteOriginX + Math.cos(angle) * self.rouletteRadius;
self.y = self.rouletteOriginY + Math.sin(angle) * self.rouletteRadius;
// Ball follows during roulette
if (distance < 100) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
// End roulette and apply stun effect
if (t >= 1) {
self.rouletteActive = false;
self.applyRouletteStun();
}
}
// Monster Dribbling logic
if (self.monsterDribblingActive) {
var elapsed = Date.now() - self.monsterDribblingStartTime;
var t = Math.min(elapsed / self.monsterDribblingDuration, 1);
// Fast, erratic movement toward target
var chaosAmplitude = 40;
var chaosFrequency = 8;
var chaosOffsetX = Math.sin(t * Math.PI * chaosFrequency) * chaosAmplitude;
var chaosOffsetY = Math.cos(t * Math.PI * chaosFrequency * 1.3) * chaosAmplitude;
// Move toward target with chaos
var targetDx = self.monsterDribblingTargetX - self.monsterDribblingStartX;
var targetDy = self.monsterDribblingTargetY - self.monsterDribblingStartY;
self.x = self.monsterDribblingStartX + targetDx * t + chaosOffsetX;
self.y = self.monsterDribblingStartY + targetDy * t + chaosOffsetY;
// Ball follows during monster dribbling
if (distance < 120) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
// Stun all opponents passed by during Monster Dribbling (only in Flow/Monster Trans)
if (self.flowActive) {
for (var i = 0; i < opponents.length; i++) {
var opp = opponents[i];
var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y));
if (oppDist <= self.monsterDribblingStunRadius && !opp.stunned) {
opp.stunned = true;
opp.originalSpeed = opp.speed;
opp.speed = 0;
self.monsterDribblingStunnedOpponents.push(opp);
tween(opp, {
tint: 0xFF0000,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 250
});
}
}
if (self.monsterDribblingStunnedOpponents.length > 0) {
self.monsterDribblingStunEnd = Date.now() + 4000;
}
}
// End monster dribbling and apply stun effect
if (t >= 1) {
self.monsterDribblingActive = false;
self.applyMonsterDribblingStun();
}
}
// Rush to ball after Roulette throw
if (self.rushingToBall) {
var rushDx = self.rushTargetX - self.x;
var rushDy = self.rushTargetY - self.y;
var rushDist = Math.sqrt(rushDx * rushDx + rushDy * rushDy);
if (rushDist > 30) {
// Rush toward the thrown ball location
self.x += rushDx / rushDist * self.rushSpeed;
self.y += rushDy / rushDist * self.rushSpeed;
// Stun all opponents passed by during Monster Trans rush (only in Flow)
if (self.flowActive) {
for (var i = 0; i < opponents.length; i++) {
var opp = opponents[i];
var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y));
if (oppDist <= self.monsterDribblingStunRadius && !opp.stunned) {
opp.stunned = true;
opp.originalSpeed = opp.speed;
opp.speed = 0;
self.monsterDribblingStunnedOpponents.push(opp);
tween(opp, {
tint: 0xFF0000,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 250
});
}
}
if (self.monsterDribblingStunnedOpponents.length > 0) {
self.monsterDribblingStunEnd = Date.now() + 4000;
}
}
} else {
// Reached the target area, check if ball is nearby
var ballDist = Math.sqrt((ball.x - self.x) * (ball.x - self.x) + (ball.y - self.y) * (ball.y - self.y));
if (ballDist < 100) {
// Ball is close, prepare to shoot
self.readyToShoot = true;
self.rushingToBall = false;
// Control the ball briefly
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
// Immediately shoot toward opponent goal
var goalDx = opponentGoal.x - self.x;
var goalDy = opponentGoal.y - self.y;
var goalDist = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDist > 0) {
ball.velocityX = goalDx / goalDist * 18; // Powerful shot
ball.velocityY = goalDy / goalDist * 18;
ball.active = true;
// Visual effect for powerful shot
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFF4500
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: self.flowActive ? 1.2 : 1,
scaleY: self.flowActive ? 1.2 : 1,
tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF
}, {
duration: 300
});
}
});
}
// Reset after shot
self.readyToShoot = false;
} else {
// Ball not found at target, stop rushing
self.rushingToBall = false;
}
}
} else if (!self.rouletteActive && !self.monsterDribblingActive && distance < 400) {
// Normal ball pursuit
var chaseSpeed = distance < 150 ? self.speed * 1.3 : self.speed * 1.1;
self.x += dx / distance * chaseSpeed;
self.y += dy / distance * chaseSpeed;
// Start abilities when close to ball
if (distance < 80) {
// Roulette (normal cooldown applies)
if (!self.rouletteActive && self.rouletteCooldown <= 0) {
var shouldUseRoulette = Math.random() < 0.4; // 40% chance
if (shouldUseRoulette) {
self.rouletteActive = true;
self.rouletteStartTime = Date.now();
self.rouletteOriginX = self.x;
self.rouletteOriginY = self.y;
// If Flow was triggered for Bachira, activate Monster Trans/Flow now
if (self.flowTriggerChecked && !self.flowActive) {
self.flowActive = true;
self.monsterTransActive = true;
// Show Flow text above Bachira
if (!self.flowText) {
self.flowText = self.addChild(new Text2('MONSTER TRANS', {
size: 48,
fill: 0xFF0000
}));
self.flowText.anchor.set(0.5, 0.5);
self.flowText.x = 0;
self.flowText.y = -100;
self.flowText.alpha = 1;
tween(self.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
});
}
// Create Monster Trans visual effect
if (!self.monsterTransVisual) {
self.monsterTransVisual = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0xFF0000
}));
self.monsterTransVisual.alpha = 0.3;
}
// Transform player appearance
tween(playerGraphics, {
tint: 0xFF4500,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500
});
}
if (!self.flowActive) {
self.rouletteCooldown = self.rouletteCooldownTime;
}
// Visual effect for roulette start
tween(self, {
tint: 0x00FFFF
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF
}, {
duration: 100
});
}
});
}
}
// Monster Dribbling (no cooldown in Flow)
if (!self.monsterDribblingActive && (self.flowActive || self.monsterDribblingCooldown <= 0)) {
var shouldUseMonsterDribbling = Math.random() < 0.6; // 60% chance
if (shouldUseMonsterDribbling) {
self.monsterDribblingActive = true;
self.monsterDribblingStartTime = Date.now();
self.monsterDribblingStartX = self.x;
self.monsterDribblingStartY = self.y;
// Target toward opponent goal
self.monsterDribblingTargetX = opponentGoal.x + (Math.random() - 0.5) * 200;
self.monsterDribblingTargetY = opponentGoal.y + 300;
// If Flow was triggered for Bachira, activate Monster Trans/Flow now
if (self.flowTriggerChecked && !self.flowActive) {
self.flowActive = true;
self.monsterTransActive = true;
// Show Flow text above Bachira
if (!self.flowText) {
self.flowText = self.addChild(new Text2('MONSTER TRANS', {
size: 48,
fill: 0xFF0000
}));
self.flowText.anchor.set(0.5, 0.5);
self.flowText.x = 0;
self.flowText.y = -100;
self.flowText.alpha = 1;
tween(self.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
});
}
// Create Monster Trans visual effect
if (!self.monsterTransVisual) {
self.monsterTransVisual = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0xFF0000
}));
self.monsterTransVisual.alpha = 0.3;
}
// Transform player appearance
tween(playerGraphics, {
tint: 0xFF4500,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500
});
}
if (!self.flowActive) {
self.monsterDribblingCooldown = self.monsterDribblingCooldownTime;
}
// Visual effect for monster dribbling start
tween(self, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
tint: self.flowActive ? 0xFF4500 : colorOverride || 0xFFFFFF,
scaleX: self.flowActive ? 1.2 : 1,
scaleY: self.flowActive ? 1.2 : 1
}, {
duration: 200
});
}
});
}
}
}
} else if (!self.rouletteActive && !self.monsterDribblingActive && distance > 500) {
// Return to home position when not actively playing
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.4;
self.y += homeDy / homeDistance * self.speed * 0.4;
}
}
};
self.applyRouletteStun = function () {
var opponentsInRange = [];
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (opponentDist <= self.rouletteStunRadius) {
opponentsInRange.push(opponent);
}
}
// Stun opponents in range
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
opponent.stunned = true;
opponent.originalSpeed = opponent.speed;
opponent.speed = 0; // Completely stop them
self.rouletteStunnedOpponents.push(opponent);
// Visual effect for stunned opponent
tween(opponent, {
tint: 0x00FFFF,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
}
if (opponentsInRange.length > 0) {
self.rouletteStunEnd = Date.now() + 3000; // 3 seconds stun
}
// After Roulette, throw ball to edge and rush to it
self.throwBallToEdgeAndRush();
};
self.applyMonsterDribblingStun = function () {
var opponentsInRange = [];
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (opponentDist <= self.monsterDribblingStunRadius) {
opponentsInRange.push(opponent);
}
}
// Stun opponents in range
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
opponent.stunned = true;
opponent.originalSpeed = opponent.speed;
opponent.speed = 0; // Completely stop them
self.monsterDribblingStunnedOpponents.push(opponent);
// Visual effect for stunned opponent
tween(opponent, {
tint: 0xFF0000,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 250
});
}
if (opponentsInRange.length > 0) {
self.monsterDribblingStunEnd = Date.now() + 4000; // 4 seconds stun
}
};
self.throwBallToEdgeAndRush = function () {
// Determine which edge to throw to (left or right side of field)
var throwToLeft = self.x < 1024; // If Bachira is on left side, throw left; otherwise right
var edgeX = throwToLeft ? 200 : 1848; // Near left or right edge
var edgeY = self.y + (Math.random() - 0.5) * 200; // Slightly randomize Y position
// Ensure edge Y is within field bounds
if (edgeY < 300) edgeY = 300;
if (edgeY > 2400) edgeY = 2400;
// Throw ball to edge with moderate power
var throwDx = edgeX - ball.x;
var throwDy = edgeY - ball.y;
var throwDist = Math.sqrt(throwDx * throwDx + throwDy * throwDy);
if (throwDist > 0) {
ball.velocityX = throwDx / throwDist * 10;
ball.velocityY = throwDy / throwDist * 10;
ball.active = true;
}
// Set rush target and start rushing
self.rushTargetX = edgeX;
self.rushTargetY = edgeY;
self.rushingToBall = true;
self.readyToShoot = false;
// Visual effect for ball throw
tween(ball, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00FFFF
}, {
duration: 200,
onFinish: function onFinish() {
tween(ball, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
};
return self;
});
var MidfieldPlayer = Container.expand(function () {
var self = Container.call(this);
var opponentGraphics = self.attachAsset('opponent', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.role = 'midfield';
self.homeX = 1024;
self.homeY = 1000;
self.hasBall = false;
// Dribbling state
self.isDribbling = false;
self.dribbleStartTime = 0;
self.dribbleDuration = 1000;
self.dribbleRadius = 120;
self.dribbleStartAngle = 0;
self.dribbleEndAngle = 0;
self.dribbleOriginX = 0;
self.dribbleOriginY = 0;
self.hasDribbled = false;
self.update = function () {
// Steal ball if player is inside opponent
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist < 80 && player.hasBall) {
// Steal the ball
player.hasBall = false;
ball.active = true;
// Ball moves away from player, toward midfield
var stealDx = self.x - player.x;
var stealDy = self.y - player.y;
var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy);
if (stealDist > 0) {
ball.velocityX = stealDx / stealDist * 10;
ball.velocityY = stealDy / stealDist * 10;
}
// Prevent immediate re-attachment
ballDetachCooldown = ballDetachCooldownTime;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Midfield: get ball and pass to forward
if (distance < 350) {
// Move toward ball with increased speed when chasing
var chaseSpeed = distance < 150 ? self.speed * 1.5 : self.speed * 1.2;
self.x += dx / distance * chaseSpeed;
self.y += dy / distance * chaseSpeed;
// If close to ball, run away from player and pass to forward
if (distance < 80) {
// Move away from player for a moment
var awayDx = self.x - player.x;
var awayDy = self.y - player.y;
var awayDist = Math.sqrt(awayDx * awayDx + awayDy * awayDy);
if (awayDist > 0) {
tween(self, {
x: self.x + awayDx / awayDist * 120,
y: self.y + awayDy / awayDist * 120
}, {
duration: 200,
easing: tween.cubicOut
});
}
// Find nearest forward player
var nearestForward = null;
var nearestDistance = Infinity;
for (var i = 0; i < opponents.length; i++) {
if (opponents[i].role === 'forward') {
var forwardDx = opponents[i].x - self.x;
var forwardDy = opponents[i].y - self.y;
var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy);
if (forwardDistance < nearestDistance) {
nearestDistance = forwardDistance;
nearestForward = opponents[i];
}
}
}
// Pass to forward if found
if (nearestForward) {
// Perfect pass: ball goes directly to forward with good speed
var passDx = nearestForward.x - ball.x;
var passDy = nearestForward.y - ball.y;
var passDistance = Math.sqrt(passDx * passDx + passDy * passDy);
if (passDistance > 0) {
ball.velocityX = passDx / passDistance * 10;
ball.velocityY = passDy / passDistance * 10;
}
} else {
// No forward to pass to: perform dribbling and curved shot
if (!self.isDribbling) {
// Start dribbling: move in a half-circle around the player
self.isDribbling = true;
self.dribbleStartTime = Date.now();
self.dribbleDuration = 1000; // 1 second
self.dribbleRadius = 120;
// Calculate angle from midfield to player
var angleToPlayer = Math.atan2(player.y - self.y, player.x - self.x);
self.dribbleStartAngle = angleToPlayer - Math.PI / 2;
self.dribbleEndAngle = angleToPlayer + Math.PI / 2;
self.dribbleOriginX = self.x;
self.dribbleOriginY = self.y;
self.hasDribbled = false;
}
if (self.isDribbling) {
var elapsed = Date.now() - self.dribbleStartTime;
var t = Math.min(elapsed / self.dribbleDuration, 1);
// Interpolate angle for half-circle
var angle = self.dribbleStartAngle + (self.dribbleEndAngle - self.dribbleStartAngle) * t;
self.x = self.dribbleOriginX + Math.cos(angle) * self.dribbleRadius;
self.y = self.dribbleOriginY + Math.sin(angle) * self.dribbleRadius;
// Ball follows midfield during dribble
if (distance < 80) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
// When dribble completes, shoot with curve
if (t >= 1 && !self.hasDribbled) {
self.hasDribbled = true;
self.isDribbling = false;
// Falsolu şut: apply curve by adding to X velocity
var goalDx = opponentGoal.x - ball.x;
var goalDy = opponentGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
// Add curve: right-footed, so curve to the right (positive X)
var curveAmount = 7;
ball.velocityX = goalDx / goalDistance * 12 + curveAmount;
ball.velocityY = goalDy / goalDistance * 12;
ball.active = true;
}
}
}
}
}
} else {
// Return to midfield position
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.3;
self.y += homeDy / homeDistance * self.speed * 0.3;
}
}
};
return self;
});
var NagiPlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && playerGraphics) {
playerGraphics.tint = colorOverride;
}
self.speed = 4;
self.role = 'nagi';
self.homeX = 1400;
self.homeY = 2000;
self.hasBall = false;
// --- FLOW SYSTEM for Nagi ---
self.flowActive = false;
self.flowTriggerChecked = false;
self.flowVolleyCount = 0;
self.flowVolleyMax = 5; // 5 Revolver Fake Volleys in Flow
self.flowText = null;
// Fake Volley variables
self.fakeVolleyState = 0; // 0: ready, 1: fake shot done, 2: real shot done
self.fakeVolleyTarget = null;
self.fakeVolleyStunRadius = 120;
self.fakeVolleyStunDuration = 2000; // 2 seconds
self.fakeVolleyStunnedOpponents = [];
self.fakeVolleyStunEnd = 0;
// Fake Attack variables
self.fakeAttackCooldown = 0;
self.fakeAttackCooldownTime = 5000; // Base 5 seconds, will be randomized
self.fakeAttackStunRadius = 140;
self.fakeAttackStunDuration = 0; // Will be randomized 5-10 seconds
self.fakeAttackStunnedOpponents = [];
self.fakeAttackStunEnd = 0;
self.fakeAttackChance = 0.3; // 30% chance for shots to be fake
self.fakeVolleyVisual = null;
// Ball pull variables
self.ballPullRange = 150;
self.ballPullActive = false;
self.ballPullVisual = null;
self.ballPullCooldown = 0;
self.ballPullCooldownTime = 5000; // 5 seconds
// Ball control text
self.ballControlText = null;
self.update = function () {
// --- FLOW TRIGGER: If 2 goals scored by Nagi's team, activate Flow for random teammate (including Nagi) ---
if (!self.flowActive && !self.flowTriggerChecked && typeof playerScore !== "undefined" && playerScore >= 2) {
self.flowTriggerChecked = true;
// Pick a random teammate (Nagi, Hiori, Chigiri, Reo) to enter Flow
var teammates = [];
if (typeof nagi !== "undefined") teammates.push(nagi);
if (typeof hiori !== "undefined") teammates.push(hiori);
if (typeof chigiri !== "undefined") teammates.push(chigiri);
if (typeof reo !== "undefined") teammates.push(reo);
if (teammates.length > 0) {
var idx = Math.floor(Math.random() * teammates.length);
var chosen = teammates[idx];
if (chosen && typeof chosen.flowActive !== "undefined") {
chosen.flowActive = true;
chosen.flowVolleyCount = 0;
// Show Flow text above chosen
if (!chosen.flowText) {
chosen.flowText = chosen.addChild(new Text2('FLOW', {
size: 48,
fill: 0x00FFFF
}));
chosen.flowText.anchor.set(0.5, 0.5);
chosen.flowText.x = 0;
chosen.flowText.y = -100;
chosen.flowText.alpha = 1;
tween(chosen.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(chosen.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
});
}
}
}
}
// Remove Flow text if not in Flow
if (!self.flowActive && self.flowText) {
self.flowText.destroy();
self.flowText = null;
}
// Update cooldowns
if (self.fakeVolleyStunEnd > 0 && Date.now() > self.fakeVolleyStunEnd) {
// Restore stunned opponents
for (var i = 0; i < self.fakeVolleyStunnedOpponents.length; i++) {
var opponent = self.fakeVolleyStunnedOpponents[i];
opponent.stunned = false;
opponent.originalSpeed = opponent.originalSpeed || opponent.speed;
opponent.speed = opponent.originalSpeed;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.fakeVolleyStunnedOpponents = [];
self.fakeVolleyStunEnd = 0;
}
if (self.ballPullCooldown > 0) {
self.ballPullCooldown -= 16;
if (self.ballPullCooldown < 0) self.ballPullCooldown = 0;
}
// Update Fake Attack cooldown
if (self.fakeAttackCooldown > 0) {
self.fakeAttackCooldown -= 16;
if (self.fakeAttackCooldown < 0) self.fakeAttackCooldown = 0;
}
// Handle Fake Attack stun end
if (self.fakeAttackStunEnd > 0 && Date.now() > self.fakeAttackStunEnd) {
// Restore stunned opponents from Fake Attack
for (var i = 0; i < self.fakeAttackStunnedOpponents.length; i++) {
var opponent = self.fakeAttackStunnedOpponents[i];
opponent.stunned = false;
opponent.originalSpeed = opponent.originalSpeed || opponent.speed;
opponent.speed = opponent.originalSpeed;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.fakeAttackStunnedOpponents = [];
self.fakeAttackStunEnd = 0;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Nagi Ball Control: Steal from opponent if close and not immune
if (!self.nagiStealImmunity) self.nagiStealImmunity = 0;
var canSteal = true;
if (self.nagiStealImmunity > Date.now()) canSteal = false;
// Check for opponent with ball in Ball Control range
var stoleFromOpponent = false;
if (canSteal) {
for (var i = 0; i < opponents.length; i++) {
var opp = opponents[i];
var oppDist = Math.sqrt((opp.x - self.x) * (opp.x - self.x) + (opp.y - self.y) * (opp.y - self.y));
if (opp.hasBall && oppDist < 120) {
// Steal the ball from opponent
opp.hasBall = false;
ball.active = true;
// Move ball to Nagi's feet
ball.velocityX = 0;
ball.velocityY = 0;
tween(ball, {
x: self.x,
y: self.y + 40
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
ball.active = false;
self.hasBall = true;
}
});
// Show "Ball Control" text
if (!self.ballControlText) {
self.ballControlText = self.addChild(new Text2('Ball Control', {
size: 40,
fill: 0xFFFFFF
}));
self.ballControlText.anchor.set(0.5, 0.5);
self.ballControlText.x = 0;
self.ballControlText.y = -80;
tween(self.ballControlText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(self.ballControlText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
onFinish: function onFinish() {
if (self.ballControlText) {
self.ballControlText.destroy();
self.ballControlText = null;
}
}
});
}, 2000);
}
});
}
// Set 3s immunity for Nagi after stealing
self.nagiStealImmunity = Date.now() + 3000;
stoleFromOpponent = true;
break;
}
}
}
// If not stealing from opponent, allow normal ball control from loose ball
if (!stoleFromOpponent && distance < 120 && ball.active && !self.hasBall && canSteal) {
ball.velocityX = 0;
ball.velocityY = 0;
tween(ball, {
x: self.x,
y: self.y + 40
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
ball.active = false;
self.hasBall = true;
}
});
if (!self.ballControlText) {
self.ballControlText = self.addChild(new Text2('Ball Control', {
size: 40,
fill: 0xFFFFFF
}));
self.ballControlText.anchor.set(0.5, 0.5);
self.ballControlText.x = 0;
self.ballControlText.y = -80;
tween(self.ballControlText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(self.ballControlText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
onFinish: function onFinish() {
if (self.ballControlText) {
self.ballControlText.destroy();
self.ballControlText = null;
}
}
});
}, 2000);
}
});
}
}
// --- FLOW: 5 Revolver Fake Volley logic ---
if (self.flowActive) {
// If close to ball, perform up to 5 chained fake volleys (fake+real) in a row
if (distance < 80 && self.flowVolleyCount < self.flowVolleyMax && self.fakeVolleyState === 0) {
self.fakeVolleyState = 1;
self.flowVolleyCount++;
// Stun nearby opponents
var opponentsInRange = [];
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (opponentDist <= self.fakeVolleyStunRadius) {
opponentsInRange.push(opponent);
}
}
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
opponent.stunned = true;
opponent.originalSpeed = opponent.speed;
opponent.speed = 0;
self.fakeVolleyStunnedOpponents.push(opponent);
tween(opponent, {
tint: 0xFFFF00,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
}
self.fakeVolleyStunEnd = Date.now() + self.fakeVolleyStunDuration;
// Fake shot - ball barely moves
ball.velocityX = (Math.random() - 0.5) * 2;
ball.velocityY = (Math.random() - 0.5) * 2;
ball.active = true;
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else if (self.flowActive && self.fakeVolleyState === 1) {
// After fake, do real volley
self.fakeVolleyState = 2;
var goalDx = opponentGoal.x - ball.x;
var goalDy = opponentGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 16;
ball.velocityY = goalDy / goalDistance * 16;
ball.active = true;
}
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFF0000
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
// Reset fake volley state after a short delay, and chain next if not reached max
LK.setTimeout(function () {
self.fakeVolleyState = 0;
// If not reached 5, chain next volley automatically (simulate rapid-fire)
if (self.flowActive && self.flowVolleyCount < self.flowVolleyMax) {
// Move Nagi slightly forward for next volley
self.x += (opponentGoal.x - self.x) * 0.08;
self.y += (opponentGoal.y - self.y) * 0.08;
} else if (self.flowActive && self.flowVolleyCount >= self.flowVolleyMax) {
// End Flow after 5 volleys
self.flowActive = false;
self.flowVolleyCount = 0;
if (self.flowText) {
self.flowText.destroy();
self.flowText = null;
}
}
}, 500);
}
// Prevent normal volley logic during Flow
return;
}
// After stealing, for 3s, Nagi cannot be stolen from and focuses on goal
if (self.hasBall && self.nagiStealImmunity > Date.now()) {
// Nagi focuses on goal: move toward opponent goal and shoot if close
var goalDx = opponentGoal.x - self.x;
var goalDy = opponentGoal.y - self.y;
var goalDist = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDist > 0) {
self.x += goalDx / goalDist * self.speed * 1.2;
self.y += goalDy / goalDist * self.speed * 1.2;
// If close to goal, shoot
if (goalDist < 200) {
ball.active = true;
ball.velocityX = goalDx / goalDist * 16;
ball.velocityY = goalDy / goalDist * 16;
self.hasBall = false;
// Remove immunity after shot
self.nagiStealImmunity = 0;
}
}
} else {
// If Nagi doesn't have ball, always move to a good attacking position near the opponent goal
var attackTargetX = opponentGoal.x + 200 * Math.sin(Date.now() / 1200); // oscillate a bit for realism
var attackTargetY = opponentGoal.y + 250;
var attackDx = attackTargetX - self.x;
var attackDy = attackTargetY - self.y;
var attackDist = Math.sqrt(attackDx * attackDx + attackDy * attackDy);
if (attackDist > 10) {
self.x += attackDx / attackDist * self.speed * 0.8;
self.y += attackDy / attackDist * self.speed * 0.8;
}
}
// Ball pull ability - show range when ball is within pull range
if (distance <= self.ballPullRange && !self.ballPullActive && self.ballPullCooldown <= 0) {
if (!self.ballPullVisual) {
// Create ball pull range visual
self.ballPullVisual = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3,
tint: 0x00FF00
}));
self.ballPullVisual.alpha = 0.2;
}
// Pull ball towards self
if (distance > 30) {
self.ballPullActive = true;
var pullForce = 8;
ball.velocityX = -dx / distance * pullForce;
ball.velocityY = -dy / distance * pullForce;
ball.active = true;
self.ballPullCooldown = self.ballPullCooldownTime;
// Visual effect for ball pull
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
}
} else {
// Remove ball pull visual if ball is out of range
if (self.ballPullVisual) {
self.ballPullVisual.destroy();
self.ballPullVisual = null;
}
self.ballPullActive = false;
}
// Normal ball pursuit
if (distance < 400) {
var chaseSpeed = distance < 150 ? self.speed * 1.2 : self.speed * 1.0;
self.x += dx / distance * chaseSpeed;
self.y += dy / distance * chaseSpeed;
// Volley ability when close to ball with Fake Attack chance
if (distance < 80) {
// Check if this should be a Fake Attack
var shouldFakeAttack = self.fakeAttackCooldown <= 0 && Math.random() < self.fakeAttackChance;
if (shouldFakeAttack) {
// FAKE ATTACK - stun opponents and barely move ball
// Find opponents within Fake Attack stun radius
var opponentsInRange = [];
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (opponentDist <= self.fakeAttackStunRadius) {
opponentsInRange.push(opponent);
}
}
// Stun opponents in range
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
opponent.stunned = true;
opponent.originalSpeed = opponent.speed;
opponent.speed = 0; // Completely stop them
self.fakeAttackStunnedOpponents.push(opponent);
// Visual effect for stunned opponent
tween(opponent, {
tint: 0xFF6600,
// Orange tint for Fake Attack
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 250
});
}
// Randomize stun duration (5-10 seconds)
self.fakeAttackStunDuration = 5000 + Math.random() * 5000;
self.fakeAttackStunEnd = Date.now() + self.fakeAttackStunDuration;
// Randomize next cooldown (5-10 seconds)
self.fakeAttackCooldownTime = 5000 + Math.random() * 5000;
self.fakeAttackCooldown = self.fakeAttackCooldownTime;
// Fake attack - ball barely moves
ball.velocityX = (Math.random() - 0.5) * 3;
ball.velocityY = (Math.random() - 0.5) * 3;
ball.active = true;
// Visual effect for Fake Attack
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFF6600 // Orange for Fake Attack
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
} else if (self.fakeVolleyState === 0) {
// First shot - FAKE VOLLEY
self.fakeVolleyState = 1;
// Find opponents within stun radius
var opponentsInRange = [];
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var opponentDist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (opponentDist <= self.fakeVolleyStunRadius) {
opponentsInRange.push(opponent);
}
}
// Stun opponents in range
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
opponent.stunned = true;
opponent.originalSpeed = opponent.speed;
opponent.speed = 0; // Completely stop them
self.fakeVolleyStunnedOpponents.push(opponent);
// Visual effect for stunned opponent
tween(opponent, {
tint: 0xFFFF00,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
}
self.fakeVolleyStunEnd = Date.now() + self.fakeVolleyStunDuration;
// Fake shot - ball barely moves to show it's fake
ball.velocityX = (Math.random() - 0.5) * 2;
ball.velocityY = (Math.random() - 0.5) * 2;
ball.active = true;
// Visual effect for fake shot
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else if (self.fakeVolleyState === 1) {
// Second shot - REAL VOLLEY
self.fakeVolleyState = 2;
// Real powerful shot toward opponent goal
var goalDx = opponentGoal.x - ball.x;
var goalDy = opponentGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 16;
ball.velocityY = goalDy / goalDistance * 16;
ball.active = true;
}
// Visual effect for real shot
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFF0000
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
// Reset fake volley state after a delay
LK.setTimeout(function () {
self.fakeVolleyState = 0;
}, 3000);
}
}
} else {
// Return to home position when not actively playing
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.4;
self.y += homeDy / homeDistance * self.speed * 0.4;
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.hasBall = false;
return self;
});
var ReoPlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && playerGraphics) {
playerGraphics.tint = colorOverride;
}
self.speed = 4;
self.role = 'reo';
self.homeX = 600;
self.homeY = 1800;
self.hasBall = false;
// Mode switching variables
self.isDefensiveMode = true;
self.defensiveColor = 0x000000; // Black
self.attackingColor = 0xffa500; // Orange
self.modeSwitch = 0;
self.modeSwitchInterval = 5000; // 5 seconds
// Chameleon ability variables
self.chameleonCooldown = 0;
self.chameleonInterval = 30000; // 30 seconds
self.copiedStats = null;
self.originalSpeed = 4;
self.originalDribbling = 1;
self.originalShot = 1;
// Copied ability variables
self.copiedWinterZone = false;
self.copiedWinterZoneRadius = 200;
self.copiedWinterZoneCooldown = 0;
self.copiedWinterZoneActive = false;
self.copiedWinterZoneDuration = 0;
self.copiedWinterZoneMaxDuration = 8000;
self.copiedSlowedOpponents = [];
self.copiedWinterZoneVisual = null;
self.copiedSpeedBurst = false;
self.copiedSpeedBurstCooldown = 0;
self.copiedSpeedBurstActive = false;
self.copiedBurstSpeed = 10;
self.copiedNormalSpeed = 4;
self.update = function () {
// Update mode switching
self.modeSwitch += 16;
if (self.modeSwitch >= self.modeSwitchInterval) {
self.modeSwitch = 0;
self.isDefensiveMode = !self.isDefensiveMode;
// Change color based on mode
if (self.isDefensiveMode) {
tween(playerGraphics, {
tint: self.defensiveColor
}, {
duration: 300
});
} else {
tween(playerGraphics, {
tint: self.attackingColor
}, {
duration: 300
});
}
}
// Update Chameleon cooldown
self.chameleonCooldown += 16;
if (self.chameleonCooldown >= self.chameleonInterval) {
self.chameleonCooldown = 0;
self.activateChameleon();
}
// Update copied abilities
if (self.copiedWinterZone) {
// Update Winter Zone cooldown
if (self.copiedWinterZoneCooldown > 0) {
self.copiedWinterZoneCooldown -= 16;
if (self.copiedWinterZoneCooldown < 0) self.copiedWinterZoneCooldown = 0;
}
// Winter Zone duration and effect
if (self.copiedWinterZoneActive) {
self.copiedWinterZoneDuration -= 16;
if (self.copiedWinterZoneDuration <= 0) {
self.copiedWinterZoneActive = false;
// Remove Winter Zone visual
if (self.copiedWinterZoneVisual) {
self.copiedWinterZoneVisual.destroy();
self.copiedWinterZoneVisual = null;
}
// Restore opponent speeds
for (var i = 0; i < self.copiedSlowedOpponents.length; i++) {
var opponent = self.copiedSlowedOpponents[i];
opponent.speed = opponent.originalSpeed || opponent.speed * 2;
}
self.copiedSlowedOpponents = [];
} else {
// Apply Winter Zone effect to nearby opponents
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (dist <= self.copiedWinterZoneRadius) {
if (self.copiedSlowedOpponents.indexOf(opponent) === -1) {
opponent.originalSpeed = opponent.speed;
opponent.speed = opponent.speed * 0.3; // Slow to 30% speed
self.copiedSlowedOpponents.push(opponent);
}
}
}
}
}
// Activate Winter Zone when opponents get close
if (!self.copiedWinterZoneActive && self.copiedWinterZoneCooldown <= 0) {
var nearbyOpponents = 0;
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var dist = Math.sqrt((opponent.x - self.x) * (opponent.x - self.x) + (opponent.y - self.y) * (opponent.y - self.y));
if (dist <= self.copiedWinterZoneRadius + 50) {
nearbyOpponents++;
}
}
if (nearbyOpponents >= 2) {
// Activate Winter Zone
self.copiedWinterZoneActive = true;
self.copiedWinterZoneDuration = self.copiedWinterZoneMaxDuration;
self.copiedWinterZoneCooldown = 12000; // 12 seconds cooldown
// Create Winter Zone visual area
self.copiedWinterZoneVisual = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 4,
tint: 0x87CEEB
}));
self.copiedWinterZoneVisual.alpha = 0.3;
// Visual effect
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0x87CEEB
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: self.isDefensiveMode ? self.defensiveColor : self.attackingColor
}, {
duration: 200
});
}
});
}
}
}
if (self.copiedSpeedBurst) {
// Update Speed Burst cooldown
if (self.copiedSpeedBurstCooldown > 0) {
self.copiedSpeedBurstCooldown -= 16;
if (self.copiedSpeedBurstCooldown < 0) self.copiedSpeedBurstCooldown = 0;
}
// Speed burst when close to ball
if (!self.copiedSpeedBurstActive && self.copiedSpeedBurstCooldown <= 0 && distance < 100) {
self.copiedSpeedBurstActive = true;
self.copiedSpeedBurstCooldown = 8000; // 8 seconds cooldown
self.speed = self.copiedBurstSpeed;
// Visual effect for speed burst
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
// Speed burst lasts for 2 seconds
LK.setTimeout(function () {
self.copiedSpeedBurstActive = false;
self.speed = self.copiedNormalSpeed;
}, 2000);
}
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Mode-based behavior
if (self.isDefensiveMode) {
// Defensive mode: more active interception and ball pursuit
if (distance < 500) {
var defensiveSpeed = self.speed * 1.1;
self.x += dx / distance * defensiveSpeed;
self.y += dy / distance * defensiveSpeed;
// Defensive tackle
if (distance < 80) {
var clearX = ball.x < 1024 ? ball.x - 300 : ball.x + 300;
var clearY = ball.y - 200;
var clearDx = clearX - ball.x;
var clearDy = clearY - ball.y;
var clearDist = Math.sqrt(clearDx * clearDx + clearDy * clearDy);
if (clearDist > 0) {
ball.velocityX = clearDx / clearDist * 8;
ball.velocityY = clearDy / clearDist * 8;
}
}
} else {
// Return to defensive position, but move more actively
var homeDx = self.homeX - self.x;
var homeDy = self.homeY - self.y;
var homeDistance = Math.sqrt(homeDx * homeDx + homeDy * homeDy);
if (homeDistance > 50) {
self.x += homeDx / homeDistance * self.speed * 0.7;
self.y += homeDy / homeDistance * self.speed * 0.7;
}
}
} else {
// Attacking mode: chase ball very aggressively and support attack
if (distance < 700) {
var attackSpeed = self.speed * 1.7;
self.x += dx / distance * attackSpeed;
self.y += dy / distance * attackSpeed;
// Attack shot
if (distance < 80) {
var goalDx = opponentGoal.x - ball.x;
var goalDy = opponentGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 14;
ball.velocityY = goalDy / goalDistance * 14;
}
}
} else {
// If not near ball, move up to a supporting attacking position
var supportX = opponentGoal.x - 200;
var supportY = opponentGoal.y + 400;
var supportDx = supportX - self.x;
var supportDy = supportY - self.y;
var supportDist = Math.sqrt(supportDx * supportDx + supportDy * supportDy);
if (supportDist > 30) {
self.x += supportDx / supportDist * self.speed * 0.7;
self.y += supportDy / supportDist * self.speed * 0.7;
}
}
}
};
self.activateChameleon = function () {
// Find random NPC to copy stats from (including player team members)
var availableNPCs = [];
for (var i = 0; i < opponents.length; i++) {
availableNPCs.push(opponents[i]);
}
// Also add player team members with special abilities
if (typeof hiori !== 'undefined') availableNPCs.push(hiori);
if (typeof chigiri !== 'undefined') availableNPCs.push(chigiri);
if (typeof nagi !== 'undefined') availableNPCs.push(nagi);
if (availableNPCs.length > 0) {
var randomNPC = availableNPCs[Math.floor(Math.random() * availableNPCs.length)];
// Copy stats from random NPC
self.copiedStats = {
speed: randomNPC.speed || self.originalSpeed,
dribbling: randomNPC.dribbleSpeed || self.originalDribbling,
shot: randomNPC.burstSpeed || self.originalShot,
role: randomNPC.role || 'reo'
};
// Apply copied stats
self.speed = self.copiedStats.speed;
// Copy special abilities based on role
if (self.copiedStats.role === 'hiori') {
// Copy Winter Zone ability
self.copiedWinterZone = true;
self.copiedWinterZoneRadius = 200;
self.copiedWinterZoneCooldown = 0;
self.copiedWinterZoneActive = false;
self.copiedWinterZoneDuration = 0;
self.copiedWinterZoneMaxDuration = 8000; // Slightly shorter than original
self.copiedSlowedOpponents = [];
self.copiedWinterZoneVisual = null;
} else if (self.copiedStats.role === 'chigiri') {
// Copy Speed Burst ability
self.copiedSpeedBurst = true;
self.copiedSpeedBurstCooldown = 0;
self.copiedSpeedBurstActive = false;
self.copiedBurstSpeed = 10;
self.copiedNormalSpeed = self.speed;
} else {
// Reset copied abilities
self.copiedWinterZone = false;
self.copiedSpeedBurst = false;
}
// Visual effect for Chameleon activation
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xff00ff
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: self.isDefensiveMode ? self.defensiveColor : self.attackingColor
}, {
duration: 200
});
}
});
}
};
return self;
});
var SaePlayer = Container.expand(function (colorOverride) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('opponent', {
anchorX: 0.5,
anchorY: 0.5
});
if (typeof colorOverride === "number" && playerGraphics) {
playerGraphics.tint = colorOverride;
}
self.speed = 4.5;
self.role = 'sae';
self.homeX = 1024;
self.homeY = 800;
self.hasBall = false;
// Perfect Pass variables
self.perfectPassRange = 800;
self.perfectPassCooldown = 0;
self.perfectPassCooldownTime = 8000; // 8 seconds
// Half-circle dribbling variables
self.isDribbling = false;
self.dribbleStartTime = 0;
self.dribbleDuration = 1500; // 1.5 seconds
self.dribbleRadius = 100;
self.dribbleStartAngle = 0;
self.dribbleEndAngle = 0;
self.dribbleOriginX = 0;
self.dribbleOriginY = 0;
self.dribbleStunRadius = 150;
self.dribbleStunDuration = 2000; // 2 seconds
self.dribbleStunnedOpponents = [];
self.dribbleStunEnd = 0;
// Zigzag dribbling variables
self.isZigzagDribbling = false;
self.zigzagStartTime = 0;
self.zigzagDuration = 2000; // 2 seconds
self.zigzagStartX = 0;
self.zigzagStartY = 0;
self.zigzagTargetX = 0;
self.zigzagTargetY = 0;
self.zigzagDevourRadius = 120;
// Zigzag chain variables
self.zigzagCount = 0;
self.zigzagPaused = false;
self.zigzagPauseStart = 0;
self.zigzagPauseDuration = 10000; // 10 seconds pause after 6 zigzags
// Chop dribbling variables
self.isChopDribbling = false;
self.chopStartTime = 0;
self.chopDuration = 1000; // 1 second
self.chopStartX = 0;
self.chopStartY = 0;
self.chopTargetX = 0;
self.chopTargetY = 0;
self.chopDevourRadius = 100;
// Devoured effect variables
self.devouredOpponents = [];
self.devouredEndTime = 0;
self.devouredDuration = 3000; // 3 seconds
// Dash ability variables
self.dashCooldown = 0;
self.dashCooldownTime = 10000; // 10 seconds
self.dashSpeed = 10;
self.dashDuration = 800; // 0.8 seconds
self.dashActive = false;
self.dashEndTime = 0;
// False shot variables
self.falseShotCooldown = 0;
self.falseShotCooldownTime = 12000; // 12 seconds
self.falseShotActive = false;
// FLOW SYSTEM for Sae
if (typeof self.flowActive === "undefined") {
self.flowActive = false;
self.flowTriggerChecked = false;
self.flowDribbleCount = 0;
self.flowDribbleMax = 999; // Unlimited while in Flow
self.flowText = null;
self.flowHilalDribbling = false;
self.flowHilalStartTime = 0;
self.flowHilalDuration = 1200; // ms per crescent
self.flowHilalOriginX = 0;
self.flowHilalOriginY = 0;
self.flowHilalRadius = 180;
self.flowHilalStartAngle = 0;
self.flowHilalEndAngle = 0;
self.flowHilalPhase = 0;
self.flowHilalCount = 0;
self.flowHilalMax = 4; // Number of crescents before shot
self.flowHilalShotDone = false;
}
// FLOW TRIGGER: If 2 goals scored by Sae, activate Flow (only once)
if (!self.flowActive && !self.flowTriggerChecked && typeof opponentScore !== "undefined" && opponentScore >= 2) {
self.flowActive = true;
self.flowDribbleCount = 0;
self.flowTriggerChecked = true;
self.flowHilalDribbling = false;
self.flowHilalCount = 0;
self.flowHilalShotDone = false;
// Show Flow text above Sae
if (!self.flowText) {
self.flowText = self.addChild(new Text2('FLOW', {
size: 48,
fill: 0xFF6600
}));
self.flowText.anchor.set(0.5, 0.5);
self.flowText.x = 0;
self.flowText.y = -100;
self.flowText.alpha = 1;
tween(self.flowText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.flowText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300
});
}
});
}
}
// Remove Flow text if not in Flow
if (!self.flowActive && self.flowText) {
self.flowText.destroy();
self.flowText = null;
}
self.update = function () {
// Update cooldowns
if (self.perfectPassCooldown > 0) {
self.perfectPassCooldown -= 16;
if (self.perfectPassCooldown < 0) self.perfectPassCooldown = 0;
}
if (self.dashCooldown > 0) {
self.dashCooldown -= 16;
if (self.dashCooldown < 0) self.dashCooldown = 0;
}
if (self.falseShotCooldown > 0) {
self.falseShotCooldown -= 16;
if (self.falseShotCooldown < 0) self.falseShotCooldown = 0;
}
// Handle dribble stun end
if (self.dribbleStunEnd > 0 && Date.now() > self.dribbleStunEnd) {
// Restore stunned opponents
for (var i = 0; i < self.dribbleStunnedOpponents.length; i++) {
var opponent = self.dribbleStunnedOpponents[i];
opponent.stunned = false;
opponent.originalSpeed = opponent.originalSpeed || opponent.speed;
opponent.speed = opponent.originalSpeed;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.dribbleStunnedOpponents = [];
self.dribbleStunEnd = 0;
}
// Handle dash duration
if (self.dashActive && Date.now() > self.dashEndTime) {
self.dashActive = false;
self.speed = 4.5; // Return to normal speed
}
// Steal ball if player is inside opponent
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist < 80 && player.hasBall) {
// Steal the ball
player.hasBall = false;
ball.active = true;
// Ball moves away from player, toward Sae
var stealDx = self.x - player.x;
var stealDy = self.y - player.y;
var stealDist = Math.sqrt(stealDx * stealDx + stealDy * stealDy);
if (stealDist > 0) {
ball.velocityX = stealDx / stealDist * 10;
ball.velocityY = stealDy / stealDist * 10;
}
// Prevent immediate re-attachment
ballDetachCooldown = ballDetachCooldownTime;
}
var dx = ball.x - self.x;
var dy = ball.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Handle devoured opponents restoration
if (self.devouredEndTime > 0 && Date.now() > self.devouredEndTime) {
// Restore devoured opponents
for (var i = 0; i < self.devouredOpponents.length; i++) {
var opponent = self.devouredOpponents[i];
opponent.speed = opponent.originalSpeed || opponent.speed * 2;
opponent.devoured = false;
tween(opponent, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
self.devouredOpponents = [];
self.devouredEndTime = 0;
}
// Half-circle dribbling logic
if (self.isDribbling) {
var elapsed = Date.now() - self.dribbleStartTime;
var t = Math.min(elapsed / self.dribbleDuration, 1);
// Interpolate angle for half-circle
var angle = self.dribbleStartAngle + (self.dribbleEndAngle - self.dribbleStartAngle) * t;
self.x = self.dribbleOriginX + Math.cos(angle) * self.dribbleRadius;
self.y = self.dribbleOriginY + Math.sin(angle) * self.dribbleRadius;
// Ball follows during dribble
if (distance < 80) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
// When dribble completes, apply Devoured effect
if (t >= 1) {
self.isDribbling = false;
self.applyDevouredEffect(self.dribbleStunRadius);
}
}
// Zigzag dribbling logic
if (self.isZigzagDribbling) {
var elapsed = Date.now() - self.zigzagStartTime;
var progress = Math.min(elapsed / self.zigzagDuration, 1);
// Create zigzag pattern
var zigzagAmplitude = 60;
var zigzagFrequency = 6;
var zigzagOffset = Math.sin(progress * Math.PI * zigzagFrequency) * zigzagAmplitude;
// Move toward target with zigzag motion
var targetDx = self.zigzagTargetX - self.zigzagStartX;
var targetDy = self.zigzagTargetY - self.zigzagStartY;
var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy);
if (targetDist > 0) {
// Calculate perpendicular vector for zigzag
var perpX = -targetDy / targetDist;
var perpY = targetDx / targetDist;
// Apply zigzag movement
self.x = self.zigzagStartX + targetDx * progress + perpX * zigzagOffset;
self.y = self.zigzagStartY + targetDy * progress + perpY * zigzagOffset;
// Ball follows during dribble
if (distance < 100) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
}
// End zigzag dribbling and apply Devoured effect
if (progress >= 1) {
self.isZigzagDribbling = false;
self.applyDevouredEffect(self.zigzagDevourRadius);
// Chain up to 6 zigzags, then pause
self.zigzagCount = (self.zigzagCount || 0) + 1;
if (self.zigzagCount < 6) {
// Start another zigzag immediately
self.isZigzagDribbling = true;
self.zigzagStartTime = Date.now();
self.zigzagStartX = self.x;
self.zigzagStartY = self.y;
// Dribble toward player goal with reduced distance
self.zigzagTargetX = self.x + (playerGoal.x - self.x) * 0.3 + (Math.random() - 0.5) * 80;
self.zigzagTargetY = self.y + (playerGoal.y - self.y) * 0.3 + 50;
} else {
// Pause after 6 zigzags
self.zigzagPaused = true;
self.zigzagPauseStart = Date.now();
self.zigzagCount = 0;
}
}
}
// Zigzag pause logic
if (self.zigzagPaused) {
var pauseElapsed = Date.now() - self.zigzagPauseStart;
if (pauseElapsed >= self.zigzagPauseDuration) {
self.zigzagPaused = false;
}
}
// Chop dribbling logic
if (self.isChopDribbling) {
var elapsed = Date.now() - self.chopStartTime;
var progress = Math.min(elapsed / self.chopDuration, 1);
// Quick chop movement - sharp direction change
var chopPhase = progress < 0.5 ? progress * 2 : 1 - (progress - 0.5) * 2;
var chopIntensity = Math.sin(chopPhase * Math.PI) * 80;
// Move with chop motion
var targetDx = self.chopTargetX - self.chopStartX;
var targetDy = self.chopTargetY - self.chopStartY;
var targetDist = Math.sqrt(targetDx * targetDx + targetDy * targetDy);
if (targetDist > 0) {
// Calculate perpendicular vector for chop
var perpX = -targetDy / targetDist;
var perpY = targetDx / targetDist;
// Apply chop movement
self.x = self.chopStartX + targetDx * progress + perpX * chopIntensity;
self.y = self.chopStartY + targetDy * progress + perpY * chopIntensity;
// Ball follows during dribble
if (distance < 100) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
}
// End chop dribbling and apply Devoured effect
if (progress >= 1) {
self.isChopDribbling = false;
self.applyDevouredEffect(self.chopDevourRadius);
}
}
// FLOW: If Sae is in Flow, perform continuous crescent (hilal) dribble and finish with a striker shot
if (self.flowActive) {
// Start Hilal dribble if not already started and not shot yet
if (!self.flowHilalDribbling && !self.flowHilalShotDone && distance < 120) {
self.flowHilalDribbling = true;
self.flowHilalStartTime = Date.now();
self.flowHilalOriginX = self.x;
self.flowHilalOriginY = self.y;
// Each crescent alternates direction for visual effect
var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x);
var dir = self.flowHilalCount % 2 === 0 ? 1 : -1;
self.flowHilalStartAngle = angleToGoal - dir * Math.PI / 2;
self.flowHilalEndAngle = angleToGoal + dir * Math.PI / 2;
self.flowHilalPhase = 0;
}
// Hilal dribble logic
if (self.flowHilalDribbling && !self.flowHilalShotDone) {
var elapsed = Date.now() - self.flowHilalStartTime;
var t = Math.min(elapsed / self.flowHilalDuration, 1);
var angle = self.flowHilalStartAngle + (self.flowHilalEndAngle - self.flowHilalStartAngle) * t;
self.x = self.flowHilalOriginX + Math.cos(angle) * self.flowHilalRadius;
self.y = self.flowHilalOriginY + Math.sin(angle) * self.flowHilalRadius;
// Ball follows during dribble
if (distance < 120) {
ball.x = self.x;
ball.y = self.y;
ball.velocityX = 0;
ball.velocityY = 0;
}
// When crescent completes, start next or shoot
if (t >= 1) {
self.flowHilalDribbling = false;
self.flowHilalCount++;
// Visual effect for each crescent
tween(self, {
scaleX: 1.15,
scaleY: 1.15,
tint: 0xFF6600
}, {
duration: 120,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 120
});
}
});
// After enough crescents, shoot at goal
if (self.flowHilalCount >= self.flowHilalMax) {
// Striker shot: powerful, straight, with curve
var goalDx = playerGoal.x - self.x;
var goalDy = playerGoal.y - self.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
var curveAmount = 18 + Math.random() * 8;
ball.velocityX = goalDx / goalDistance * 20 + curveAmount;
ball.velocityY = goalDy / goalDistance * 20;
ball.active = true;
// Visual effect for shot
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFF6600
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
self.flowHilalShotDone = true;
// Reset after a short delay for next Flow
LK.setTimeout(function () {
self.flowActive = false;
self.flowHilalCount = 0;
self.flowHilalShotDone = false;
}, 1200);
} else {
// Start next crescent immediately
self.flowHilalDribbling = true;
self.flowHilalStartTime = Date.now();
self.flowHilalOriginX = self.x;
self.flowHilalOriginY = self.y;
var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x);
var dir = self.flowHilalCount % 2 === 0 ? 1 : -1;
self.flowHilalStartAngle = angleToGoal - dir * Math.PI / 2;
self.flowHilalEndAngle = angleToGoal + dir * Math.PI / 2;
self.flowHilalPhase = 0;
}
}
// Prevent other dribbling/shot logic during Flow
return;
}
}
// (Original code follows)
// Sae behavior: pursue ball aggressively but stay in attacking areas
if (distance < 500) {
// Use dash if available and close to ball
if (!self.dashActive && self.dashCooldown <= 0 && distance < 150) {
self.dashActive = true;
self.dashCooldown = self.dashCooldownTime;
self.dashEndTime = Date.now() + self.dashDuration;
self.speed = self.dashSpeed;
// Visual effect for dash
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00FFFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
}
// Move toward ball but avoid going too deep into player's half
var pursuitSpeed = distance < 200 ? self.speed * 1.4 : self.speed * 1.2;
// Limit Sae's movement to stay in upper half of field (y < 1500)
var targetX = self.x + dx / distance * pursuitSpeed;
var targetY = self.y + dy / distance * pursuitSpeed;
if (targetY > 1500) {
// Don't go too deep, stay in attacking position
targetY = Math.min(targetY, 1500);
}
self.x = targetX;
self.y = targetY;
// Actions when close to ball
if (distance < 80) {
// Perfect Pass - find forward player to pass to
if (self.perfectPassCooldown <= 0) {
var nearestForward = null;
var nearestDistance = Infinity;
for (var i = 0; i < opponents.length; i++) {
if (opponents[i].role === 'forward') {
var forwardDx = opponents[i].x - self.x;
var forwardDy = opponents[i].y - self.y;
var forwardDistance = Math.sqrt(forwardDx * forwardDx + forwardDy * forwardDy);
if (forwardDistance < nearestDistance && forwardDistance < self.perfectPassRange) {
nearestDistance = forwardDistance;
nearestForward = opponents[i];
}
}
}
if (nearestForward) {
// Execute Perfect Pass
var passDx = nearestForward.x - ball.x;
var passDy = nearestForward.y - ball.y;
var passDist = Math.sqrt(passDx * passDx + passDy * passDy);
if (passDist > 0) {
ball.velocityX = passDx / passDist * 15;
ball.velocityY = passDy / passDist * 15;
ball.active = true;
}
self.perfectPassCooldown = self.perfectPassCooldownTime;
// Visual effect for Perfect Pass
tween(ball, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0x00FF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(ball, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
}
// Choose dribbling type randomly if no pass available
if (!self.isDribbling && !self.isZigzagDribbling && !self.isChopDribbling && self.perfectPassCooldown > 0) {
var dribbleType = Math.random();
if (dribbleType < 0.33) {
// Half-circle dribbling
self.isDribbling = true;
self.dribbleStartTime = Date.now();
self.dribbleRadius = 100;
// Calculate angle from Sae to player goal
var angleToGoal = Math.atan2(playerGoal.y - self.y, playerGoal.x - self.x);
self.dribbleStartAngle = angleToGoal - Math.PI / 2;
self.dribbleEndAngle = angleToGoal + Math.PI / 2;
self.dribbleOriginX = self.x;
self.dribbleOriginY = self.y;
} else if (dribbleType < 0.66) {
// Zigzag dribbling
self.isZigzagDribbling = true;
self.zigzagStartTime = Date.now();
self.zigzagStartX = self.x;
self.zigzagStartY = self.y;
// Dribble toward player goal with reduced distance
self.zigzagTargetX = self.x + (playerGoal.x - self.x) * 0.3 + (Math.random() - 0.5) * 80;
self.zigzagTargetY = self.y + (playerGoal.y - self.y) * 0.3 + 50;
// Visual effect for zigzag start
tween(self, {
tint: 0xFF4500
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else {
// Chop dribbling
self.isChopDribbling = true;
self.chopStartTime = Date.now();
self.chopStartX = self.x;
self.chopStartY = self.y;
// Chop toward player goal (even shorter range)
self.chopTargetX = self.x + (playerGoal.x - self.x) * 0.2 + (Math.random() - 0.5) * 30;
self.chopTargetY = self.y + (playerGoal.y - self.y) * 0.2 + 20;
// Visual effect for chop start
tween(self, {
tint: 0x32CD32
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
}
}
// False shot ability
if (self.falseShotCooldown <= 0 && !self.falseShotActive) {
self.falseShotActive = true;
self.falseShotCooldown = self.falseShotCooldownTime;
// Fake shot motion - ball barely moves
ball.velocityX = (Math.random() - 0.5) * 3;
ball.velocityY = (Math.random() - 0.5) * 3;
ball.active = true;
// Visual effect for false shot
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF6600
}, {
duration: 300,
onFinish: function onFinish() {
// After fake, real shot toward goal
var goalDx = playerGoal.x - ball.x;
var goalDy = playerGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
ball.velocityX = goalDx / goalDistance * 14;
ball.velocityY = goalDy / goalDistance * 14;
ball.active = true;
}
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
self.falseShotActive = false;
}
});
}
}
} else {
// Sae is always active: maintain attacking midfield position
var attackingMidfieldX = 1024 + (Math.random() - 0.5) * 300;
var attackingMidfieldY = 1000 + Math.random() * 200; // Stay in upper midfield area
var shotDx = attackingMidfieldX - self.x;
var shotDy = attackingMidfieldY - self.y;
var shotDist = Math.sqrt(shotDx * shotDx + shotDy * shotDy);
if (shotDist > 50) {
self.x += shotDx / shotDist * self.speed * 0.5;
self.y += shotDy / shotDist * self.speed * 0.5;
}
// If far from ball but in shooting range, attempt long-range curved shot
if (distance > 200 && distance < 700 && !self.isDribbling && !self.isZigzagDribbling && !self.isChopDribbling && !self.falseShotActive) {
// Falsolu şut: apply curve by adding to X velocity
var goalDx = playerGoal.x - ball.x;
var goalDy = playerGoal.y - ball.y;
var goalDistance = Math.sqrt(goalDx * goalDx + goalDy * goalDy);
if (goalDistance > 0) {
var curveAmount = 12 + Math.random() * 8; // Stronger curve for long shot
ball.velocityX = goalDx / goalDistance * 16 + curveAmount;
ball.velocityY = goalDy / goalDistance * 16;
ball.active = true;
// Visual effect for long-range shot
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF1493
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
}
}
}
// Apply Devoured effect to nearby opponents
self.applyDevouredEffect = function (radius) {
var opponentsInRange = [];
// Check all player team members
if (typeof player !== 'undefined') {
var playerDist = Math.sqrt((player.x - self.x) * (player.x - self.x) + (player.y - self.y) * (player.y - self.y));
if (playerDist <= radius) {
opponentsInRange.push(player);
}
}
if (typeof chigiri !== 'undefined') {
var chigiriDist = Math.sqrt((chigiri.x - self.x) * (chigiri.x - self.x) + (chigiri.y - self.y) * (chigiri.y - self.y));
if (chigiriDist <= radius) {
opponentsInRange.push(chigiri);
}
}
if (typeof hiori !== 'undefined') {
var hioriDist = Math.sqrt((hiori.x - self.x) * (hiori.x - self.x) + (hiori.y - self.y) * (hiori.y - self.y));
if (hioriDist <= radius) {
opponentsInRange.push(hiori);
}
}
if (typeof reo !== 'undefined') {
var reoDist = Math.sqrt((reo.x - self.x) * (reo.x - self.x) + (reo.y - self.y) * (reo.y - self.y));
if (reoDist <= radius) {
opponentsInRange.push(reo);
}
}
if (typeof nagi !== 'undefined') {
var nagiDist = Math.sqrt((nagi.x - self.x) * (nagi.x - self.x) + (nagi.y - self.y) * (nagi.y - self.y));
if (nagiDist <= radius) {
opponentsInRange.push(nagi);
}
}
// Apply Devoured effect to found opponents
for (var i = 0; i < opponentsInRange.length; i++) {
var opponent = opponentsInRange[i];
// 30% chance to apply full effect, otherwise just stun
var isFullDevoured = Math.random() < 0.3;
if (isFullDevoured) {
// Full Devoured effect: severely reduce speed
opponent.originalSpeed = opponent.speed;
opponent.speed = opponent.speed * 0.2; // 20% speed
opponent.devoured = true;
self.devouredOpponents.push(opponent);
// Visual effect for devoured opponent
tween(opponent, {
tint: 0x800080,
// Purple tint for devoured
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300
});
} else {
// Just stun effect
opponent.originalSpeed = opponent.speed;
opponent.speed = 0;
opponent.stunned = true;
self.dribbleStunnedOpponents.push(opponent);
// Visual effect for stunned opponent
tween(opponent, {
tint: 0xFFFF00,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
}
}
if (opponentsInRange.length > 0) {
self.devouredEndTime = Date.now() + self.devouredDuration;
if (self.dribbleStunnedOpponents.length > 0) {
self.dribbleStunEnd = Date.now() + self.dribbleStunDuration;
}
}
};
};
return self;
});
var SoccerBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.98;
self.active = true;
self.update = function () {
if (self.active) {
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Keep ball in bounds with bounce effect
if (self.x < 30) {
self.x = 30;
self.velocityX = Math.abs(self.velocityX) * 0.8;
}
if (self.x > 2018) {
self.x = 2018;
self.velocityX = -Math.abs(self.velocityX) * 0.8;
}
if (self.y < 30) {
self.y = 30;
self.velocityY = Math.abs(self.velocityY) * 0.8;
}
if (self.y > 2702) {
self.y = 2702;
self.velocityY = -Math.abs(self.velocityY) * 0.8;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
game.setBackgroundColor(0x228B22);
// Game variables
var opponents = [];
var playerScore = 0;
var opponentScore = 0;
var dragNode = null;
var maxStamina = 100;
var currentStamina = 100;
var staminaRegenRate = 0.2;
var staminaDrainRate = 0.5;
var lastPlayerX = 0;
var lastPlayerY = 0;
var shotChargeStart = 0;
var isChargingShot = false;
var shotPower = 0;
var maxShotPower = 20;
var minChargeTime = 2000; // 2 seconds minimum charge time
var lastTouchX = 0;
var lastTouchY = 0;
var ballDetachCooldown = 0;
var ballDetachCooldownTime = 1000; // 1 second cooldown
// UI Elements
var scoreTxt = new Text2('Player: 0 - Opponent: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Stamina UI
var staminaTxt = new Text2('Stamina: 100', {
size: 60,
fill: 0x00FF00
});
staminaTxt.anchor.set(0, 0);
staminaTxt.x = 50;
staminaTxt.y = 50;
LK.gui.topLeft.addChild(staminaTxt);
// Shot Power UI
var shotPowerTxt = new Text2('Shot Power: 0%', {
size: 60,
fill: 0xFFFFFF
});
shotPowerTxt.anchor.set(0, 0);
shotPowerTxt.x = 50;
shotPowerTxt.y = 130;
LK.gui.topLeft.addChild(shotPowerTxt);
// Game objects
var player = game.addChild(new Player());
player.x = 1024;
player.y = 2200;
var ball = game.addChild(new SoccerBall());
ball.x = 1024;
ball.y = 1366;
// Player's goal (bottom)
var playerGoal = game.addChild(new Goal());
playerGoal.x = 1024;
playerGoal.y = 2600;
// Opponent's goal (top)
var opponentGoal = game.addChild(new Goal());
opponentGoal.x = 1024;
opponentGoal.y = 300;
// Create opponents with specific roles
// Defense player (opponent)
var defensePlayer = game.addChild(new DefensePlayer());
defensePlayer.x = 1024;
defensePlayer.y = 600;
defensePlayer.homeX = 1024;
defensePlayer.homeY = 600;
opponents.push(defensePlayer);
// Chigiri - player team hybrid defender
var chigiri = game.addChild(new ChigiriPlayer(0x3399ff));
chigiri.x = 800;
chigiri.y = 2000;
chigiri.homeX = 800;
chigiri.homeY = 2000;
// Hiori - player team creative midfielder
var hiori = game.addChild(new HioriPlayer(0x6699ff));
hiori.x = 1200;
hiori.y = 2000;
hiori.homeX = 1200;
hiori.homeY = 2000;
// Reo - player team adaptive defender/attacker
var reo = game.addChild(new ReoPlayer(0x000000));
reo.x = 600;
reo.y = 1800;
reo.homeX = 600;
reo.homeY = 1800;
// Nagi - player team striker with Fake Volley ability
var nagi = game.addChild(new NagiPlayer(0x9966ff));
nagi.x = 1400;
nagi.y = 2000;
nagi.homeX = 1400;
nagi.homeY = 2000;
// Nagi starts without Flow
// Meguru Bachira - player team creative winger with Roulette and Monster abilities
var bachira = game.addChild(new MeguruBachiraPlayer(0x20B2AA));
bachira.x = 1000;
bachira.y = 1900;
bachira.homeX = 1000;
bachira.homeY = 1900;
// Midfield player
var midfieldPlayer = game.addChild(new MidfieldPlayer());
midfieldPlayer.x = 1024;
midfieldPlayer.y = 1000;
midfieldPlayer.homeX = 1024;
midfieldPlayer.homeY = 1000;
opponents.push(midfieldPlayer);
// Forward player
var forwardPlayer = game.addChild(new ForwardPlayer());
forwardPlayer.x = 1024;
forwardPlayer.y = 1400;
forwardPlayer.homeX = 1024;
forwardPlayer.homeY = 1400;
opponents.push(forwardPlayer);
// Sae - opponent team attacking midfielder with Perfect Pass and special abilities
var sae = game.addChild(new SaePlayer(0xff6600));
sae.x = 1024;
sae.y = 800;
sae.homeX = 1024;
sae.homeY = 800;
opponents.push(sae);
// Create goalkeepers
var playerGoalkeeper = game.addChild(new Goalkeeper(true)); // Blue goalkeeper for player team
playerGoalkeeper.x = 1024;
playerGoalkeeper.y = 2500; // In front of player's goal
playerGoalkeeper.originalX = 1024;
playerGoalkeeper.originalY = 2500;
var opponentGoalkeeper = game.addChild(new Goalkeeper(false)); // Red goalkeeper for opponent team
opponentGoalkeeper.x = 1024;
opponentGoalkeeper.y = 400; // In front of opponent's goal
opponentGoalkeeper.originalX = 1024;
opponentGoalkeeper.originalY = 400;
// Ball kicking mechanics
function kickBall(targetX, targetY, power) {
var dx = targetX - ball.x;
var dy = targetY - ball.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var finalPower = power || Math.min(distance / 100, 15);
// Properly detach ball from player first
player.hasBall = false;
ball.active = true;
// Set cooldown to prevent immediate re-attachment
ballDetachCooldown = ballDetachCooldownTime;
// Apply velocity after detachment
ball.velocityX = dx / distance * finalPower;
ball.velocityY = dy / distance * finalPower;
LK.getSound('kick').play();
}
}
// Touch controls for player movement and ball kicking
game.down = function (x, y, obj) {
// Always update last touch position for shot direction
lastTouchX = x;
lastTouchY = y;
var playerDistance = Math.sqrt((x - player.x) * (x - player.x) + (y - player.y) * (y - player.y));
var ballDistance = Math.sqrt((x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y));
if (playerDistance < ballDistance) {
// Move player
dragNode = player;
} else {
// Start charging shot if player has ball
if (player.hasBall) {
shotChargeStart = Date.now();
isChargingShot = true;
shotPower = 0;
}
}
};
game.move = function (x, y, obj) {
// Always update last touch position for shot direction
lastTouchX = x;
lastTouchY = y;
if (dragNode && currentStamina > 0) {
var dx = x - dragNode.x;
var dy = y - dragNode.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Slow down movement and consume stamina - even slower when having ball
var moveSpeed = player.hasBall ? 0.15 : 0.2;
dragNode.x += dx * moveSpeed;
dragNode.y += dy * moveSpeed;
// Consume stamina based on movement
if (distance > 5) {
currentStamina -= staminaDrainRate;
if (currentStamina < 0) currentStamina = 0;
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
// Handle shot release
if (isChargingShot && player.hasBall) {
var chargeTime = Date.now() - shotChargeStart;
if (chargeTime >= minChargeTime) {
// Release charged shot - use last touch position for direction
kickBall(lastTouchX, lastTouchY, shotPower);
} else {
// Not charged enough, no shot
}
isChargingShot = false;
shotPower = 0;
}
};
// Goal scoring function
function scoreGoal(isPlayer) {
if (isPlayer) {
playerScore++;
} else {
opponentScore++;
}
scoreTxt.setText('Player: ' + playerScore + ' - Opponent: ' + opponentScore);
LK.getSound('goal').play();
// Reset ball position
ball.x = 1024;
ball.y = 1366;
ball.velocityX = 0;
ball.velocityY = 0;
}
// Game update loop
game.update = function () {
// Update ball detach cooldown
if (ballDetachCooldown > 0) {
ballDetachCooldown -= 16; // Approximately 60 FPS
if (ballDetachCooldown < 0) ballDetachCooldown = 0;
}
// Check if player is close to ball for interaction (only if cooldown expired)
var playerToBallDistance = Math.sqrt((player.x - ball.x) * (player.x - ball.x) + (player.y - ball.y) * (player.y - ball.y));
if (playerToBallDistance < 80 && !player.hasBall && ballDetachCooldown <= 0) {
player.hasBall = true;
ball.active = false;
ball.velocityX = 0;
ball.velocityY = 0;
}
// If player has ball, make it stick to player
if (player.hasBall && ball.active === false) {
ball.x = player.x;
ball.y = player.y - 50;
}
// Stamina regeneration when not moving
var playerMoving = Math.abs(player.x - lastPlayerX) > 1 || Math.abs(player.y - lastPlayerY) > 1;
if (!playerMoving && currentStamina < maxStamina) {
currentStamina += staminaRegenRate;
if (currentStamina > maxStamina) currentStamina = maxStamina;
}
// Update last position
lastPlayerX = player.x;
lastPlayerY = player.y;
// Update stamina UI
var staminaPercent = Math.round(currentStamina / maxStamina * 100);
staminaTxt.setText('Stamina: ' + staminaPercent);
// Change color based on stamina level
if (staminaPercent > 50) {
staminaTxt.fill = 0x00FF00;
} else if (staminaPercent > 25) {
staminaTxt.fill = 0xFFFF00;
} else {
staminaTxt.fill = 0xFF0000;
}
// Update shot charging
if (isChargingShot && player.hasBall) {
var chargeTime = Date.now() - shotChargeStart;
if (chargeTime >= minChargeTime) {
// Calculate shot power based on charge time
var chargeProgress = Math.min((chargeTime - minChargeTime) / 1000, 1); // 1 second after minimum for max power
shotPower = 5 + chargeProgress * maxShotPower; // Minimum 5, maximum 25 power
var powerPercent = Math.round(chargeProgress * 100);
shotPowerTxt.setText('Shot Power: ' + powerPercent + '%');
shotPowerTxt.fill = 0x00FF00;
// Automatically kick ball forward when shot power reaches 100%
if (powerPercent >= 100) {
shotPower = 5 + maxShotPower; // Keep at maximum power
shotPowerTxt.setText('Shot Power: 100%');
shotPowerTxt.fill = 0x00FF00;
// Auto-kick ball forward when power reaches 100%
var forwardX = player.x;
var forwardY = player.y - 300; // Kick forward (up the field)
kickBall(forwardX, forwardY, shotPower);
isChargingShot = false;
shotPower = 0;
}
} else {
// Not charged enough yet
var timeLeft = Math.ceil((minChargeTime - chargeTime) / 1000);
shotPowerTxt.setText('Hold for ' + timeLeft + 's');
shotPowerTxt.fill = 0xFFFF00;
}
} else {
shotPowerTxt.setText('Shot Power: 0%');
shotPowerTxt.fill = 0xFFFFFF;
}
// Check player goal scoring (opponent scores)
if (ball.y > playerGoal.y - 50 && ball.x > playerGoal.x - 200 && ball.x < playerGoal.x + 200) {
scoreGoal(false);
LK.effects.flashObject(playerGoal, 0xFF0000, 500);
}
// Check opponent goal scoring (player scores)
if (ball.y < opponentGoal.y + 50 && ball.x > opponentGoal.x - 200 && ball.x < opponentGoal.x + 200) {
scoreGoal(true);
LK.effects.flashObject(opponentGoal, 0x00FF00, 500);
}
// Check win condition
if (playerScore >= 5) {
LK.showYouWin();
}
if (opponentScore >= 5) {
LK.showGameOver();
}
// Enhanced AI positioning based on ball location
for (var i = 0; i < opponents.length; i++) {
var opponent = opponents[i];
var ballThreatLevel = Math.sqrt((ball.x - 1024) * (ball.x - 1024) + (ball.y - 1366) * (ball.y - 1366));
// Adjust opponent aggression based on ball threat
if (ballThreatLevel < 300) {
// Ball is in center - all opponents become more aggressive
if (opponent.role === 'defense') {
opponent.speed = 4.5;
opponent.maxDistance = 500;
} else if (opponent.role === 'midfield') {
opponent.speed = 3.5;
} else if (opponent.role === 'forward') {
opponent.speed = 4.2;
}
} else {
// Ball is far - return to normal behavior
if (opponent.role === 'defense') {
opponent.speed = 4;
opponent.maxDistance = 400;
} else if (opponent.role === 'midfield') {
opponent.speed = 3;
} else if (opponent.role === 'forward') {
opponent.speed = 3.5;
}
}
}
// Role-based AI is now handled in each player's update method
// Defense, Midfield, and Forward players have their own specialized behaviors
};