/**** 
* 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);
:quality(85)/https://cdn.frvr.ai/681ed407aaaa498fd45ec0af.png%3F3) 
 Stroge node. In-Game asset. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/681ed4cfaaaa498fd45ec0bd.png%3F3) 
 Error. In-Game asset. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/681ee05798634e35fa849ce8.png%3F3) 
 Power up. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
:quality(85)/https://cdn.frvr.ai/681ee3bb694685ea4cb299f6.png%3F3) 
 Semibot. In-Game asset. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/681f5d7d87d65e6bb188d38f.png%3F3) 
 (StabilityBarBg). In-Game asset. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/681f606f87d65e6bb188d410.png%3F3) 
 Creepy sad and happy emoji with black eyes and white people's. In-Game asset. High contrast. No shadows
:quality(85)/https://cdn.frvr.ai/681f6cbc6bdba198265e703f.png%3F3) 
 Blue semibot. In-Game asset. High contrast. No shadows