/****
* 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