/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); // Properties self.speedX = 0; self.speedY = -10; // Default upward self.damage = 10; self.range = 500; self.distanceTraveled = 0; self.lastX = 0; self.lastY = 0; // Create bullet graphic var bulletGraphic = self.attachAsset('dataPacket', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); bulletGraphic.tint = 0x00ffff; // Cyan color for bullet // Update method self.update = function () { // Store last position self.lastX = self.x; self.lastY = self.y; // Move bullet self.x += self.speedX; self.y += self.speedY; // Calculate distance traveled var dx = self.x - self.lastX; var dy = self.y - self.lastY; self.distanceTraveled += Math.sqrt(dx * dx + dy * dy); // Rotate in direction of movement self.rotation = Math.atan2(self.speedY, self.speedX); // Store if this bullet has already hit something self.isDestroyed = false; }; return self; }); var DataPacket = Container.expand(function () { var self = Container.call(this); // Color constants var COLORS = [0x3498db, 0xe74c3c, 0xf39c12, 0x2ecc71, 0x9b59b6]; var COLOR_NAMES = ['blue', 'red', 'orange', 'green', 'purple']; // Properties self.colorIndex = 0; self.colorName = ''; self.isBeingDragged = false; self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; self.matchedNode = null; // Create packet graphic var packetGraphic = self.attachAsset('dataPacket', { anchorX: 0.5, anchorY: 0.5 }); // Set random color self.setRandomColor = function () { self.colorIndex = Math.floor(Math.random() * COLORS.length); self.colorName = COLOR_NAMES[self.colorIndex]; packetGraphic.tint = COLORS[self.colorIndex]; }; // Down event for dragging self.down = function (x, y, obj) { self.isBeingDragged = true; // Bring to front if (self.parent) { self.parent.removeChild(self); self.parent.addChild(self); } }; // Update method called every tick self.update = function () { // Track position for intersection detection self.lastX = self.x; self.lastY = self.y; }; // Initialize with random color self.setRandomColor(); return self; }); var ErrorBlock = Container.expand(function () { var self = Container.call(this); // Properties self.lifespan = 10 * 60; // 10 seconds at 60fps // Create block graphic var blockGraphic = self.attachAsset('errorBlock', { anchorX: 0.5, anchorY: 0.5 }); // Update method self.update = function () { self.lifespan--; // Fade out near end of lifespan if (self.lifespan < 60) { self.alpha = self.lifespan / 60; } // Rotate slowly self.rotation += 0.01; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); // Properties self.speed = 5; self.health = 100; self.maxHealth = 100; self.score = 0; self.lastX = 0; self.lastY = 0; self.isMoving = false; self.lastDirection = 'right'; // Create player graphic var playerGraphic = self.attachAsset('dataPacket', { anchorX: 0.5, anchorY: 0.5 }); // Set player appearance playerGraphic.tint = 0xffffff; // White color playerGraphic.scaleX = 1.3; playerGraphic.scaleY = 1.3; // Health bar background var healthBarBg = self.attachAsset('stabilityBarBg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.3 }); healthBarBg.y = -60; // Health bar var healthBar = self.attachAsset('stabilityBar', { anchorX: 0, anchorY: 0.5, scaleX: 1.5, scaleY: 0.2 }); healthBar.y = -60; healthBar.x = -healthBarBg.width / 2; // Set health level self.setHealth = function (amount) { self.health = Math.max(0, Math.min(self.maxHealth, amount)); var healthPercentage = self.health / self.maxHealth; healthBar.scaleX = 1.5 * healthPercentage; // Update bar color based on health if (self.health <= 20) { healthBar.tint = 0xe74c3c; // Critical - red } else if (self.health <= 50) { healthBar.tint = 0xf39c12; // Warning - orange } else { healthBar.tint = 0x27ae60; // Normal - green } }; // Move player in specified direction self.move = function (dx, dy) { self.isMoving = true; // Determine direction if (Math.abs(dx) > Math.abs(dy)) { self.lastDirection = dx > 0 ? 'right' : 'left'; } else { self.lastDirection = dy > 0 ? 'down' : 'up'; } // Update position self.lastX = self.x; self.lastY = self.y; self.x += dx * self.speed; self.y += dy * self.speed; // Keep player within game bounds self.x = Math.max(50, Math.min(2048 - 50, self.x)); self.y = Math.max(150, Math.min(2732 - 150, self.y)); // Move weapon with player if (playerWeapon) { playerWeapon.x = self.x; playerWeapon.y = self.y - 30; } }; // Collect packet self.collectPacket = function (packet) { // Visual feedback tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); // Increase score self.score += 10; // Heal player slightly self.setHealth(self.health + 5); return self.score; }; // Take damage self.takeDamage = function (amount) { self.setHealth(self.health - amount); // Visual feedback LK.effects.flashObject(self, 0xff0000, 500); return self.health; }; // Update method self.update = function () { // Reset moving state each frame self.isMoving = false; // Pulse effect when health is low if (self.health < 30) { var pulseAmount = Math.sin(LK.ticks * 0.1) * 0.1 + 1; playerGraphic.scale.set(pulseAmount * 1.3, pulseAmount * 1.3); } // Store last position for collision detection self.lastX = self.x; self.lastY = self.y; }; // Initialize health self.setHealth(self.health); return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); // Properties self.type = 'slowTime'; // Default type self.isBeingDragged = false; self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Create power-up graphic var powerUpGraphic = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5 }); // Power-up types and their colors var TYPES = ['slowTime', 'clearPath', 'stabilityBoost']; var TYPE_COLORS = [0xf39c12, 0x9b59b6, 0x3498db]; // Set type self.setType = function (typeIndex) { self.type = TYPES[typeIndex]; powerUpGraphic.tint = TYPE_COLORS[typeIndex]; }; // Down event for dragging self.down = function (x, y, obj) { self.isBeingDragged = true; // Bring to front if (self.parent) { self.parent.removeChild(self); self.parent.addChild(self); } }; // Update method self.update = function () { // Pulse effect var pulseAmount = Math.sin(LK.ticks * 0.1) * 0.1 + 1; self.scale.set(pulseAmount, pulseAmount); // Track position self.lastX = self.x; self.lastY = self.y; }; // Set random type self.setType(Math.floor(Math.random() * TYPES.length)); return self; }); var SemibotPlayer = Container.expand(function () { var self = Container.call(this); // Bot properties self.active = true; self.intelligence = 0.7; // 0-1 scale of bot intelligence self.lastActionTime = 0; self.actionDelay = 60; // Frames between actions self.target = null; // Create semibot graphic var botGraphic = self.attachAsset('blueBot', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); botGraphic.tint = 0x3498db; // Blue color for semibot // Toggle bot activity self.toggle = function () { self.active = !self.active; return self.active; }; // Find optimal move - returns best packet and target node or villain to attack self.findOptimalMove = function () { // ALWAYS prioritize attacking the villain if player is visible if (player && villain && villain.active) { return { attackVillain: true }; } // Fall back to normal packet matching if villain isn't present if (!packets.length || !storageNodes.length) return null; // Only act if we have a good match var bestMatch = null; var highestScore = -1; for (var i = 0; i < packets.length; i++) { var packet = packets[i]; // Skip packets being dragged by player if (packet.isBeingDragged) continue; for (var j = 0; j < storageNodes.length; j++) { var node = storageNodes[j]; var score = 0; // Color match is most important if (packet.colorIndex === node.colorIndex) { score += 100; // Prioritize continuing chains if (lastSuccessfulChain === packet.colorIndex) { score += 50 * chainCount; } // Adjust for difficulty based on distance and position score -= Math.abs(packet.x - node.x) * 0.05; score -= packet.y * 0.1; // Prioritize packets farther down // Apply intelligence factor - sometimes make suboptimal choices if (Math.random() > self.intelligence) { score *= Math.random() * 0.5; } if (score > highestScore) { highestScore = score; bestMatch = { packet: packet, node: node }; } } } } return bestMatch; }; // Chase and attack villain self.chaseVillain = function () { if (!villain || !villain.active) return false; // Check if player is present - semibot only helps when player is around if (!player) return false; // Visual feedback - move semibot toward villain more quickly tween(self, { x: villain.x, y: villain.y - 50 }, { duration: 200, // Faster movement easing: tween.easeOutQuad, onFinish: function onFinish() { // Attack villain if close enough if (self.intersects(villain)) { // Deal increased damage to villain when helping player villain.takeDamage(30); // Flash semibot when attacking LK.effects.flashObject(self, 0x00ffff, 300); // If villain is defeated, prevent behavior from running again if (villain.health <= 0) { villain.active = false; // Add visual celebration effect for player and semibot defeating villain LK.effects.flashScreen(0x00ffff, 500); } } } }); return true; }; // Move a packet toward a node self.movePacketToNode = function (packet, node) { if (!packet || packet.isBeingDragged) return false; // Release current target if we have one if (self.target && self.target.isBeingDragged) { self.target.isBeingDragged = false; } // Set target packet as being dragged by bot self.target = packet; packet.isBeingDragged = true; // Visual feedback - move semibot to packet first tween(self, { x: packet.x, y: packet.y - 50 }, { duration: 200, easing: tween.easeOutQuad, onFinish: function onFinish() { // Flash semibot LK.effects.flashObject(self, 0x00ffff, 300); } }); // Move packet toward node with tweening for smooth movement tween(packet, { x: node.x, y: node.y - 20 }, { duration: 500, easing: tween.easeOutQuad, onFinish: function onFinish() { // Release packet when it reaches the node packet.isBeingDragged = false; self.target = null; // Check for match at destination for (var i = 0; i < storageNodes.length; i++) { if (checkNodeMatch(packet, storageNodes[i])) { break; } } } }); return true; }; // Update method self.update = function () { if (!self.active || !gameRunning) return; // Track position for collision detection self.lastX = self.x; self.lastY = self.y; // Pulse effect var pulseAmount = Math.sin(LK.ticks * 0.05) * 0.05 + 1; self.scale.set(pulseAmount * 1.2, pulseAmount * 1.2); // Only act periodically to allow player to also interact if (LK.ticks - self.lastActionTime < self.actionDelay) return; // Skip action randomly to make bot feel more human if (Math.random() > 0.8) return; // PRIORITY 1: Attack villain if player is visible and villain exists if (player && villain && villain.active) { // If player is visible and villain exists, attack villain if (self.chaseVillain()) { self.lastActionTime = LK.ticks; // Visual feedback for semibot helping player LK.effects.flashObject(self, 0x00ffff, 300); return; } } // Check if we need to handle power-ups when stability is low if (stabilityBar.stability < 30 && powerUps.length > 0) { for (var i = 0; i < powerUps.length; i++) { var powerUp = powerUps[i]; if (!powerUp.isBeingDragged && powerUp.y > 300) { // Move power-up to center to activate it tween(powerUp, { x: 2048 / 2, y: 2732 / 2 }, { duration: 300, easing: tween.easeOutQuad, onFinish: function onFinish() { activatePowerUp(powerUp); var index = powerUps.indexOf(powerUp); if (index !== -1) { powerUps.splice(index, 1); } powerUp.destroy(); } }); self.lastActionTime = LK.ticks; return; } } } // Find and execute optimal move var move = self.findOptimalMove(); if (move) { if (move.attackVillain) { // Chase and attack the villain self.chaseVillain(); } else { // Regular packet matching behavior self.movePacketToNode(move.packet, move.node); } self.lastActionTime = LK.ticks; } }; return self; }); // Down event for dragging var StabilityBar = Container.expand(function () { var self = Container.call(this); // Properties self.stability = 50; // Start at 50% self.maxStability = 100; self.warningLevel = 25; self.criticalLevel = 10; // Create background var barBg = self.attachAsset('stabilityBarBg', { anchorX: 0.5, anchorY: 0.5 }); // Create bar var bar = self.attachAsset('stabilityBar', { anchorX: 0.5, anchorY: 1.0 }); // Position the bar bar.y = barBg.height / 2; // Set stability amount (0-100) self.setStability = function (amount) { self.stability = Math.max(0, Math.min(self.maxStability, amount)); // Update bar height var heightPercentage = self.stability / self.maxStability; bar.scaleY = heightPercentage; // Update bar color based on stability if (self.stability <= self.criticalLevel) { bar.tint = 0xe74c3c; // Critical - red } else if (self.stability <= self.warningLevel) { bar.tint = 0xf39c12; // Warning - orange } else { bar.tint = 0x27ae60; // Normal - green } }; // Change stability self.changeStability = function (delta) { self.setStability(self.stability + delta); return self.stability; }; // Initialize self.setStability(self.stability); return self; }); // Game variables var StorageNode = Container.expand(function () { var self = Container.call(this); // Color constants var COLORS = [0x3498db, 0xe74c3c, 0xf39c12, 0x2ecc71, 0x9b59b6]; var COLOR_NAMES = ['blue', 'red', 'orange', 'green', 'purple']; // Properties self.colorIndex = 0; self.colorName = ''; self.lastPacketsStored = 0; self.packetsStored = 0; // Create node graphic var nodeGraphic = self.attachAsset('storageNode', { anchorX: 0.5, anchorY: 0.5 }); // Set color self.setColor = function (index) { self.colorIndex = index; self.colorName = COLOR_NAMES[index]; nodeGraphic.tint = COLORS[index]; }; // Handle packet storage self.storePacket = function () { self.packetsStored++; // Visual feedback tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); }; // Update method self.update = function () { self.lastPacketsStored = self.packetsStored; }; return self; }); var TaxmanPlayer = Container.expand(function () { var self = Container.call(this); // Properties self.active = false; self.taxRate = 0.10; // 10% tax rate self.taxCollected = 0; self.collectionInterval = 120; // Collect every 2 seconds (120 frames) self.lastCollectionTime = 0; self.targetPacket = null; self.speed = 3; // Movement speed when chasing player self.lastX = 0; self.lastY = 0; self.chasingPlayer = false; self.playerChaseCooldown = 0; // Visual representation var taxmanGraphic = self.attachAsset('taxman', { anchorX: 0.5, anchorY: 0.5 }); taxmanGraphic.tint = 0x990000; // Dark red color for taxman taxmanGraphic.scaleX = 1.2; taxmanGraphic.scaleY = 1.2; // Toggle taxman activity self.toggle = function () { self.active = !self.active; return self.active; }; // Find a packet to tax self.findPacketToTax = function () { if (!packets.length) return null; // Find the highest value packet (prefer packets being matched) var bestPacket = null; var highestValue = -1; for (var i = 0; i < packets.length; i++) { var packet = packets[i]; // Skip packets being dragged if (packet.isBeingDragged) continue; var value = 10; // Base value // Prefer packets that are about to be matched for (var j = 0; j < storageNodes.length; j++) { var node = storageNodes[j]; if (packet.colorIndex === node.colorIndex) { // Distance to node (closer = higher value) var distance = Math.sqrt(Math.pow(packet.x - node.x, 2) + Math.pow(packet.y - node.y, 2)); if (distance < 300) { value += 300 - distance; } } } if (value > highestValue) { highestValue = value; bestPacket = packet; } } return bestPacket; }; // Apply tax to a packet (reduces its value) self.taxPacket = function (packet) { if (!packet) return; // Visual feedback self.x = packet.x; self.y = packet.y; // Flash effect tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0.7 }, { duration: 300, onFinish: function onFinish() { tween(self, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 300 }); } }); // Collect tax self.taxCollected += 1; // Reduce packet value visually (make it slightly transparent) packet.alpha = 0.8; // Update UI if (taxmanStatusText) { taxmanStatusText.setText('Taxman: ' + (self.active ? 'Active' : 'Paused') + ' ($' + self.taxCollected + ')'); } }; // Audit the player (tax the player directly) self.auditPlayer = function () { if (!player) return; // Visual feedback tween(self, { scaleX: 1.7, scaleY: 1.7, alpha: 0.8 }, { duration: 400, onFinish: function onFinish() { tween(self, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 300 }); } }); // Take some health from player as "tax" player.takeDamage(5); // Increase tax collected self.taxCollected += 5; // Reset chase cooldown self.playerChaseCooldown = 300; // 5 seconds cooldown self.chasingPlayer = false; // Update UI if (taxmanStatusText) { taxmanStatusText.setText('Taxman: ' + (self.active ? 'Active' : 'Paused') + ' ($' + self.taxCollected + ')'); } }; // Chase the player self.chasePlayer = function () { if (!player) return; // Calculate direction to player var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If close enough to player, audit them if (distance < 80) { self.auditPlayer(); return; } // Normalize direction and move towards player if (distance > 0) { dx = dx / distance; dy = dy / distance; } // Save last position self.lastX = self.x; self.lastY = self.y; // Move towards player self.x += dx * self.speed; self.y += dy * self.speed; // Keep within game bounds self.x = Math.max(50, Math.min(2048 - 50, self.x)); self.y = Math.max(150, Math.min(2732 - 150, self.y)); }; // Update method self.update = function () { if (!self.active || !gameRunning) return; // Track last position self.lastX = self.x; self.lastY = self.y; // Decrease player chase cooldown if active if (self.playerChaseCooldown > 0) { self.playerChaseCooldown--; } // Decide whether to chase player or packets if (!self.chasingPlayer && self.playerChaseCooldown <= 0 && player && Math.random() < 0.01) { // 1% chance per frame to start chasing player when not on cooldown self.chasingPlayer = true; } // Chase player if in that mode if (self.chasingPlayer && player) { self.chasePlayer(); return; } // Otherwise collect tax from packets if (LK.ticks - self.lastCollectionTime < self.collectionInterval) return; // Find and tax a packet var packetToTax = self.findPacketToTax(); if (packetToTax) { self.taxPacket(packetToTax); self.lastCollectionTime = LK.ticks; } }; return self; }); var VillainPlayer = Container.expand(function () { var self = Container.call(this); // Villain properties self.active = true; self.health = 100; self.maxHealth = 100; self.attackCooldown = 180; // 3 seconds at 60fps self.currentCooldown = 0; self.target = null; self.targetingPlayer = true; // Alternates between targeting player and nodes self.attackDamage = 15; self.speed = 2; self.lastX = 0; self.lastY = 0; // Create villain graphic var villainGraphic = self.attachAsset('dataPacket', { anchorX: 0.5, anchorY: 0.5 }); villainGraphic.tint = 0x800080; // Purple color for villain villainGraphic.scaleX = 1.4; villainGraphic.scaleY = 1.4; // Health bar background var healthBarBg = self.attachAsset('stabilityBarBg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 0.2 }); healthBarBg.y = -50; // Health bar var healthBar = self.attachAsset('stabilityBar', { anchorX: 0, anchorY: 0.5, scaleX: 1.2, scaleY: 0.15 }); healthBar.y = -50; healthBar.x = -healthBarBg.width / 2; // Set health level self.setHealth = function (amount) { self.health = Math.max(0, Math.min(self.maxHealth, amount)); var healthPercentage = self.health / self.maxHealth; healthBar.scaleX = 1.2 * healthPercentage; // Update bar color based on health if (self.health <= 20) { healthBar.tint = 0xe74c3c; // Critical - red } else if (self.health <= 50) { healthBar.tint = 0xf39c12; // Warning - orange } else { healthBar.tint = 0x800080; // Normal - purple } }; // Take damage self.takeDamage = function (amount) { self.setHealth(self.health - amount); // Visual feedback LK.effects.flashObject(self, 0xff0000, 300); return self.health; }; // Find target (player or storage node) self.findTarget = function () { // Target player if that's our current priority if (self.targetingPlayer && player) { self.target = player; return; } // Otherwise target a random storage node if (storageNodes.length > 0) { self.target = storageNodes[Math.floor(Math.random() * storageNodes.length)]; } }; // Attack target self.attackTarget = function () { self.currentCooldown = self.attackCooldown; // If targeting player, reduce player health if (self.target === player) { player.takeDamage(self.attackDamage); // Visual effect for attack tween(self, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200, onFinish: function onFinish() { tween(self, { scaleX: 1.4, scaleY: 1.4 }, { duration: 200 }); } }); } // If targeting node, create error blocks around it else if (storageNodes.indexOf(self.target) !== -1) { // Create error blocks around the node for (var i = 0; i < 3; i++) { var angle = Math.random() * Math.PI * 2; var distance = 80 + Math.random() * 40; var x = self.target.x + Math.cos(angle) * distance; var y = self.target.y + Math.sin(angle) * distance; createErrorBlock(x, y); } // Reduce stability stabilityBar.changeStability(-10); // Visual effect for attack tween(self, { rotation: Math.PI * 2 }, { duration: 500, onFinish: function onFinish() { self.rotation = 0; } }); } // Switch targeting priority self.targetingPlayer = !self.targetingPlayer; }; // Move toward target self.moveTowardTarget = function () { if (!self.target) return; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If close enough, attack if (distance < 100 && self.currentCooldown <= 0) { self.attackTarget(); return; } // Normalize movement vector if (distance > 0) { dx = dx / distance; dy = dy / distance; } // Move villain toward target self.lastX = self.x; self.lastY = self.y; self.x += dx * self.speed; self.y += dy * self.speed; // Keep villain within game bounds self.x = Math.max(100, Math.min(2048 - 100, self.x)); self.y = Math.max(200, Math.min(2732 - 200, self.y)); }; // Update method self.update = function () { if (!self.active || !gameRunning) return; // Decrease attack cooldown if (self.currentCooldown > 0) { self.currentCooldown--; } // Find new target if we don't have one if (!self.target || self.target === player && !player) { self.findTarget(); } // Move toward target self.moveTowardTarget(); // Pulse effect when attacking if (self.currentCooldown > self.attackCooldown - 30) { var pulseAmount = 1 + 0.2 * Math.sin(LK.ticks * 0.2); villainGraphic.scale.set(pulseAmount * 1.4, pulseAmount * 1.4); } else { villainGraphic.scale.set(1.4, 1.4); } // Track position for collision detection self.lastX = self.x; self.lastY = self.y; }; // Initialize health self.setHealth(self.health); return self; }); var Weapon = Container.expand(function () { var self = Container.call(this); // Properties self.damage = 10; self.cooldown = 30; // frames between shots self.currentCooldown = 0; self.range = 500; self.lastX = 0; self.lastY = 0; // Create weapon graphic var weaponGraphic = self.attachAsset('dataPacket', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); weaponGraphic.tint = 0x00ffff; // Cyan color for weapon // Update method self.update = function () { // Update cooldown if (self.currentCooldown > 0) { self.currentCooldown--; } // Track position for collision detection self.lastX = self.x; self.lastY = self.y; }; // Fire method self.fire = function (targetX, targetY) { if (self.currentCooldown > 0) return null; // Reset cooldown self.currentCooldown = self.cooldown; // Create bullet var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; // Calculate direction to target var dx = targetX - self.x; var dy = targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Normalize direction if (dist > 0) { dx = dx / dist; dy = dy / dist; } // Set bullet properties bullet.speedX = dx * 10; bullet.speedY = dy * 10; bullet.damage = self.damage; bullet.range = self.range; // Visual feedback tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); return bullet; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Tween library for animations // Game assets // Game variables var packetSpawnInterval = 3 * 60; // Spawn every 3 seconds at 60fps var nextPacketSpawn = packetSpawnInterval; var stabilityDecayRate = 0.05; // Stability decay per tick var packets = []; var storageNodes = []; var errorBlocks = []; var powerUps = []; var level = 1; var score = 0; var player; var playerHealthText; var stabilityBar; var scoreText; var levelText; var botStatusText; var draggedObject = null; var gameRunning = true; var lastSuccessfulChain = 0; var chainCount = 0; var semibot; // AI assistant player var taxman; // Taxman player var villain; // Villain player var playerWeapon; // Player's weapon var bullets = []; // Bullets in the game var targetX = 0; // Target X for weapon var targetY = 0; // Target Y for weapon // UI text setup scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 50; scoreText.y = 50; LK.gui.topLeft.addChild(scoreText); // Add message to inform player they can only shoot villains var shootInfoText = new Text2('You can only shoot villains!', { size: 50, fill: 0x00FFFF }); shootInfoText.anchor.set(0.5, 0); shootInfoText.x = 0; shootInfoText.y = 540; LK.gui.top.addChild(shootInfoText); levelText = new Text2('Level: 1', { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(1, 0); levelText.x = -50; levelText.y = 50; LK.gui.topRight.addChild(levelText); // Bot status text botStatusText = new Text2('Bot: Active', { size: 60, fill: 0x00FF00 }); botStatusText.anchor.set(0.5, 0); botStatusText.x = 0; botStatusText.y = 50; LK.gui.top.addChild(botStatusText); // Taxman status text var taxmanStatusText = new Text2('Taxman: Paused ($0)', { size: 60, fill: 0xFF0000 }); taxmanStatusText.anchor.set(0.5, 0); taxmanStatusText.x = 0; taxmanStatusText.y = 230; LK.gui.top.addChild(taxmanStatusText); // Create toggle button var toggleBotBtn = new Text2('Toggle Bot', { size: 60, fill: 0xFFFFFF }); toggleBotBtn.anchor.set(0.5, 0); toggleBotBtn.x = 0; toggleBotBtn.y = 120; toggleBotBtn.interactive = true; toggleBotBtn.hitArea = new Rectangle(-150, 0, 300, 60); // Toggle bot event toggleBotBtn.down = function () { if (semibot) { var isActive = semibot.toggle(); botStatusText.setText('Bot: ' + (isActive ? 'Active' : 'Paused')); botStatusText.style.fill = isActive ? 0x00FF00 : 0xFF0000; } }; LK.gui.top.addChild(toggleBotBtn); // Create toggle taxman button var toggleTaxmanBtn = new Text2('Toggle Taxman', { size: 60, fill: 0xFFFFFF }); toggleTaxmanBtn.anchor.set(0.5, 0); toggleTaxmanBtn.x = 0; toggleTaxmanBtn.y = 300; toggleTaxmanBtn.interactive = true; toggleTaxmanBtn.hitArea = new Rectangle(-150, 0, 300, 60); // Toggle taxman event toggleTaxmanBtn.down = function () { if (taxman) { var isActive = taxman.toggle(); taxmanStatusText.setText('Taxman: ' + (isActive ? 'Active' : 'Paused') + ' ($' + taxman.taxCollected + ')'); taxmanStatusText.style.fill = isActive ? 0x00FF00 : 0xFF0000; } }; LK.gui.top.addChild(toggleTaxmanBtn); // Player health text playerHealthText = new Text2('Health: 100%', { size: 60, fill: 0x00FF00 }); playerHealthText.anchor.set(0, 1); playerHealthText.x = 50; playerHealthText.y = -50; LK.gui.bottomLeft.addChild(playerHealthText); // Initialize stability bar stabilityBar = new StabilityBar(); stabilityBar.x = 80; stabilityBar.y = 2732 / 2; game.addChild(stabilityBar); // Create storage nodes function createStorageNodes() { // Clear existing nodes storageNodes.forEach(function (node) { node.destroy(); }); storageNodes = []; var numNodes = Math.min(5, 2 + Math.floor(level / 2)); // Increase nodes with level var spacing = 2048 / (numNodes + 1); for (var i = 0; i < numNodes; i++) { var node = new StorageNode(); node.setColor(i % 5); node.x = spacing * (i + 1); node.y = 2732 - 300; game.addChild(node); storageNodes.push(node); } } // Create a new data packet function createDataPacket() { var packet = new DataPacket(); packet.x = Math.random() * (2048 - 200) + 100; packet.y = -100; game.addChild(packet); packets.push(packet); // Random vertical speed based on level packet.speedY = 1 + Math.random() * level * 0.5; return packet; } // Create power-up function createPowerUp() { var powerUp = new PowerUp(); powerUp.x = Math.random() * (2048 - 200) + 100; powerUp.y = -100; game.addChild(powerUp); powerUps.push(powerUp); // Set vertical speed powerUp.speedY = 1 + Math.random() * 0.5; return powerUp; } // Create error block obstacle function createErrorBlock(x, y) { var block = new ErrorBlock(); block.x = x; block.y = y; game.addChild(block); errorBlocks.push(block); return block; } // Handle power-up effect function activatePowerUp(powerUp) { LK.getSound('powerup').play(); switch (powerUp.type) { case 'slowTime': // Slow down all packets packets.forEach(function (packet) { packet.speedY *= 0.5; }); break; case 'clearPath': // Remove all error blocks errorBlocks.forEach(function (block) { block.destroy(); }); errorBlocks = []; break; case 'stabilityBoost': // Boost stability stabilityBar.changeStability(25); break; } // Visual feedback LK.effects.flashScreen(0xf39c12, 500); } // Check if a packet matches a storage node function checkNodeMatch(packet, node) { if (packet.colorIndex === node.colorIndex && packet.intersects(node)) { // Match found LK.getSound('match').play(); // Update score based on chain if (lastSuccessfulChain === packet.colorIndex) { chainCount++; score += 10 * chainCount; } else { chainCount = 1; score += 10; } // Apply tax reduction if packet was taxed (indicated by alpha < 1) if (packet.alpha < 1 && taxman) { // Reduce score by taxman's rate var taxAmount = Math.floor(score * taxman.taxRate); score -= taxAmount; taxman.taxCollected += taxAmount; // Update taxman display if (taxmanStatusText) { taxmanStatusText.setText('Taxman: ' + (taxman.active ? 'Active' : 'Paused') + ' ($' + taxman.taxCollected + ')'); } } lastSuccessfulChain = packet.colorIndex; // Update score display LK.setScore(score); scoreText.setText('Score: ' + score); // Increase stability stabilityBar.changeStability(5); // Visual feedback node.storePacket(); // Remove packet var index = packets.indexOf(packet); if (index !== -1) { packets.splice(index, 1); } packet.destroy(); // Level up check if (score >= level * 100) { levelUp(); } return true; } return false; } // Handle level up function levelUp() { level++; levelText.setText('Level: ' + level); // Increase difficulty packetSpawnInterval = Math.max(60, packetSpawnInterval - 15); stabilityDecayRate += 0.01; // Create new node layout createStorageNodes(); // Visual feedback LK.effects.flashScreen(0x2ecc71, 500); } // Initialize semibot player if (!semibot) { semibot = new SemibotPlayer(); semibot.x = 2048 / 4; // Position at 1/4 of screen width semibot.y = 400; // Position near top } game.addChild(semibot); // The semibot is now persistent across game resets // Initialize taxman player taxman = new TaxmanPlayer(); taxman.x = 100; taxman.y = 300; game.addChild(taxman); // Initialize game createStorageNodes(); // Initialize player player = new Player(); player.x = 2048 / 2; player.y = 2732 / 2; game.addChild(player); // Initialize player weapon playerWeapon = new Weapon(); playerWeapon.x = player.x; playerWeapon.y = player.y - 30; game.addChild(playerWeapon); // Initialize villain villain = new VillainPlayer(); villain.x = 2048 / 2; villain.y = 300; game.addChild(villain); // Villain status text var villainStatusText = new Text2('Villain: Active', { size: 60, fill: 0x800080 }); villainStatusText.anchor.set(0.5, 0); villainStatusText.x = 0; villainStatusText.y = 400; LK.gui.top.addChild(villainStatusText); // Create toggle villain button var toggleVillainBtn = new Text2('Toggle Villain', { size: 60, fill: 0xFFFFFF }); toggleVillainBtn.anchor.set(0.5, 0); toggleVillainBtn.x = 0; toggleVillainBtn.y = 470; toggleVillainBtn.interactive = true; toggleVillainBtn.hitArea = new Rectangle(-150, 0, 300, 60); // Toggle villain event toggleVillainBtn.down = function () { if (villain) { villain.active = !villain.active; villainStatusText.setText('Villain: ' + (villain.active ? 'Active' : 'Paused')); villainStatusText.style.fill = villain.active ? 0x800080 : 0xFF0000; } }; LK.gui.top.addChild(toggleVillainBtn); // Game touch/mouse events game.down = function (x, y, obj) { // Check if we're clicking on a packet or power-up var foundObject = false; // Check packets first (in reverse to get top-most) for (var i = packets.length - 1; i >= 0; i--) { var packet = packets[i]; if (packet.getBounds().contains(x, y)) { draggedObject = packet; packet.down(x, y, obj); foundObject = true; break; } } // Check power-ups if no packet was found if (!foundObject) { for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; if (powerUp.getBounds().contains(x, y)) { draggedObject = powerUp; powerUp.down(x, y, obj); foundObject = true; break; } } } }; game.move = function (x, y, obj) { if (draggedObject && draggedObject.isBeingDragged) { draggedObject.x = x; draggedObject.y = y; } // If player exists and no object is being dragged, move player toward touch position if (player && !draggedObject) { var dx = x - player.x; var dy = y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Only move if touch is not too close to player if (distance > 30) { // Normalize and move dx = dx / distance; dy = dy / distance; player.move(dx, dy); } // Set target for weapon aiming targetX = x; targetY = y; // Fire weapon if cooldown allows if (playerWeapon && playerWeapon.currentCooldown <= 0) { var bullet = playerWeapon.fire(targetX, targetY); if (bullet) { game.addChild(bullet); bullets.push(bullet); } } } }; game.up = function (x, y, obj) { if (draggedObject) { // Check for matches with storage nodes if (draggedObject instanceof DataPacket) { var matched = false; for (var i = 0; i < storageNodes.length; i++) { if (checkNodeMatch(draggedObject, storageNodes[i])) { matched = true; break; } } // Create error block if no match if (!matched) { LK.getSound('error').play(); createErrorBlock(draggedObject.x, draggedObject.y); stabilityBar.changeStability(-10); // Remove the mismatched packet var index = packets.indexOf(draggedObject); if (index !== -1) { packets.splice(index, 1); } draggedObject.destroy(); } } // Check if it's a power-up else if (draggedObject instanceof PowerUp) { activatePowerUp(draggedObject); // Remove the used power-up var index = powerUps.indexOf(draggedObject); if (index !== -1) { powerUps.splice(index, 1); } draggedObject.destroy(); } draggedObject = null; } }; // Main game update loop game.update = function () { if (!gameRunning) return; // Update player if (player) { player.update(); // Update player health text playerHealthText.setText('Health: ' + player.health + '%'); // Ensure style object exists before setting fill property if (!playerHealthText.style) playerHealthText.style = {}; playerHealthText.style.fill = player.health <= 20 ? 0xe74c3c : player.health <= 50 ? 0xf39c12 : 0x00FF00; // Check collisions with packets for (var i = packets.length - 1; i >= 0; i--) { if (player.intersects(packets[i]) && !packets[i].isBeingDragged) { if (packets[i].colorIndex === 0) { // Blue packets are good for player player.collectPacket(packets[i]); packets.splice(i, 1); } } } // Check collisions with error blocks for (var i = errorBlocks.length - 1; i >= 0; i--) { if (player.intersects(errorBlocks[i])) { player.takeDamage(0.2); // Slow damage over time } } // Game over if player dies if (player.health <= 0 && gameRunning) { gameRunning = false; LK.effects.flashScreen(0xe74c3c, 1000); LK.showGameOver(); } } // Update player weapon if (playerWeapon) { playerWeapon.update(); } // Update semibot if active if (semibot) { semibot.update(); } // Update taxman if active if (taxman) { taxman.update(); // Check for player-taxman collisions when not currently auditing if (player && player.intersects(taxman) && taxman.active && taxman.playerChaseCooldown <= 0) { taxman.auditPlayer(); // Add knockback effect when colliding var dx = player.x - taxman.x; var dy = player.y - taxman.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { dx = dx / dist; dy = dy / dist; tween(player, { x: player.x + dx * 40, y: player.y + dy * 40 }, { duration: 200 }); } } } // Update villain if active if (villain) { villain.update(); // Check for player-villain collisions if (player && player.intersects(villain) && villain.currentCooldown <= 0) { // Damage player on collision player.takeDamage(5); villain.currentCooldown = 60; // Set a cooldown // Knockback effect var dx = player.x - villain.x; var dy = player.y - villain.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { dx = dx / dist; dy = dy / dist; // Apply knockback to both tween(player, { x: player.x + dx * 50, y: player.y + dy * 50 }, { duration: 200 }); tween(villain, { x: villain.x - dx * 30, y: villain.y - dy * 30 }, { duration: 200 }); } } // Check for packet-villain collisions for (var i = packets.length - 1; i >= 0; i--) { if (packets[i].intersects(villain)) { // Villain steals packets packets[i].destroy(); packets.splice(i, 1); // Villain gains health villain.setHealth(villain.health + 5); // Visual feedback tween(villain, { scaleX: 1.6, scaleY: 1.6 }, { duration: 200, onFinish: function onFinish() { tween(villain, { scaleX: 1.4, scaleY: 1.4 }, { duration: 200 }); } }); } } } // Update all game objects for (var i = packets.length - 1; i >= 0; i--) { var packet = packets[i]; packet.update(); // Move non-dragged packets down if (!packet.isBeingDragged) { packet.y += packet.speedY; } // Check if packet is out of bounds if (packet.y > 2732 + 100) { // Missed packet - penalty stabilityBar.changeStability(-15); LK.getSound('error').play(); // Remove packet packets.splice(i, 1); packet.destroy(); continue; } } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Check if bullet is out of bounds or exceeded range if (bullet.x < -50 || bullet.x > 2048 + 50 || bullet.y < -50 || bullet.y > 2732 + 50 || bullet.distanceTraveled > bullet.range) { bullets.splice(i, 1); bullet.destroy(); continue; } // Check bullet collisions with villain if (villain && bullet.intersects(villain)) { // Damage villain villain.takeDamage(bullet.damage); // Remove bullet bullets.splice(i, 1); bullet.destroy(); // Increase score score += 5; LK.setScore(score); scoreText.setText('Score: ' + score); continue; } // We've removed the code that checked for bullet collisions with error blocks // as player can only shoot villains now } // Update power-ups for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; powerUp.update(); // Move non-dragged power-ups down if (!powerUp.isBeingDragged) { powerUp.y += powerUp.speedY; } // Remove if out of bounds if (powerUp.y > 2732 + 100) { powerUps.splice(i, 1); powerUp.destroy(); } } // Update error blocks for (var i = errorBlocks.length - 1; i >= 0; i--) { var block = errorBlocks[i]; block.update(); // Remove expired blocks if (block.lifespan <= 0) { errorBlocks.splice(i, 1); block.destroy(); } } // Update storage nodes for (var i = 0; i < storageNodes.length; i++) { storageNodes[i].update(); } // Spawn new packets nextPacketSpawn--; if (nextPacketSpawn <= 0) { createDataPacket(); nextPacketSpawn = packetSpawnInterval; // Occasionally spawn power-ups (5% chance) if (Math.random() < 0.05) { createPowerUp(); } } // Decrease stability over time stabilityBar.changeStability(-stabilityDecayRate); // Warning when stability is low if (stabilityBar.stability <= stabilityBar.warningLevel && LK.ticks % 60 === 0) { LK.getSound('warning').play(); } // Check for game over if (stabilityBar.stability <= 0) { gameRunning = false; LK.effects.flashScreen(0xe74c3c, 1000); LK.showGameOver(); } }; // Set background color to dark blue/grey for tech theme game.setBackgroundColor(0x2c3e50);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Properties
self.speedX = 0;
self.speedY = -10; // Default upward
self.damage = 10;
self.range = 500;
self.distanceTraveled = 0;
self.lastX = 0;
self.lastY = 0;
// Create bullet graphic
var bulletGraphic = self.attachAsset('dataPacket', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
bulletGraphic.tint = 0x00ffff; // Cyan color for bullet
// Update method
self.update = function () {
// Store last position
self.lastX = self.x;
self.lastY = self.y;
// Move bullet
self.x += self.speedX;
self.y += self.speedY;
// Calculate distance traveled
var dx = self.x - self.lastX;
var dy = self.y - self.lastY;
self.distanceTraveled += Math.sqrt(dx * dx + dy * dy);
// Rotate in direction of movement
self.rotation = Math.atan2(self.speedY, self.speedX);
// Store if this bullet has already hit something
self.isDestroyed = false;
};
return self;
});
var DataPacket = Container.expand(function () {
var self = Container.call(this);
// Color constants
var COLORS = [0x3498db, 0xe74c3c, 0xf39c12, 0x2ecc71, 0x9b59b6];
var COLOR_NAMES = ['blue', 'red', 'orange', 'green', 'purple'];
// Properties
self.colorIndex = 0;
self.colorName = '';
self.isBeingDragged = false;
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
self.matchedNode = null;
// Create packet graphic
var packetGraphic = self.attachAsset('dataPacket', {
anchorX: 0.5,
anchorY: 0.5
});
// Set random color
self.setRandomColor = function () {
self.colorIndex = Math.floor(Math.random() * COLORS.length);
self.colorName = COLOR_NAMES[self.colorIndex];
packetGraphic.tint = COLORS[self.colorIndex];
};
// Down event for dragging
self.down = function (x, y, obj) {
self.isBeingDragged = true;
// Bring to front
if (self.parent) {
self.parent.removeChild(self);
self.parent.addChild(self);
}
};
// Update method called every tick
self.update = function () {
// Track position for intersection detection
self.lastX = self.x;
self.lastY = self.y;
};
// Initialize with random color
self.setRandomColor();
return self;
});
var ErrorBlock = Container.expand(function () {
var self = Container.call(this);
// Properties
self.lifespan = 10 * 60; // 10 seconds at 60fps
// Create block graphic
var blockGraphic = self.attachAsset('errorBlock', {
anchorX: 0.5,
anchorY: 0.5
});
// Update method
self.update = function () {
self.lifespan--;
// Fade out near end of lifespan
if (self.lifespan < 60) {
self.alpha = self.lifespan / 60;
}
// Rotate slowly
self.rotation += 0.01;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Properties
self.speed = 5;
self.health = 100;
self.maxHealth = 100;
self.score = 0;
self.lastX = 0;
self.lastY = 0;
self.isMoving = false;
self.lastDirection = 'right';
// Create player graphic
var playerGraphic = self.attachAsset('dataPacket', {
anchorX: 0.5,
anchorY: 0.5
});
// Set player appearance
playerGraphic.tint = 0xffffff; // White color
playerGraphic.scaleX = 1.3;
playerGraphic.scaleY = 1.3;
// Health bar background
var healthBarBg = self.attachAsset('stabilityBarBg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.3
});
healthBarBg.y = -60;
// Health bar
var healthBar = self.attachAsset('stabilityBar', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.2
});
healthBar.y = -60;
healthBar.x = -healthBarBg.width / 2;
// Set health level
self.setHealth = function (amount) {
self.health = Math.max(0, Math.min(self.maxHealth, amount));
var healthPercentage = self.health / self.maxHealth;
healthBar.scaleX = 1.5 * healthPercentage;
// Update bar color based on health
if (self.health <= 20) {
healthBar.tint = 0xe74c3c; // Critical - red
} else if (self.health <= 50) {
healthBar.tint = 0xf39c12; // Warning - orange
} else {
healthBar.tint = 0x27ae60; // Normal - green
}
};
// Move player in specified direction
self.move = function (dx, dy) {
self.isMoving = true;
// Determine direction
if (Math.abs(dx) > Math.abs(dy)) {
self.lastDirection = dx > 0 ? 'right' : 'left';
} else {
self.lastDirection = dy > 0 ? 'down' : 'up';
}
// Update position
self.lastX = self.x;
self.lastY = self.y;
self.x += dx * self.speed;
self.y += dy * self.speed;
// Keep player within game bounds
self.x = Math.max(50, Math.min(2048 - 50, self.x));
self.y = Math.max(150, Math.min(2732 - 150, self.y));
// Move weapon with player
if (playerWeapon) {
playerWeapon.x = self.x;
playerWeapon.y = self.y - 30;
}
};
// Collect packet
self.collectPacket = function (packet) {
// Visual feedback
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
// Increase score
self.score += 10;
// Heal player slightly
self.setHealth(self.health + 5);
return self.score;
};
// Take damage
self.takeDamage = function (amount) {
self.setHealth(self.health - amount);
// Visual feedback
LK.effects.flashObject(self, 0xff0000, 500);
return self.health;
};
// Update method
self.update = function () {
// Reset moving state each frame
self.isMoving = false;
// Pulse effect when health is low
if (self.health < 30) {
var pulseAmount = Math.sin(LK.ticks * 0.1) * 0.1 + 1;
playerGraphic.scale.set(pulseAmount * 1.3, pulseAmount * 1.3);
}
// Store last position for collision detection
self.lastX = self.x;
self.lastY = self.y;
};
// Initialize health
self.setHealth(self.health);
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Properties
self.type = 'slowTime'; // Default type
self.isBeingDragged = false;
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Create power-up graphic
var powerUpGraphic = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
// Power-up types and their colors
var TYPES = ['slowTime', 'clearPath', 'stabilityBoost'];
var TYPE_COLORS = [0xf39c12, 0x9b59b6, 0x3498db];
// Set type
self.setType = function (typeIndex) {
self.type = TYPES[typeIndex];
powerUpGraphic.tint = TYPE_COLORS[typeIndex];
};
// Down event for dragging
self.down = function (x, y, obj) {
self.isBeingDragged = true;
// Bring to front
if (self.parent) {
self.parent.removeChild(self);
self.parent.addChild(self);
}
};
// Update method
self.update = function () {
// Pulse effect
var pulseAmount = Math.sin(LK.ticks * 0.1) * 0.1 + 1;
self.scale.set(pulseAmount, pulseAmount);
// Track position
self.lastX = self.x;
self.lastY = self.y;
};
// Set random type
self.setType(Math.floor(Math.random() * TYPES.length));
return self;
});
var SemibotPlayer = Container.expand(function () {
var self = Container.call(this);
// Bot properties
self.active = true;
self.intelligence = 0.7; // 0-1 scale of bot intelligence
self.lastActionTime = 0;
self.actionDelay = 60; // Frames between actions
self.target = null;
// Create semibot graphic
var botGraphic = self.attachAsset('blueBot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
botGraphic.tint = 0x3498db; // Blue color for semibot
// Toggle bot activity
self.toggle = function () {
self.active = !self.active;
return self.active;
};
// Find optimal move - returns best packet and target node or villain to attack
self.findOptimalMove = function () {
// ALWAYS prioritize attacking the villain if player is visible
if (player && villain && villain.active) {
return {
attackVillain: true
};
}
// Fall back to normal packet matching if villain isn't present
if (!packets.length || !storageNodes.length) return null;
// Only act if we have a good match
var bestMatch = null;
var highestScore = -1;
for (var i = 0; i < packets.length; i++) {
var packet = packets[i];
// Skip packets being dragged by player
if (packet.isBeingDragged) continue;
for (var j = 0; j < storageNodes.length; j++) {
var node = storageNodes[j];
var score = 0;
// Color match is most important
if (packet.colorIndex === node.colorIndex) {
score += 100;
// Prioritize continuing chains
if (lastSuccessfulChain === packet.colorIndex) {
score += 50 * chainCount;
}
// Adjust for difficulty based on distance and position
score -= Math.abs(packet.x - node.x) * 0.05;
score -= packet.y * 0.1; // Prioritize packets farther down
// Apply intelligence factor - sometimes make suboptimal choices
if (Math.random() > self.intelligence) {
score *= Math.random() * 0.5;
}
if (score > highestScore) {
highestScore = score;
bestMatch = {
packet: packet,
node: node
};
}
}
}
}
return bestMatch;
};
// Chase and attack villain
self.chaseVillain = function () {
if (!villain || !villain.active) return false;
// Check if player is present - semibot only helps when player is around
if (!player) return false;
// Visual feedback - move semibot toward villain more quickly
tween(self, {
x: villain.x,
y: villain.y - 50
}, {
duration: 200,
// Faster movement
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Attack villain if close enough
if (self.intersects(villain)) {
// Deal increased damage to villain when helping player
villain.takeDamage(30);
// Flash semibot when attacking
LK.effects.flashObject(self, 0x00ffff, 300);
// If villain is defeated, prevent behavior from running again
if (villain.health <= 0) {
villain.active = false;
// Add visual celebration effect for player and semibot defeating villain
LK.effects.flashScreen(0x00ffff, 500);
}
}
}
});
return true;
};
// Move a packet toward a node
self.movePacketToNode = function (packet, node) {
if (!packet || packet.isBeingDragged) return false;
// Release current target if we have one
if (self.target && self.target.isBeingDragged) {
self.target.isBeingDragged = false;
}
// Set target packet as being dragged by bot
self.target = packet;
packet.isBeingDragged = true;
// Visual feedback - move semibot to packet first
tween(self, {
x: packet.x,
y: packet.y - 50
}, {
duration: 200,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Flash semibot
LK.effects.flashObject(self, 0x00ffff, 300);
}
});
// Move packet toward node with tweening for smooth movement
tween(packet, {
x: node.x,
y: node.y - 20
}, {
duration: 500,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Release packet when it reaches the node
packet.isBeingDragged = false;
self.target = null;
// Check for match at destination
for (var i = 0; i < storageNodes.length; i++) {
if (checkNodeMatch(packet, storageNodes[i])) {
break;
}
}
}
});
return true;
};
// Update method
self.update = function () {
if (!self.active || !gameRunning) return;
// Track position for collision detection
self.lastX = self.x;
self.lastY = self.y;
// Pulse effect
var pulseAmount = Math.sin(LK.ticks * 0.05) * 0.05 + 1;
self.scale.set(pulseAmount * 1.2, pulseAmount * 1.2);
// Only act periodically to allow player to also interact
if (LK.ticks - self.lastActionTime < self.actionDelay) return;
// Skip action randomly to make bot feel more human
if (Math.random() > 0.8) return;
// PRIORITY 1: Attack villain if player is visible and villain exists
if (player && villain && villain.active) {
// If player is visible and villain exists, attack villain
if (self.chaseVillain()) {
self.lastActionTime = LK.ticks;
// Visual feedback for semibot helping player
LK.effects.flashObject(self, 0x00ffff, 300);
return;
}
}
// Check if we need to handle power-ups when stability is low
if (stabilityBar.stability < 30 && powerUps.length > 0) {
for (var i = 0; i < powerUps.length; i++) {
var powerUp = powerUps[i];
if (!powerUp.isBeingDragged && powerUp.y > 300) {
// Move power-up to center to activate it
tween(powerUp, {
x: 2048 / 2,
y: 2732 / 2
}, {
duration: 300,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
activatePowerUp(powerUp);
var index = powerUps.indexOf(powerUp);
if (index !== -1) {
powerUps.splice(index, 1);
}
powerUp.destroy();
}
});
self.lastActionTime = LK.ticks;
return;
}
}
}
// Find and execute optimal move
var move = self.findOptimalMove();
if (move) {
if (move.attackVillain) {
// Chase and attack the villain
self.chaseVillain();
} else {
// Regular packet matching behavior
self.movePacketToNode(move.packet, move.node);
}
self.lastActionTime = LK.ticks;
}
};
return self;
});
// Down event for dragging
var StabilityBar = Container.expand(function () {
var self = Container.call(this);
// Properties
self.stability = 50; // Start at 50%
self.maxStability = 100;
self.warningLevel = 25;
self.criticalLevel = 10;
// Create background
var barBg = self.attachAsset('stabilityBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
// Create bar
var bar = self.attachAsset('stabilityBar', {
anchorX: 0.5,
anchorY: 1.0
});
// Position the bar
bar.y = barBg.height / 2;
// Set stability amount (0-100)
self.setStability = function (amount) {
self.stability = Math.max(0, Math.min(self.maxStability, amount));
// Update bar height
var heightPercentage = self.stability / self.maxStability;
bar.scaleY = heightPercentage;
// Update bar color based on stability
if (self.stability <= self.criticalLevel) {
bar.tint = 0xe74c3c; // Critical - red
} else if (self.stability <= self.warningLevel) {
bar.tint = 0xf39c12; // Warning - orange
} else {
bar.tint = 0x27ae60; // Normal - green
}
};
// Change stability
self.changeStability = function (delta) {
self.setStability(self.stability + delta);
return self.stability;
};
// Initialize
self.setStability(self.stability);
return self;
});
// Game variables
var StorageNode = Container.expand(function () {
var self = Container.call(this);
// Color constants
var COLORS = [0x3498db, 0xe74c3c, 0xf39c12, 0x2ecc71, 0x9b59b6];
var COLOR_NAMES = ['blue', 'red', 'orange', 'green', 'purple'];
// Properties
self.colorIndex = 0;
self.colorName = '';
self.lastPacketsStored = 0;
self.packetsStored = 0;
// Create node graphic
var nodeGraphic = self.attachAsset('storageNode', {
anchorX: 0.5,
anchorY: 0.5
});
// Set color
self.setColor = function (index) {
self.colorIndex = index;
self.colorName = COLOR_NAMES[index];
nodeGraphic.tint = COLORS[index];
};
// Handle packet storage
self.storePacket = function () {
self.packetsStored++;
// Visual feedback
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
};
// Update method
self.update = function () {
self.lastPacketsStored = self.packetsStored;
};
return self;
});
var TaxmanPlayer = Container.expand(function () {
var self = Container.call(this);
// Properties
self.active = false;
self.taxRate = 0.10; // 10% tax rate
self.taxCollected = 0;
self.collectionInterval = 120; // Collect every 2 seconds (120 frames)
self.lastCollectionTime = 0;
self.targetPacket = null;
self.speed = 3; // Movement speed when chasing player
self.lastX = 0;
self.lastY = 0;
self.chasingPlayer = false;
self.playerChaseCooldown = 0;
// Visual representation
var taxmanGraphic = self.attachAsset('taxman', {
anchorX: 0.5,
anchorY: 0.5
});
taxmanGraphic.tint = 0x990000; // Dark red color for taxman
taxmanGraphic.scaleX = 1.2;
taxmanGraphic.scaleY = 1.2;
// Toggle taxman activity
self.toggle = function () {
self.active = !self.active;
return self.active;
};
// Find a packet to tax
self.findPacketToTax = function () {
if (!packets.length) return null;
// Find the highest value packet (prefer packets being matched)
var bestPacket = null;
var highestValue = -1;
for (var i = 0; i < packets.length; i++) {
var packet = packets[i];
// Skip packets being dragged
if (packet.isBeingDragged) continue;
var value = 10; // Base value
// Prefer packets that are about to be matched
for (var j = 0; j < storageNodes.length; j++) {
var node = storageNodes[j];
if (packet.colorIndex === node.colorIndex) {
// Distance to node (closer = higher value)
var distance = Math.sqrt(Math.pow(packet.x - node.x, 2) + Math.pow(packet.y - node.y, 2));
if (distance < 300) {
value += 300 - distance;
}
}
}
if (value > highestValue) {
highestValue = value;
bestPacket = packet;
}
}
return bestPacket;
};
// Apply tax to a packet (reduces its value)
self.taxPacket = function (packet) {
if (!packet) return;
// Visual feedback
self.x = packet.x;
self.y = packet.y;
// Flash effect
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.7
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300
});
}
});
// Collect tax
self.taxCollected += 1;
// Reduce packet value visually (make it slightly transparent)
packet.alpha = 0.8;
// Update UI
if (taxmanStatusText) {
taxmanStatusText.setText('Taxman: ' + (self.active ? 'Active' : 'Paused') + ' ($' + self.taxCollected + ')');
}
};
// Audit the player (tax the player directly)
self.auditPlayer = function () {
if (!player) return;
// Visual feedback
tween(self, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0.8
}, {
duration: 400,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300
});
}
});
// Take some health from player as "tax"
player.takeDamage(5);
// Increase tax collected
self.taxCollected += 5;
// Reset chase cooldown
self.playerChaseCooldown = 300; // 5 seconds cooldown
self.chasingPlayer = false;
// Update UI
if (taxmanStatusText) {
taxmanStatusText.setText('Taxman: ' + (self.active ? 'Active' : 'Paused') + ' ($' + self.taxCollected + ')');
}
};
// Chase the player
self.chasePlayer = function () {
if (!player) return;
// Calculate direction to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If close enough to player, audit them
if (distance < 80) {
self.auditPlayer();
return;
}
// Normalize direction and move towards player
if (distance > 0) {
dx = dx / distance;
dy = dy / distance;
}
// Save last position
self.lastX = self.x;
self.lastY = self.y;
// Move towards player
self.x += dx * self.speed;
self.y += dy * self.speed;
// Keep within game bounds
self.x = Math.max(50, Math.min(2048 - 50, self.x));
self.y = Math.max(150, Math.min(2732 - 150, self.y));
};
// Update method
self.update = function () {
if (!self.active || !gameRunning) return;
// Track last position
self.lastX = self.x;
self.lastY = self.y;
// Decrease player chase cooldown if active
if (self.playerChaseCooldown > 0) {
self.playerChaseCooldown--;
}
// Decide whether to chase player or packets
if (!self.chasingPlayer && self.playerChaseCooldown <= 0 && player && Math.random() < 0.01) {
// 1% chance per frame to start chasing player when not on cooldown
self.chasingPlayer = true;
}
// Chase player if in that mode
if (self.chasingPlayer && player) {
self.chasePlayer();
return;
}
// Otherwise collect tax from packets
if (LK.ticks - self.lastCollectionTime < self.collectionInterval) return;
// Find and tax a packet
var packetToTax = self.findPacketToTax();
if (packetToTax) {
self.taxPacket(packetToTax);
self.lastCollectionTime = LK.ticks;
}
};
return self;
});
var VillainPlayer = Container.expand(function () {
var self = Container.call(this);
// Villain properties
self.active = true;
self.health = 100;
self.maxHealth = 100;
self.attackCooldown = 180; // 3 seconds at 60fps
self.currentCooldown = 0;
self.target = null;
self.targetingPlayer = true; // Alternates between targeting player and nodes
self.attackDamage = 15;
self.speed = 2;
self.lastX = 0;
self.lastY = 0;
// Create villain graphic
var villainGraphic = self.attachAsset('dataPacket', {
anchorX: 0.5,
anchorY: 0.5
});
villainGraphic.tint = 0x800080; // Purple color for villain
villainGraphic.scaleX = 1.4;
villainGraphic.scaleY = 1.4;
// Health bar background
var healthBarBg = self.attachAsset('stabilityBarBg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.2
});
healthBarBg.y = -50;
// Health bar
var healthBar = self.attachAsset('stabilityBar', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.15
});
healthBar.y = -50;
healthBar.x = -healthBarBg.width / 2;
// Set health level
self.setHealth = function (amount) {
self.health = Math.max(0, Math.min(self.maxHealth, amount));
var healthPercentage = self.health / self.maxHealth;
healthBar.scaleX = 1.2 * healthPercentage;
// Update bar color based on health
if (self.health <= 20) {
healthBar.tint = 0xe74c3c; // Critical - red
} else if (self.health <= 50) {
healthBar.tint = 0xf39c12; // Warning - orange
} else {
healthBar.tint = 0x800080; // Normal - purple
}
};
// Take damage
self.takeDamage = function (amount) {
self.setHealth(self.health - amount);
// Visual feedback
LK.effects.flashObject(self, 0xff0000, 300);
return self.health;
};
// Find target (player or storage node)
self.findTarget = function () {
// Target player if that's our current priority
if (self.targetingPlayer && player) {
self.target = player;
return;
}
// Otherwise target a random storage node
if (storageNodes.length > 0) {
self.target = storageNodes[Math.floor(Math.random() * storageNodes.length)];
}
};
// Attack target
self.attackTarget = function () {
self.currentCooldown = self.attackCooldown;
// If targeting player, reduce player health
if (self.target === player) {
player.takeDamage(self.attackDamage);
// Visual effect for attack
tween(self, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 200
});
}
});
}
// If targeting node, create error blocks around it
else if (storageNodes.indexOf(self.target) !== -1) {
// Create error blocks around the node
for (var i = 0; i < 3; i++) {
var angle = Math.random() * Math.PI * 2;
var distance = 80 + Math.random() * 40;
var x = self.target.x + Math.cos(angle) * distance;
var y = self.target.y + Math.sin(angle) * distance;
createErrorBlock(x, y);
}
// Reduce stability
stabilityBar.changeStability(-10);
// Visual effect for attack
tween(self, {
rotation: Math.PI * 2
}, {
duration: 500,
onFinish: function onFinish() {
self.rotation = 0;
}
});
}
// Switch targeting priority
self.targetingPlayer = !self.targetingPlayer;
};
// Move toward target
self.moveTowardTarget = function () {
if (!self.target) return;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If close enough, attack
if (distance < 100 && self.currentCooldown <= 0) {
self.attackTarget();
return;
}
// Normalize movement vector
if (distance > 0) {
dx = dx / distance;
dy = dy / distance;
}
// Move villain toward target
self.lastX = self.x;
self.lastY = self.y;
self.x += dx * self.speed;
self.y += dy * self.speed;
// Keep villain within game bounds
self.x = Math.max(100, Math.min(2048 - 100, self.x));
self.y = Math.max(200, Math.min(2732 - 200, self.y));
};
// Update method
self.update = function () {
if (!self.active || !gameRunning) return;
// Decrease attack cooldown
if (self.currentCooldown > 0) {
self.currentCooldown--;
}
// Find new target if we don't have one
if (!self.target || self.target === player && !player) {
self.findTarget();
}
// Move toward target
self.moveTowardTarget();
// Pulse effect when attacking
if (self.currentCooldown > self.attackCooldown - 30) {
var pulseAmount = 1 + 0.2 * Math.sin(LK.ticks * 0.2);
villainGraphic.scale.set(pulseAmount * 1.4, pulseAmount * 1.4);
} else {
villainGraphic.scale.set(1.4, 1.4);
}
// Track position for collision detection
self.lastX = self.x;
self.lastY = self.y;
};
// Initialize health
self.setHealth(self.health);
return self;
});
var Weapon = Container.expand(function () {
var self = Container.call(this);
// Properties
self.damage = 10;
self.cooldown = 30; // frames between shots
self.currentCooldown = 0;
self.range = 500;
self.lastX = 0;
self.lastY = 0;
// Create weapon graphic
var weaponGraphic = self.attachAsset('dataPacket', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
weaponGraphic.tint = 0x00ffff; // Cyan color for weapon
// Update method
self.update = function () {
// Update cooldown
if (self.currentCooldown > 0) {
self.currentCooldown--;
}
// Track position for collision detection
self.lastX = self.x;
self.lastY = self.y;
};
// Fire method
self.fire = function (targetX, targetY) {
if (self.currentCooldown > 0) return null;
// Reset cooldown
self.currentCooldown = self.cooldown;
// Create bullet
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
// Calculate direction to target
var dx = targetX - self.x;
var dy = targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Normalize direction
if (dist > 0) {
dx = dx / dist;
dy = dy / dist;
}
// Set bullet properties
bullet.speedX = dx * 10;
bullet.speedY = dy * 10;
bullet.damage = self.damage;
bullet.range = self.range;
// Visual feedback
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
return bullet;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Tween library for animations
// Game assets
// Game variables
var packetSpawnInterval = 3 * 60; // Spawn every 3 seconds at 60fps
var nextPacketSpawn = packetSpawnInterval;
var stabilityDecayRate = 0.05; // Stability decay per tick
var packets = [];
var storageNodes = [];
var errorBlocks = [];
var powerUps = [];
var level = 1;
var score = 0;
var player;
var playerHealthText;
var stabilityBar;
var scoreText;
var levelText;
var botStatusText;
var draggedObject = null;
var gameRunning = true;
var lastSuccessfulChain = 0;
var chainCount = 0;
var semibot; // AI assistant player
var taxman; // Taxman player
var villain; // Villain player
var playerWeapon; // Player's weapon
var bullets = []; // Bullets in the game
var targetX = 0; // Target X for weapon
var targetY = 0; // Target Y for weapon
// UI text setup
scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 50;
scoreText.y = 50;
LK.gui.topLeft.addChild(scoreText);
// Add message to inform player they can only shoot villains
var shootInfoText = new Text2('You can only shoot villains!', {
size: 50,
fill: 0x00FFFF
});
shootInfoText.anchor.set(0.5, 0);
shootInfoText.x = 0;
shootInfoText.y = 540;
LK.gui.top.addChild(shootInfoText);
levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(1, 0);
levelText.x = -50;
levelText.y = 50;
LK.gui.topRight.addChild(levelText);
// Bot status text
botStatusText = new Text2('Bot: Active', {
size: 60,
fill: 0x00FF00
});
botStatusText.anchor.set(0.5, 0);
botStatusText.x = 0;
botStatusText.y = 50;
LK.gui.top.addChild(botStatusText);
// Taxman status text
var taxmanStatusText = new Text2('Taxman: Paused ($0)', {
size: 60,
fill: 0xFF0000
});
taxmanStatusText.anchor.set(0.5, 0);
taxmanStatusText.x = 0;
taxmanStatusText.y = 230;
LK.gui.top.addChild(taxmanStatusText);
// Create toggle button
var toggleBotBtn = new Text2('Toggle Bot', {
size: 60,
fill: 0xFFFFFF
});
toggleBotBtn.anchor.set(0.5, 0);
toggleBotBtn.x = 0;
toggleBotBtn.y = 120;
toggleBotBtn.interactive = true;
toggleBotBtn.hitArea = new Rectangle(-150, 0, 300, 60);
// Toggle bot event
toggleBotBtn.down = function () {
if (semibot) {
var isActive = semibot.toggle();
botStatusText.setText('Bot: ' + (isActive ? 'Active' : 'Paused'));
botStatusText.style.fill = isActive ? 0x00FF00 : 0xFF0000;
}
};
LK.gui.top.addChild(toggleBotBtn);
// Create toggle taxman button
var toggleTaxmanBtn = new Text2('Toggle Taxman', {
size: 60,
fill: 0xFFFFFF
});
toggleTaxmanBtn.anchor.set(0.5, 0);
toggleTaxmanBtn.x = 0;
toggleTaxmanBtn.y = 300;
toggleTaxmanBtn.interactive = true;
toggleTaxmanBtn.hitArea = new Rectangle(-150, 0, 300, 60);
// Toggle taxman event
toggleTaxmanBtn.down = function () {
if (taxman) {
var isActive = taxman.toggle();
taxmanStatusText.setText('Taxman: ' + (isActive ? 'Active' : 'Paused') + ' ($' + taxman.taxCollected + ')');
taxmanStatusText.style.fill = isActive ? 0x00FF00 : 0xFF0000;
}
};
LK.gui.top.addChild(toggleTaxmanBtn);
// Player health text
playerHealthText = new Text2('Health: 100%', {
size: 60,
fill: 0x00FF00
});
playerHealthText.anchor.set(0, 1);
playerHealthText.x = 50;
playerHealthText.y = -50;
LK.gui.bottomLeft.addChild(playerHealthText);
// Initialize stability bar
stabilityBar = new StabilityBar();
stabilityBar.x = 80;
stabilityBar.y = 2732 / 2;
game.addChild(stabilityBar);
// Create storage nodes
function createStorageNodes() {
// Clear existing nodes
storageNodes.forEach(function (node) {
node.destroy();
});
storageNodes = [];
var numNodes = Math.min(5, 2 + Math.floor(level / 2)); // Increase nodes with level
var spacing = 2048 / (numNodes + 1);
for (var i = 0; i < numNodes; i++) {
var node = new StorageNode();
node.setColor(i % 5);
node.x = spacing * (i + 1);
node.y = 2732 - 300;
game.addChild(node);
storageNodes.push(node);
}
}
// Create a new data packet
function createDataPacket() {
var packet = new DataPacket();
packet.x = Math.random() * (2048 - 200) + 100;
packet.y = -100;
game.addChild(packet);
packets.push(packet);
// Random vertical speed based on level
packet.speedY = 1 + Math.random() * level * 0.5;
return packet;
}
// Create power-up
function createPowerUp() {
var powerUp = new PowerUp();
powerUp.x = Math.random() * (2048 - 200) + 100;
powerUp.y = -100;
game.addChild(powerUp);
powerUps.push(powerUp);
// Set vertical speed
powerUp.speedY = 1 + Math.random() * 0.5;
return powerUp;
}
// Create error block obstacle
function createErrorBlock(x, y) {
var block = new ErrorBlock();
block.x = x;
block.y = y;
game.addChild(block);
errorBlocks.push(block);
return block;
}
// Handle power-up effect
function activatePowerUp(powerUp) {
LK.getSound('powerup').play();
switch (powerUp.type) {
case 'slowTime':
// Slow down all packets
packets.forEach(function (packet) {
packet.speedY *= 0.5;
});
break;
case 'clearPath':
// Remove all error blocks
errorBlocks.forEach(function (block) {
block.destroy();
});
errorBlocks = [];
break;
case 'stabilityBoost':
// Boost stability
stabilityBar.changeStability(25);
break;
}
// Visual feedback
LK.effects.flashScreen(0xf39c12, 500);
}
// Check if a packet matches a storage node
function checkNodeMatch(packet, node) {
if (packet.colorIndex === node.colorIndex && packet.intersects(node)) {
// Match found
LK.getSound('match').play();
// Update score based on chain
if (lastSuccessfulChain === packet.colorIndex) {
chainCount++;
score += 10 * chainCount;
} else {
chainCount = 1;
score += 10;
}
// Apply tax reduction if packet was taxed (indicated by alpha < 1)
if (packet.alpha < 1 && taxman) {
// Reduce score by taxman's rate
var taxAmount = Math.floor(score * taxman.taxRate);
score -= taxAmount;
taxman.taxCollected += taxAmount;
// Update taxman display
if (taxmanStatusText) {
taxmanStatusText.setText('Taxman: ' + (taxman.active ? 'Active' : 'Paused') + ' ($' + taxman.taxCollected + ')');
}
}
lastSuccessfulChain = packet.colorIndex;
// Update score display
LK.setScore(score);
scoreText.setText('Score: ' + score);
// Increase stability
stabilityBar.changeStability(5);
// Visual feedback
node.storePacket();
// Remove packet
var index = packets.indexOf(packet);
if (index !== -1) {
packets.splice(index, 1);
}
packet.destroy();
// Level up check
if (score >= level * 100) {
levelUp();
}
return true;
}
return false;
}
// Handle level up
function levelUp() {
level++;
levelText.setText('Level: ' + level);
// Increase difficulty
packetSpawnInterval = Math.max(60, packetSpawnInterval - 15);
stabilityDecayRate += 0.01;
// Create new node layout
createStorageNodes();
// Visual feedback
LK.effects.flashScreen(0x2ecc71, 500);
}
// Initialize semibot player
if (!semibot) {
semibot = new SemibotPlayer();
semibot.x = 2048 / 4; // Position at 1/4 of screen width
semibot.y = 400; // Position near top
}
game.addChild(semibot);
// The semibot is now persistent across game resets
// Initialize taxman player
taxman = new TaxmanPlayer();
taxman.x = 100;
taxman.y = 300;
game.addChild(taxman);
// Initialize game
createStorageNodes();
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// Initialize player weapon
playerWeapon = new Weapon();
playerWeapon.x = player.x;
playerWeapon.y = player.y - 30;
game.addChild(playerWeapon);
// Initialize villain
villain = new VillainPlayer();
villain.x = 2048 / 2;
villain.y = 300;
game.addChild(villain);
// Villain status text
var villainStatusText = new Text2('Villain: Active', {
size: 60,
fill: 0x800080
});
villainStatusText.anchor.set(0.5, 0);
villainStatusText.x = 0;
villainStatusText.y = 400;
LK.gui.top.addChild(villainStatusText);
// Create toggle villain button
var toggleVillainBtn = new Text2('Toggle Villain', {
size: 60,
fill: 0xFFFFFF
});
toggleVillainBtn.anchor.set(0.5, 0);
toggleVillainBtn.x = 0;
toggleVillainBtn.y = 470;
toggleVillainBtn.interactive = true;
toggleVillainBtn.hitArea = new Rectangle(-150, 0, 300, 60);
// Toggle villain event
toggleVillainBtn.down = function () {
if (villain) {
villain.active = !villain.active;
villainStatusText.setText('Villain: ' + (villain.active ? 'Active' : 'Paused'));
villainStatusText.style.fill = villain.active ? 0x800080 : 0xFF0000;
}
};
LK.gui.top.addChild(toggleVillainBtn);
// Game touch/mouse events
game.down = function (x, y, obj) {
// Check if we're clicking on a packet or power-up
var foundObject = false;
// Check packets first (in reverse to get top-most)
for (var i = packets.length - 1; i >= 0; i--) {
var packet = packets[i];
if (packet.getBounds().contains(x, y)) {
draggedObject = packet;
packet.down(x, y, obj);
foundObject = true;
break;
}
}
// Check power-ups if no packet was found
if (!foundObject) {
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
if (powerUp.getBounds().contains(x, y)) {
draggedObject = powerUp;
powerUp.down(x, y, obj);
foundObject = true;
break;
}
}
}
};
game.move = function (x, y, obj) {
if (draggedObject && draggedObject.isBeingDragged) {
draggedObject.x = x;
draggedObject.y = y;
}
// If player exists and no object is being dragged, move player toward touch position
if (player && !draggedObject) {
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only move if touch is not too close to player
if (distance > 30) {
// Normalize and move
dx = dx / distance;
dy = dy / distance;
player.move(dx, dy);
}
// Set target for weapon aiming
targetX = x;
targetY = y;
// Fire weapon if cooldown allows
if (playerWeapon && playerWeapon.currentCooldown <= 0) {
var bullet = playerWeapon.fire(targetX, targetY);
if (bullet) {
game.addChild(bullet);
bullets.push(bullet);
}
}
}
};
game.up = function (x, y, obj) {
if (draggedObject) {
// Check for matches with storage nodes
if (draggedObject instanceof DataPacket) {
var matched = false;
for (var i = 0; i < storageNodes.length; i++) {
if (checkNodeMatch(draggedObject, storageNodes[i])) {
matched = true;
break;
}
}
// Create error block if no match
if (!matched) {
LK.getSound('error').play();
createErrorBlock(draggedObject.x, draggedObject.y);
stabilityBar.changeStability(-10);
// Remove the mismatched packet
var index = packets.indexOf(draggedObject);
if (index !== -1) {
packets.splice(index, 1);
}
draggedObject.destroy();
}
}
// Check if it's a power-up
else if (draggedObject instanceof PowerUp) {
activatePowerUp(draggedObject);
// Remove the used power-up
var index = powerUps.indexOf(draggedObject);
if (index !== -1) {
powerUps.splice(index, 1);
}
draggedObject.destroy();
}
draggedObject = null;
}
};
// Main game update loop
game.update = function () {
if (!gameRunning) return;
// Update player
if (player) {
player.update();
// Update player health text
playerHealthText.setText('Health: ' + player.health + '%');
// Ensure style object exists before setting fill property
if (!playerHealthText.style) playerHealthText.style = {};
playerHealthText.style.fill = player.health <= 20 ? 0xe74c3c : player.health <= 50 ? 0xf39c12 : 0x00FF00;
// Check collisions with packets
for (var i = packets.length - 1; i >= 0; i--) {
if (player.intersects(packets[i]) && !packets[i].isBeingDragged) {
if (packets[i].colorIndex === 0) {
// Blue packets are good for player
player.collectPacket(packets[i]);
packets.splice(i, 1);
}
}
}
// Check collisions with error blocks
for (var i = errorBlocks.length - 1; i >= 0; i--) {
if (player.intersects(errorBlocks[i])) {
player.takeDamage(0.2); // Slow damage over time
}
}
// Game over if player dies
if (player.health <= 0 && gameRunning) {
gameRunning = false;
LK.effects.flashScreen(0xe74c3c, 1000);
LK.showGameOver();
}
}
// Update player weapon
if (playerWeapon) {
playerWeapon.update();
}
// Update semibot if active
if (semibot) {
semibot.update();
}
// Update taxman if active
if (taxman) {
taxman.update();
// Check for player-taxman collisions when not currently auditing
if (player && player.intersects(taxman) && taxman.active && taxman.playerChaseCooldown <= 0) {
taxman.auditPlayer();
// Add knockback effect when colliding
var dx = player.x - taxman.x;
var dy = player.y - taxman.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
dx = dx / dist;
dy = dy / dist;
tween(player, {
x: player.x + dx * 40,
y: player.y + dy * 40
}, {
duration: 200
});
}
}
}
// Update villain if active
if (villain) {
villain.update();
// Check for player-villain collisions
if (player && player.intersects(villain) && villain.currentCooldown <= 0) {
// Damage player on collision
player.takeDamage(5);
villain.currentCooldown = 60; // Set a cooldown
// Knockback effect
var dx = player.x - villain.x;
var dy = player.y - villain.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
dx = dx / dist;
dy = dy / dist;
// Apply knockback to both
tween(player, {
x: player.x + dx * 50,
y: player.y + dy * 50
}, {
duration: 200
});
tween(villain, {
x: villain.x - dx * 30,
y: villain.y - dy * 30
}, {
duration: 200
});
}
}
// Check for packet-villain collisions
for (var i = packets.length - 1; i >= 0; i--) {
if (packets[i].intersects(villain)) {
// Villain steals packets
packets[i].destroy();
packets.splice(i, 1);
// Villain gains health
villain.setHealth(villain.health + 5);
// Visual feedback
tween(villain, {
scaleX: 1.6,
scaleY: 1.6
}, {
duration: 200,
onFinish: function onFinish() {
tween(villain, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 200
});
}
});
}
}
}
// Update all game objects
for (var i = packets.length - 1; i >= 0; i--) {
var packet = packets[i];
packet.update();
// Move non-dragged packets down
if (!packet.isBeingDragged) {
packet.y += packet.speedY;
}
// Check if packet is out of bounds
if (packet.y > 2732 + 100) {
// Missed packet - penalty
stabilityBar.changeStability(-15);
LK.getSound('error').play();
// Remove packet
packets.splice(i, 1);
packet.destroy();
continue;
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.update();
// Check if bullet is out of bounds or exceeded range
if (bullet.x < -50 || bullet.x > 2048 + 50 || bullet.y < -50 || bullet.y > 2732 + 50 || bullet.distanceTraveled > bullet.range) {
bullets.splice(i, 1);
bullet.destroy();
continue;
}
// Check bullet collisions with villain
if (villain && bullet.intersects(villain)) {
// Damage villain
villain.takeDamage(bullet.damage);
// Remove bullet
bullets.splice(i, 1);
bullet.destroy();
// Increase score
score += 5;
LK.setScore(score);
scoreText.setText('Score: ' + score);
continue;
}
// We've removed the code that checked for bullet collisions with error blocks
// as player can only shoot villains now
}
// Update power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
// Move non-dragged power-ups down
if (!powerUp.isBeingDragged) {
powerUp.y += powerUp.speedY;
}
// Remove if out of bounds
if (powerUp.y > 2732 + 100) {
powerUps.splice(i, 1);
powerUp.destroy();
}
}
// Update error blocks
for (var i = errorBlocks.length - 1; i >= 0; i--) {
var block = errorBlocks[i];
block.update();
// Remove expired blocks
if (block.lifespan <= 0) {
errorBlocks.splice(i, 1);
block.destroy();
}
}
// Update storage nodes
for (var i = 0; i < storageNodes.length; i++) {
storageNodes[i].update();
}
// Spawn new packets
nextPacketSpawn--;
if (nextPacketSpawn <= 0) {
createDataPacket();
nextPacketSpawn = packetSpawnInterval;
// Occasionally spawn power-ups (5% chance)
if (Math.random() < 0.05) {
createPowerUp();
}
}
// Decrease stability over time
stabilityBar.changeStability(-stabilityDecayRate);
// Warning when stability is low
if (stabilityBar.stability <= stabilityBar.warningLevel && LK.ticks % 60 === 0) {
LK.getSound('warning').play();
}
// Check for game over
if (stabilityBar.stability <= 0) {
gameRunning = false;
LK.effects.flashScreen(0xe74c3c, 1000);
LK.showGameOver();
}
};
// Set background color to dark blue/grey for tech theme
game.setBackgroundColor(0x2c3e50);
Stroge node. In-Game asset. High contrast. No shadows
Error. In-Game asset. High contrast. No shadows
Power up. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Semibot. In-Game asset. High contrast. No shadows
(StabilityBarBg). In-Game asset. High contrast. No shadows
Creepy sad and happy emoji with black eyes and white people's. In-Game asset. High contrast. No shadows
Blue semibot. In-Game asset. High contrast. No shadows