/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
points: 0,
damage: 1,
tools: [],
level: 1
});
/****
* Classes
****/
// Cosmetic item class
var Cosmetic = Container.expand(function (type, itemId) {
var self = Container.call(this);
self.itemType = type; // "hat" or "accessory"
self.itemId = itemId;
// Create the visual element
var graphic;
// Normal cosmetic behavior for all items
graphic = self.attachAsset(itemId, {
anchorX: 0.5,
anchorY: 0.5
});
// We'll set positions dynamically in the update method
// based on the stickman's head position
// Add wobble animation
self.update = function () {
// Find the stickman's head position (assuming the parent of this cosmetic is the stickman)
var stickmanParent = self.parent;
if (stickmanParent) {
// Find the head among the stickman's children
var head = null;
for (var i = 0; i < stickmanParent.children.length; i++) {
var child = stickmanParent.children[i];
// Check if this is the head (based on its properties)
if (child.originalY === -225) {
head = child;
break;
}
}
// If we found the head, follow its position and rotation
if (head) {
// Position cosmetics relative to head's center point
var offsetX = 0;
// Adjust Y offset based on item type and ID
var offsetY = 0;
// Special positioning for different hat types
if (self.itemType === "hat") {
if (self.itemId === "ninjaHeadband") {
// Position ninja headband in the middle of the head
offsetY = -10; // Centered on the head
} else {
offsetY = -50; // Default position for other hats
}
} else if (self.itemId === "sunglasses") {
offsetY = -10; // Higher position for sunglasses
} else {
offsetY = 20; // Default for other accessories
}
// Set position and rotation from the head's center
self.x = head.x;
self.y = head.y;
// Set rotation to match head
self.rotation = head.rotation;
// Apply the offset after rotation to make it rotate around the head
if (offsetY !== 0 || offsetX !== 0) {
// Calculate the offset position after rotation
var sinR = Math.sin(head.rotation);
var cosR = Math.cos(head.rotation);
// Apply rotated offset
self.x += cosR * offsetX - sinR * offsetY;
self.y += sinR * offsetX + cosR * offsetY;
}
// Add small wobble/rotation effect for natural movement
if (Math.random() < 0.1) {
// Random jolts occasionally - but relative to head's rotation
var joltRot = (Math.random() - 0.5) * 0.05;
var joltY = (Math.random() - 0.5) * 2;
tween(self, {
rotation: head.rotation + joltRot,
// Add jolt to head's rotation
y: self.y + joltY
}, {
duration: 300,
easing: tween.elasticOut
});
}
}
}
};
return self;
});
var Stickman = Container.expand(function () {
var self = Container.call(this);
// Initialize physics properties for ragdoll effect
var bodyParts = [];
var isRagdolling = false;
var restoreTimer = null;
// Cosmetic items
var currentHat = null;
var currentAccessory = null;
// Make isRagdolling accessible
self.isRagdolling = isRagdolling;
// Make startRagdoll accessible
self.startRagdoll = startRagdoll;
// Body parts with physics properties
var body = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 1
});
body.velocityX = 0;
body.velocityY = 0;
body.rotationalVelocity = 0;
body.mass = 10;
body.originalX = 0;
body.originalY = 0;
body.originalRotation = 0;
body.elasticity = 0.7;
body.friction = 0.9;
bodyParts.push(body);
var head = self.attachAsset('head', {
anchorX: 0.5,
anchorY: 0.5,
y: -225
});
head.velocityX = 0;
head.velocityY = 0;
head.rotationalVelocity = 0;
head.mass = 5;
head.originalX = 0;
head.originalY = -225;
head.originalRotation = 0;
head.elasticity = 0.8;
head.friction = 0.85;
bodyParts.push(head);
// Arms (using stickman asset but resized)
var leftArm = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.6,
x: -70,
y: -150,
rotation: 0
});
leftArm.velocityX = 0;
leftArm.velocityY = 0;
leftArm.rotationalVelocity = 0;
leftArm.mass = 3;
leftArm.originalX = -70;
leftArm.originalY = -150;
leftArm.originalRotation = 0;
leftArm.elasticity = 0.9;
leftArm.friction = 0.8;
bodyParts.push(leftArm);
var rightArm = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.6,
x: 70,
y: -150,
rotation: 0
});
rightArm.velocityX = 0;
rightArm.velocityY = 0;
rightArm.rotationalVelocity = 0;
rightArm.mass = 3;
rightArm.originalX = 70;
rightArm.originalY = -150;
rightArm.originalRotation = 0;
rightArm.elasticity = 0.9;
rightArm.friction = 0.8;
bodyParts.push(rightArm);
// Legs (using stickman asset but resized)
var leftLeg = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.7,
x: -40,
y: 50,
rotation: 0
});
leftLeg.velocityX = 0;
leftLeg.velocityY = 0;
leftLeg.rotationalVelocity = 0;
leftLeg.mass = 4;
leftLeg.originalX = -40;
leftLeg.originalY = 50;
leftLeg.originalRotation = 0;
leftLeg.elasticity = 0.6;
leftLeg.friction = 0.85;
bodyParts.push(leftLeg);
var rightLeg = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.7,
x: 40,
y: 50,
rotation: 0
});
rightLeg.velocityX = 0;
rightLeg.velocityY = 0;
rightLeg.rotationalVelocity = 0;
rightLeg.mass = 4;
rightLeg.originalX = 40;
rightLeg.originalY = 50;
rightLeg.originalRotation = 0;
rightLeg.elasticity = 0.6;
rightLeg.friction = 0.85;
bodyParts.push(rightLeg);
// Health properties
self.maxHealth = 100;
self.health = self.maxHealth;
self.level = storage.level || 1;
// Apply a force to a body part
function applyForce(part, forceX, forceY, torque) {
part.velocityX += forceX / part.mass;
part.velocityY += forceY / part.mass;
part.rotationalVelocity += torque / part.mass;
}
// Start ragdoll physics
function startRagdoll() {
isRagdolling = true;
// Clear any existing restore timer
if (restoreTimer) {
LK.clearTimeout(restoreTimer);
}
// Set a timer to restore the stickman to normal position
restoreTimer = LK.setTimeout(function () {
restorePosition();
}, 3000);
}
// Restore stickman to normal position
function restorePosition() {
isRagdolling = false;
// Reset velocities first to prevent further movement
bodyParts.forEach(function (part) {
part.velocityX = 0;
part.velocityY = 0;
part.rotationalVelocity = 0;
});
// Animate each body part back to its original position
bodyParts.forEach(function (part) {
// Stop any ongoing tweens
tween.stop(part, {
x: true,
y: true,
rotation: true
});
// Animate back to original position with staggered timing for more natural movement
var delay = Math.random() * 200; // Stagger the animations slightly
// Animate back to original position
tween(part, {
x: part.originalX,
y: part.originalY,
rotation: part.originalRotation
}, {
duration: 1200,
easing: tween.elasticOut,
delay: delay
});
});
// Only add cosmetics in defeat() method, not here when unragdolling
}
// Update physics simulation
self.update = function () {
if (isRagdolling) {
var gravity = 0.2; // Further reduced gravity for less falling apart
bodyParts.forEach(function (part) {
// Apply gravity - but less than before
part.velocityY += gravity;
// Add random jolts for more realism (every few frames randomly)
if (Math.random() < 0.1) {
// 10% chance each frame for a random jolt
var joltX = (Math.random() - 0.5) * 2.0;
var joltY = (Math.random() - 0.5) * 1.5;
var joltRot = (Math.random() - 0.5) * 0.05;
part.velocityX += joltX;
part.velocityY += joltY;
part.rotationalVelocity += joltRot;
}
// Always add a gentle force toward the center of the screen
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var towardsCenterX = centerX - stickman.x - part.x;
var towardsCenterY = centerY - stickman.y - part.y;
var distance = Math.sqrt(towardsCenterX * towardsCenterX + towardsCenterY * towardsCenterY);
// Only apply centering force if we're not too close to center already
if (distance > 50) {
// Normalize the direction vector
var dirX = towardsCenterX / distance;
var dirY = towardsCenterY / distance;
// Apply a constant stronger force toward center
var centeringForce = 2.0; // Dramatically increased to make sliding to middle super fast
part.velocityX += dirX * centeringForce;
part.velocityY += dirY * centeringForce;
}
// Apply velocities
part.x += part.velocityX;
part.y += part.velocityY;
part.rotation += part.rotationalVelocity;
// Apply more realistic friction system with surface friction
// Ground friction increases as parts get closer to the bottom of the screen
var groundY = 2732 - 200; // Position where ground friction is strongest
var distanceFromGround = Math.max(0, groundY - (stickman.y + part.y));
var groundFrictionFactor = Math.max(0, 1 - distanceFromGround / 1000); // Gradually increase friction near ground
var surfaceFriction = 0.92 + groundFrictionFactor * 0.08; // 0.92 to 1.0 based on ground proximity
// Apply friction with different coefficients for horizontal vs vertical movement
// Less friction on X to allow more sliding on surfaces
part.velocityX *= part.friction + 0.05 * (1 - groundFrictionFactor); // Less friction for X when sliding
part.velocityY *= part.friction * surfaceFriction; // More friction for Y when on surfaces
part.rotationalVelocity *= 0.95;
// Add damping to prevent excessive movement
if (Math.abs(part.velocityX) < 0.1) part.velocityX = 0;
if (Math.abs(part.velocityY) < 0.1) part.velocityY = 0;
if (Math.abs(part.rotationalVelocity) < 0.01) part.rotationalVelocity = 0;
// Constrain rotation
if (part.rotation > Math.PI * 2) {
part.rotation -= Math.PI * 2;
} else if (part.rotation < -Math.PI * 2) {
part.rotation += Math.PI * 2;
}
// Always keep limbs in their original positions by tweening them back
// This ensures limbs stay where they belong while allowing some movement
if (part === head) {
tween(part, {
x: part.originalX,
y: part.originalY - 100 // Make head hover higher by 100 pixels
}, {
duration: 300,
easing: tween.easeOut
});
}
// Body stays in original position with tweening
if (part === body) {
tween(part, {
x: part.originalX,
y: part.originalY,
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
// Arms stay connected to body with tweening but hover closer together and lower
if (part === leftArm || part === rightArm) {
tween(part, {
x: part === leftArm ? part.originalX - 10 : part.originalX + 10,
// Position arms closer together but lower
y: part.originalY - 30,
// Make arms hover lower by only 30 pixels instead of 130
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
// Legs stay connected to body with tweening but hover slightly lower
if (part === leftLeg || part === rightLeg) {
tween(part, {
x: part.originalX,
y: part.originalY + 20,
// Make legs hover lower by adding 20 pixels
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
});
} else {
// Bob animation when not ragdolling
// Use LK.ticks to create a smooth bobbing effect
var bobAmount = Math.sin(LK.ticks / 20) * 15; // Control bob height with multiplier
var armBobAmount = Math.sin(LK.ticks / 15) * 10; // Slightly different timing for arms
// Bob head up and down
tween(head, {
y: head.originalY + bobAmount * 0.8 // Head bobs slightly less than body
}, {
duration: 100,
easing: tween.easeOut
});
// Bob body up and down
tween(body, {
y: body.originalY + bobAmount
}, {
duration: 100,
easing: tween.easeOut
});
// Bob arms with slight delay compared to body for natural swinging effect
// Add arm swing using sin wave with different frequency than bob
var armSwingAmount = Math.sin(LK.ticks / 25) * 0.2; // Swinging rotation
tween(leftArm, {
x: leftArm.originalX - 10,
// Keep arms closer together by moving them further inward
y: leftArm.originalY - 30 + armBobAmount,
// Arms bob with slight offset, hovering lower now
rotation: armSwingAmount // Arms swing around slightly
}, {
duration: 100,
easing: tween.easeOut
});
tween(rightArm, {
x: rightArm.originalX + 10,
// Keep arms closer together by moving them further inward
y: rightArm.originalY - 30 + armBobAmount,
// Arms bob with slight offset, hovering lower now
rotation: -armSwingAmount // Arms swing in opposite direction
}, {
duration: 100,
easing: tween.easeOut
});
// Bob legs with opposite timing to body for natural movement, but lower
// Add leg swing using cos wave for offset from arm swing
var legSwingAmount = Math.cos(LK.ticks / 30) * 0.15; // Smaller swing for legs
tween(leftLeg, {
y: leftLeg.originalY + 50 - bobAmount * 0.5,
// Position legs lower by 50px and move opposite to body
rotation: legSwingAmount // Legs swing around slightly
}, {
duration: 100,
easing: tween.easeOut
});
tween(rightLeg, {
y: rightLeg.originalY + 50 - bobAmount * 0.5,
// Position legs lower by 50px and move opposite to body
rotation: -legSwingAmount // Legs swing in opposite direction
}, {
duration: 100,
easing: tween.easeOut
});
}
};
// Hit reaction
self.hit = function (damage) {
// If health is already zero, don't allow further hits
if (self.health <= 0) {
return false;
}
// Decrease health
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
self.defeat();
}
// Update health UI
updateHealthBar();
// Visual reaction - flash
LK.effects.flashObject(self, 0xFF0000, 300);
// Get random body part for hit location
var parts = [body, head, leftArm, rightArm, leftLeg, rightLeg];
var randomPart = parts[Math.floor(Math.random() * parts.length)];
// Create hit effect
var hitEffect = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: randomPart.x,
y: randomPart.y,
alpha: 0.7,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(hitEffect);
// Animate the hit effect
tween(hitEffect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
hitEffect.destroy();
}
});
// Start ragdoll physics if not already in that state
startRagdoll();
// Apply impulse to the hit body part and make entire stickman slide
var randomDirection = Math.random() * Math.PI * 2; // Random angle in radians
var slideForce = damage * 2.0; // Reduced sliding force for less movement
var slideForceX = Math.cos(randomDirection) * slideForce;
var slideForceY = Math.sin(randomDirection) * slideForce;
// Apply sliding force to all body parts - but with more control
bodyParts.forEach(function (part) {
// Apply more balanced forces between X and Y for better overall movement
// Cap the maximum force to prevent excessive displacement
var maxForce = 30; // Reduced max force for lighter sliding
var forceFactor = 3.0; // Reduced force factor for gentler middle movement
var appliedForceX = Math.min(maxForce, slideForceX * forceFactor);
var appliedForceY = Math.min(maxForce, slideForceY * forceFactor);
// Apply the forces
part.velocityX += appliedForceX;
part.velocityY += appliedForceY;
});
// Apply additional impulse to the specific hit body part
// Apply more controlled force to avoid body parts flying apart
var hitAngle = Math.random() * Math.PI * 2;
var hitMagnitude = Math.min(damage * 1.5, 10); // Further reduced hit magnitude
var hitForceX = Math.cos(hitAngle) * hitMagnitude;
var hitForceY = Math.sin(hitAngle) * hitMagnitude;
var hitTorque = (Math.random() - 0.5) * damage * 0.1; // Significantly reduced torque
// Apply force with special effects based on the body part hit
if (randomPart === head) {
// Head hits are more impactful
applyForce(randomPart, hitForceX * 1.5, hitForceY * 1.5, hitTorque * 2);
// Also make the head wobble
tween(randomPart, {
rotation: randomPart.rotation + Math.PI * (Math.random() - 0.5)
}, {
duration: 500,
easing: tween.elasticOut
});
} else if (randomPart === leftArm || randomPart === rightArm) {
// Arms swing more
applyForce(randomPart, hitForceX, hitForceY, hitTorque * 3);
} else if (randomPart === leftLeg || randomPart === rightLeg) {
// Legs have more mass, less rotation
applyForce(randomPart, hitForceX * 0.8, hitForceY * 0.8, hitTorque * 0.5);
} else if (randomPart === body) {
// Body affects all parts
bodyParts.forEach(function (part) {
applyForce(part, hitForceX * 0.6, hitForceY * 0.6, hitTorque * 0.3);
});
}
// Play sound
LK.getSound('hit').play();
// No chance to get cosmetics on hit - only on defeat
return true;
};
// When stickman is defeated
self.defeat = function () {
// Massive ragdoll effect on defeat
startRagdoll();
// Apply explosive force to all body parts
bodyParts.forEach(function (part) {
var explosiveForceX = (Math.random() - 0.5) * 50;
var explosiveForceY = (Math.random() - 0.5) * 50;
var explosiveTorque = (Math.random() - 0.5) * 2;
applyForce(part, explosiveForceX, explosiveForceY, explosiveTorque);
});
// Make cosmetics fly off dramatically
if (currentHat) {
var hatX = currentHat.x;
var hatY = currentHat.y;
tween(currentHat, {
x: hatX + (Math.random() - 0.5) * 300,
y: hatY - Math.random() * 400,
rotation: Math.PI * (Math.random() * 4 - 2),
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (currentHat) {
currentHat.destroy();
currentHat = null;
}
}
});
}
if (currentAccessory) {
var accX = currentAccessory.x;
var accY = currentAccessory.y;
tween(currentAccessory, {
x: accX + (Math.random() - 0.5) * 300,
y: accY + Math.random() * 400,
rotation: Math.PI * (Math.random() * 4 - 2),
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (currentAccessory) {
currentAccessory.destroy();
currentAccessory = null;
}
}
});
}
// Create explosion effects around each body part
bodyParts.forEach(function (part) {
// Create multiple explosion particles for each body part
for (var i = 0; i < 3; i++) {
var offsetX = (Math.random() - 0.5) * 100;
var offsetY = (Math.random() - 0.5) * 100;
var explosion = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: part.x + offsetX,
y: part.y + offsetY,
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8,
tint: Math.random() > 0.5 ? 0xff0000 : 0xff6600
});
self.addChild(explosion);
// Animate explosion
tween(explosion, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
// Hide body parts temporarily (they'll reappear when respawned)
bodyParts.forEach(function (part) {
tween(part, {
alpha: 0
}, {
duration: 400
});
});
// Level up
self.level++;
storage.level = self.level;
// Reward player with 500 points on defeat
var reward = 500;
addPoints(reward);
// Health will remain the same after each kill (no increase)
// Show level up message
showLevelUpMessage(self.level, reward);
// Reset health with increased max health after a delay (reduced to 2 seconds)
LK.setTimeout(function () {
// Make parts visible again
bodyParts.forEach(function (part) {
part.alpha = 1;
});
// Increase max health by 25% each respawn
self.maxHealth = Math.floor(self.maxHealth * 1.25);
self.health = self.maxHealth;
updateHealthBar();
restorePosition();
// Play respawn sound
LK.getSound('respawn').play();
// Apply new random cosmetics on respawn
self.applyRandomCosmetic("hat");
self.applyRandomCosmetic("accessory");
}, 2000); // Changed from 3000 to 2000 for 2-second respawn
// Play death sound
LK.getSound('death').play();
};
// Make applyRandomCosmetic a public method so it can be accessed from outside the class
self.applyRandomCosmetic = function (cosmeticType) {
// Remove current cosmetic of this type if exists
if (cosmeticType === "hat" && currentHat) {
if (currentHat) {
currentHat.destroy();
currentHat = null;
}
} else if (cosmeticType === "accessory" && currentAccessory) {
if (currentAccessory) {
currentAccessory.destroy();
currentAccessory = null;
}
}
// Available cosmetic items
var hatOptions = ["cowboyHat", "topHat", "partyHat", "crown", "propellerHat", "pirateBandana", "ninjaHeadband", "mohawk"];
var accessoryOptions = ["sunglasses", "mustache", "beard"];
// Choose a random item
var options = cosmeticType === "hat" ? hatOptions : accessoryOptions;
var randomItem = options[Math.floor(Math.random() * options.length)];
// Create and attach the new cosmetic item
var newCosmetic = new Cosmetic(cosmeticType, randomItem);
// Normal cosmetic addition for all items
self.addChild(newCosmetic);
// Store reference to the new item
if (cosmeticType === "hat") {
currentHat = newCosmetic;
} else {
currentAccessory = newCosmetic;
}
// Add entrance animation
newCosmetic.scaleX = 0.1;
newCosmetic.scaleY = 0.1;
newCosmetic.alpha = 0;
tween(newCosmetic, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.elasticOut
});
// Play unlock sound
LK.getSound('unlock').play();
// Show message
var itemName = randomItem.charAt(0).toUpperCase() + randomItem.slice(1);
showMessage("New " + cosmeticType + ": " + itemName);
};
// Event handler when clicking on stickman
self.down = function (x, y, obj) {
// Convert coordinates to local space
var localX = x - self.x;
var localY = y - self.y;
// Find closest body part to the click point
var closestPart = body; // Default to body
var closestDistance = Number.MAX_VALUE;
bodyParts.forEach(function (part) {
var dx = part.x - localX;
var dy = part.y - localY;
var distance = dx * dx + dy * dy;
if (distance < closestDistance) {
closestDistance = distance;
closestPart = part;
}
});
// Use the class method instead of local function
// Now we use self.applyRandomCosmetic which is defined outside of this method
// Apply current tool damage
self.hit(currentDamage);
// Additional force to the specific body part that was clicked - reduced to make knockback gentler
var hitForceX = (Math.random() - 0.5) * currentDamage * 2;
var hitForceY = (Math.random() - 0.5) * currentDamage * 2;
var hitTorque = (Math.random() - 0.5) * currentDamage * 0.1;
applyForce(closestPart, hitForceX, hitForceY, hitTorque);
// Add points based on current damage
addPoints(currentDamage);
};
return self;
});
var Tool = Container.expand(function (toolData) {
var self = Container.call(this);
self.data = toolData;
var toolShape = self.attachAsset(toolData.id, {
anchorX: 0.5,
anchorY: 0.5
});
var nameText = new Text2(toolData.name, {
size: 24,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0);
nameText.y = 80;
self.addChild(nameText);
var damageText = new Text2("+" + toolData.damage + " DMG", {
size: 22,
fill: 0xFFFF00
});
damageText.anchor.set(0.5, 0);
damageText.y = 110;
self.addChild(damageText);
var priceText = new Text2(toolData.price + " PTS", {
size: 22,
fill: 0xFFFFFF
});
priceText.anchor.set(0.5, 0);
priceText.y = 140;
self.addChild(priceText);
self.down = function (x, y, obj) {
// Only interact with the shop when it's open
if (!isShopOpen) return;
if (points >= toolData.price && !isToolOwned(toolData.id)) {
// Purchase the tool
addPoints(-toolData.price);
unlockTool(toolData);
// Play unlock sound
LK.getSound('unlock').play();
// Show purchase confirmation
showMessage("You unlocked " + toolData.name + "!");
// Update shop
updateShop();
} else if (isToolOwned(toolData.id) && points >= toolData.price) {
// Pay for using the tool if already owned
addPoints(-toolData.price);
// Equip the tool
equipTool(toolData.id);
LK.getSound('unlock').play();
showMessage(toolData.name + " equipped for " + toolData.price + " points!");
// Update shop
updateShop();
} else {
// Show not enough points message
showMessage("Not enough points!");
}
};
return self;
});
var Weapon = Container.expand(function () {
var self = Container.call(this);
// Create weapon graphic based on current tool
var toolAsset = equippedTool ? equippedTool.id : 'fist';
var weaponGraphic = self.attachAsset(toolAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Weapon properties
self.velocityX = 0;
self.velocityY = 0;
self.speed = 15;
self.damage = 0;
self.target = null;
self.lastIntersecting = false;
// Update method to move weapon towards target
self.update = function () {
if (!self.target) return;
// Calculate direction to target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If weapon is close to target, hit it
if (distance < 50) {
if (self.target.health > 0) {
self.target.hit(self.damage);
createHitAnimation(self.x, self.y);
}
self.destroy();
return;
}
// Normalize direction vector
var dirX = dx / distance;
var dirY = dy / distance;
// Move towards target
self.x += dirX * self.speed;
self.y += dirY * self.speed;
// Rotate weapon to face movement direction
self.rotation = Math.atan2(dy, dx) + Math.PI / 4;
// Add spinning effect to weapons except for fist
if (toolAsset !== 'fist') {
// Add a continuous rotation on top of the directional rotation
self.rotation += LK.ticks * 0.2; // Controls spin speed
}
// Check intersection with target
var currentIntersecting = self.intersects(self.target);
if (!self.lastIntersecting && currentIntersecting) {
if (self.target.health > 0) {
self.target.hit(self.damage);
createHitAnimation(self.x, self.y);
// Increment score when weapon hits stickman
addPoints(1);
}
self.destroy();
}
self.lastIntersecting = currentIntersecting;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xEEEEEE // Light gray background for better stickman visibility
});
/****
* Game Code
****/
// Face accessory assets
// Hat assets
// Game state variables
// Headphones removed
if (!tween) {
console.error("Failed to import tween library");
// Create fallback implementation
tween = function tween(obj, props, options) {
if (options && options.onFinish) {
setTimeout(options.onFinish, options.duration || 1000);
}
// Apply props immediately as fallback
for (var key in props) {
if (props.hasOwnProperty(key)) {
obj[key] = props[key];
}
}
};
tween.easeOut = function (t) {
return t;
};
tween.easeIn = function (t) {
return t;
};
tween.elasticOut = function (t) {
return t;
};
tween.stop = function () {};
}
var points = storage.points || 0;
var baseDamage = storage.damage || 1;
var currentDamage = baseDamage;
var tools = storage.tools || [];
var equippedTool = null;
var isShopOpen = false;
var shopContainer = null;
var activeWeapons = [];
// Tool definitions
var availableTools = [{
id: 'fist',
name: 'Fist',
damage: 1,
price: 0,
color: 0xFFB380,
owned: true
}, {
id: 'stick',
name: 'Stick',
damage: 2,
price: 300,
color: 0x8B4513
}, {
id: 'bat',
name: 'Baseball Bat',
damage: 5,
price: 800,
color: 0x654321
}, {
id: 'hammer',
name: 'Hammer',
damage: 10,
price: 2500,
color: 0x808080
}, {
id: 'pan',
name: 'Frying Pan',
damage: 15,
price: 5000,
color: 0x404040
}, {
id: 'mallet',
name: 'Cartoon Mallet',
damage: 25,
price: 12000,
color: 0xFF0000
}, {
id: 'anvil',
name: 'Anvil',
damage: 50,
price: 25000,
color: 0x303030
}, {
id: 'piano',
name: 'Grand Piano',
damage: 100,
price: 50000,
color: 0x000000
}];
// Set up the stickman
var stickman = new Stickman();
stickman.x = 2048 / 2;
stickman.y = 2732 / 2;
// Make stickman bigger for better visibility
stickman.scale.set(1.5, 1.5);
game.addChild(stickman);
// Set up UI elements
var pointsText = new Text2("POINTS: 0", {
size: 70,
fill: 0x000000
});
pointsText.anchor.set(0.5, 0);
pointsText.y = 50;
LK.gui.top.addChild(pointsText);
var damageText = new Text2("DAMAGE: 1", {
size: 50,
fill: 0xFF0000
});
damageText.anchor.set(0.5, 0);
damageText.y = 130;
LK.gui.top.addChild(damageText);
// Level counter removed
// Health bar
var healthBarBg = LK.getAsset('stickman', {
anchorX: 0,
anchorY: 0.5,
width: 1000,
height: 60,
tint: 0x888888
});
healthBarBg.x = 2048 / 2 - 500;
healthBarBg.y = 300;
game.addChild(healthBarBg);
var healthBar = LK.getAsset('stickman', {
anchorX: 0,
anchorY: 0.5,
width: 1000,
height: 60,
tint: 0xFF0000
});
healthBar.x = 2048 / 2 - 500;
healthBar.y = 300;
game.addChild(healthBar);
var healthText = new Text2("100/100", {
size: 46,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthText.x = 2048 / 2;
healthText.y = 300;
game.addChild(healthText);
// Shop button
var shopButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
shopButton.x = 2048 - 150;
shopButton.y = 100;
game.addChild(shopButton);
var shopText = new Text2("SHOP", {
size: 60,
fill: 0xFFFFFF
});
shopText.anchor.set(0.5, 0.5);
shopText.x = 2048 - 150;
shopText.y = 100;
game.addChild(shopText);
// Message display
var messageText = new Text2("", {
size: 60,
fill: 0xFF0000
});
messageText.anchor.set(0.5, 0.5);
messageText.x = 2048 / 2;
messageText.y = 500;
messageText.alpha = 0;
game.addChild(messageText);
// Initialize the game
function init() {
// Set points to 0 when game starts
points = 0;
storage.points = 0;
// Reset weapons when game starts
currentDamage = baseDamage;
equippedTool = availableTools[0]; // Reset to fist
activeWeapons = [];
tools = []; // Reset tools array to empty so no weapons are purchased
storage.tools = []; // Update storage to persist the reset
// Set up initial values
updatePointsDisplay();
updateDamageDisplay();
updateHealthBar();
// Give stickman a random hat and/or accessory on start
if (Math.random() < 0.95) {
// 95% chance to start with a hat (increased from 70%)
stickman.applyRandomCosmetic("hat");
}
if (Math.random() < 0.9) {
// 90% chance to start with an accessory (increased from 50%)
stickman.applyRandomCosmetic("accessory");
}
// Preload all cosmetic assets to ensure they're available immediately
var allCosmetics = ["cowboyHat", "topHat", "partyHat", "crown", "propellerHat", "pirateBandana", "ninjaHeadband", "mohawk", "sunglasses", "mustache", "beard"];
allCosmetics.forEach(function (cosmeticId) {
// Create temporary assets to ensure they're loaded into memory
var tempAsset = LK.getAsset(cosmeticId, {
anchorX: 0.5,
anchorY: 0.5
});
// We don't need to add these to the game, just make sure they're loaded
});
// Mark tools as owned if in storage
tools.forEach(function (toolId) {
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolId) {
availableTools[i].owned = true;
}
}
});
// Equip the first owned tool
var ownedTools = availableTools.filter(function (tool) {
return tool.owned;
});
if (ownedTools.length > 0) {
equipTool(ownedTools[0].id);
}
// Set shop button interaction
shopButton.interactive = true;
shopButton.down = function () {
toggleShop();
};
// Play background music
LK.playMusic('gameMusic');
}
// Toggle shop visibility
function toggleShop() {
if (isShopOpen) {
closeShop();
} else {
openShop();
}
}
// Open the shop
function openShop() {
isShopOpen = true;
// Create shop container
shopContainer = new Container();
shopContainer.x = 2048 / 2;
shopContainer.y = 2732 / 2;
game.addChild(shopContainer);
// Shop background
var shopBg = LK.getAsset('stickman', {
anchorX: 0.5,
anchorY: 0.5,
width: 1600,
height: 1800,
tint: 0x333333,
alpha: 0.9
});
shopContainer.addChild(shopBg);
// Shop title
var shopTitle = new Text2("TOOL SHOP", {
size: 80,
fill: 0xFFFFFF
});
shopTitle.anchor.set(0.5, 0);
shopTitle.y = -800;
shopContainer.addChild(shopTitle);
// Close button
var closeButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 700,
y: -800,
tint: 0xFF0000
});
shopContainer.addChild(closeButton);
var closeText = new Text2("CLOSE", {
size: 40,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 700;
closeText.y = -800;
shopContainer.addChild(closeText);
closeButton.interactive = true;
closeButton.down = function () {
closeShop();
};
// Layout tools in a grid
var toolsPerRow = 3;
var toolWidth = 300;
var toolHeight = 350;
var startX = -(toolsPerRow - 1) * toolWidth / 2;
var startY = -600;
availableTools.forEach(function (toolData, index) {
var row = Math.floor(index / toolsPerRow);
var col = index % toolsPerRow;
var toolItem = new Tool(toolData);
toolItem.x = startX + col * toolWidth;
toolItem.y = startY + row * toolHeight;
shopContainer.addChild(toolItem);
// Show "OWNED" or "EQUIPPED" if applicable
if (toolData.owned) {
var statusText = new Text2(isToolEquipped(toolData.id) ? "EQUIPPED" : "OWNED", {
size: 24,
fill: isToolEquipped(toolData.id) ? "#00FF00" : "#FFFFFF"
});
statusText.anchor.set(0.5, 0);
statusText.y = 170;
toolItem.addChild(statusText);
}
});
// Animate shop opening
shopContainer.alpha = 0;
shopContainer.scaleX = 0.8;
shopContainer.scaleY = 0.8;
tween(shopContainer, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Close the shop
function closeShop() {
if (!isShopOpen || !shopContainer) return;
tween(shopContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (shopContainer) {
shopContainer.destroy();
shopContainer = null;
}
isShopOpen = false;
}
});
}
// Check if a tool is owned
function isToolOwned(toolId) {
return tools.indexOf(toolId) !== -1;
}
// Check if a tool is currently equipped
function isToolEquipped(toolId) {
return equippedTool && equippedTool.id === toolId;
}
// Unlock a new tool
function unlockTool(toolData) {
if (!isToolOwned(toolData.id)) {
tools.push(toolData.id);
storage.tools = tools;
// Mark as owned
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolData.id) {
availableTools[i].owned = true;
break;
}
}
// Equip the newly unlocked tool
equipTool(toolData.id);
}
}
// Equip a tool
function equipTool(toolId) {
if (!isToolOwned(toolId)) return;
// Find the tool data
var toolData = null;
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolId) {
toolData = availableTools[i];
break;
}
}
if (toolData) {
equippedTool = toolData;
currentDamage = baseDamage + toolData.damage;
updateDamageDisplay();
}
}
// Update the shop display
function updateShop() {
if (isShopOpen) {
closeShop();
openShop();
}
}
// Add points to the player's total
function addPoints(amount) {
points += amount;
storage.points = points;
updatePointsDisplay();
}
// Update the points display
function updatePointsDisplay() {
pointsText.setText("POINTS: " + points);
}
// Update the damage display
function updateDamageDisplay() {
damageText.setText("DAMAGE: " + currentDamage);
}
// Level display function removed
// Update the health bar
function updateHealthBar() {
var percentage = stickman.health / stickman.maxHealth;
healthBar.width = 1000 * percentage;
healthText.setText(stickman.health + "/" + stickman.maxHealth);
}
// Show a message temporarily
function showMessage(text) {
messageText.setText(text);
messageText.alpha = 1;
// Clear any existing tweens
tween.stop(messageText, {
alpha: true
});
// Animate message
tween(messageText, {
alpha: 0
}, {
duration: 2000
});
}
// Show level up message
function showLevelUpMessage(level, reward) {
var levelUpText = new Text2("LEVEL UP!\n+" + reward + " Points", {
size: 80,
fill: 0xFFFF00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.x = 2048 / 2;
levelUpText.y = 2732 / 2 - 400;
levelUpText.alpha = 0;
game.addChild(levelUpText);
// Animate the level up text
tween(levelUpText, {
alpha: 1,
y: levelUpText.y - 100
}, {
duration: 500,
onFinish: function onFinish() {
tween(levelUpText, {
alpha: 0
}, {
duration: 1000,
delay: 1000,
onFinish: function onFinish() {
levelUpText.destroy();
}
});
}
});
}
// Create hit animation
function createHitAnimation(x, y) {
var hitEffect = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.7,
scaleX: 0.5,
scaleY: 0.5
});
game.addChild(hitEffect);
tween(hitEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
hitEffect.destroy();
}
});
}
// Track dragging state
var isDragging = false;
var draggedBodyPart = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Mouse/touch handlers for the game
game.down = function (x, y, obj) {
// If the shop is open, don't register clicks on the game
if (isShopOpen) return;
// If stickman is dead, don't allow hitting
if (stickman.health <= 0) {
return;
}
// No longer add points on each click
// Spawn weapon at tap location and make it fly towards stickman
var weapon = new Weapon();
weapon.x = x;
weapon.y = y;
weapon.damage = currentDamage;
weapon.target = stickman;
// Add to game
game.addChild(weapon);
// Create a hit animation at the tap point
createHitAnimation(x, y);
// Play click sound for tapping the screen
LK.getSound('click').play();
};
// Handle move events for dragging
game.move = function (x, y, obj) {
// If stickman is dead, don't allow dragging
if (stickman.health <= 0) {
isDragging = false;
draggedBodyPart = null;
return;
}
if (isDragging && draggedBodyPart && !isShopOpen) {
var localX = x - stickman.x;
var localY = y - stickman.y;
// Update dragged part position
draggedBodyPart.x = localX - dragOffsetX;
draggedBodyPart.y = localY - dragOffsetY;
// Reset velocity for smooth dragging
draggedBodyPart.velocityX = 0;
draggedBodyPart.velocityY = 0;
}
};
// Handle up events for dragging
game.up = function (x, y, obj) {
if (isDragging && draggedBodyPart && !isShopOpen) {
// On release, give the part some velocity based on movement
var releaseVelocityX = (Math.random() - 0.5) * 10;
var releaseVelocityY = (Math.random() - 0.5) * 10;
draggedBodyPart.velocityX = releaseVelocityX;
draggedBodyPart.velocityY = releaseVelocityY;
isDragging = false;
draggedBodyPart = null;
}
};
// Update function called every tick
game.update = function () {
// Clean up destroyed weapons
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child instanceof Weapon) {
if (!child.parent) {
game.children.splice(i, 1);
i--;
}
}
}
};
// Initialize the game
init(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
points: 0,
damage: 1,
tools: [],
level: 1
});
/****
* Classes
****/
// Cosmetic item class
var Cosmetic = Container.expand(function (type, itemId) {
var self = Container.call(this);
self.itemType = type; // "hat" or "accessory"
self.itemId = itemId;
// Create the visual element
var graphic;
// Normal cosmetic behavior for all items
graphic = self.attachAsset(itemId, {
anchorX: 0.5,
anchorY: 0.5
});
// We'll set positions dynamically in the update method
// based on the stickman's head position
// Add wobble animation
self.update = function () {
// Find the stickman's head position (assuming the parent of this cosmetic is the stickman)
var stickmanParent = self.parent;
if (stickmanParent) {
// Find the head among the stickman's children
var head = null;
for (var i = 0; i < stickmanParent.children.length; i++) {
var child = stickmanParent.children[i];
// Check if this is the head (based on its properties)
if (child.originalY === -225) {
head = child;
break;
}
}
// If we found the head, follow its position and rotation
if (head) {
// Position cosmetics relative to head's center point
var offsetX = 0;
// Adjust Y offset based on item type and ID
var offsetY = 0;
// Special positioning for different hat types
if (self.itemType === "hat") {
if (self.itemId === "ninjaHeadband") {
// Position ninja headband in the middle of the head
offsetY = -10; // Centered on the head
} else {
offsetY = -50; // Default position for other hats
}
} else if (self.itemId === "sunglasses") {
offsetY = -10; // Higher position for sunglasses
} else {
offsetY = 20; // Default for other accessories
}
// Set position and rotation from the head's center
self.x = head.x;
self.y = head.y;
// Set rotation to match head
self.rotation = head.rotation;
// Apply the offset after rotation to make it rotate around the head
if (offsetY !== 0 || offsetX !== 0) {
// Calculate the offset position after rotation
var sinR = Math.sin(head.rotation);
var cosR = Math.cos(head.rotation);
// Apply rotated offset
self.x += cosR * offsetX - sinR * offsetY;
self.y += sinR * offsetX + cosR * offsetY;
}
// Add small wobble/rotation effect for natural movement
if (Math.random() < 0.1) {
// Random jolts occasionally - but relative to head's rotation
var joltRot = (Math.random() - 0.5) * 0.05;
var joltY = (Math.random() - 0.5) * 2;
tween(self, {
rotation: head.rotation + joltRot,
// Add jolt to head's rotation
y: self.y + joltY
}, {
duration: 300,
easing: tween.elasticOut
});
}
}
}
};
return self;
});
var Stickman = Container.expand(function () {
var self = Container.call(this);
// Initialize physics properties for ragdoll effect
var bodyParts = [];
var isRagdolling = false;
var restoreTimer = null;
// Cosmetic items
var currentHat = null;
var currentAccessory = null;
// Make isRagdolling accessible
self.isRagdolling = isRagdolling;
// Make startRagdoll accessible
self.startRagdoll = startRagdoll;
// Body parts with physics properties
var body = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 1
});
body.velocityX = 0;
body.velocityY = 0;
body.rotationalVelocity = 0;
body.mass = 10;
body.originalX = 0;
body.originalY = 0;
body.originalRotation = 0;
body.elasticity = 0.7;
body.friction = 0.9;
bodyParts.push(body);
var head = self.attachAsset('head', {
anchorX: 0.5,
anchorY: 0.5,
y: -225
});
head.velocityX = 0;
head.velocityY = 0;
head.rotationalVelocity = 0;
head.mass = 5;
head.originalX = 0;
head.originalY = -225;
head.originalRotation = 0;
head.elasticity = 0.8;
head.friction = 0.85;
bodyParts.push(head);
// Arms (using stickman asset but resized)
var leftArm = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.6,
x: -70,
y: -150,
rotation: 0
});
leftArm.velocityX = 0;
leftArm.velocityY = 0;
leftArm.rotationalVelocity = 0;
leftArm.mass = 3;
leftArm.originalX = -70;
leftArm.originalY = -150;
leftArm.originalRotation = 0;
leftArm.elasticity = 0.9;
leftArm.friction = 0.8;
bodyParts.push(leftArm);
var rightArm = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.6,
x: 70,
y: -150,
rotation: 0
});
rightArm.velocityX = 0;
rightArm.velocityY = 0;
rightArm.rotationalVelocity = 0;
rightArm.mass = 3;
rightArm.originalX = 70;
rightArm.originalY = -150;
rightArm.originalRotation = 0;
rightArm.elasticity = 0.9;
rightArm.friction = 0.8;
bodyParts.push(rightArm);
// Legs (using stickman asset but resized)
var leftLeg = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.7,
x: -40,
y: 50,
rotation: 0
});
leftLeg.velocityX = 0;
leftLeg.velocityY = 0;
leftLeg.rotationalVelocity = 0;
leftLeg.mass = 4;
leftLeg.originalX = -40;
leftLeg.originalY = 50;
leftLeg.originalRotation = 0;
leftLeg.elasticity = 0.6;
leftLeg.friction = 0.85;
bodyParts.push(leftLeg);
var rightLeg = self.attachAsset('stickman', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 0.7,
x: 40,
y: 50,
rotation: 0
});
rightLeg.velocityX = 0;
rightLeg.velocityY = 0;
rightLeg.rotationalVelocity = 0;
rightLeg.mass = 4;
rightLeg.originalX = 40;
rightLeg.originalY = 50;
rightLeg.originalRotation = 0;
rightLeg.elasticity = 0.6;
rightLeg.friction = 0.85;
bodyParts.push(rightLeg);
// Health properties
self.maxHealth = 100;
self.health = self.maxHealth;
self.level = storage.level || 1;
// Apply a force to a body part
function applyForce(part, forceX, forceY, torque) {
part.velocityX += forceX / part.mass;
part.velocityY += forceY / part.mass;
part.rotationalVelocity += torque / part.mass;
}
// Start ragdoll physics
function startRagdoll() {
isRagdolling = true;
// Clear any existing restore timer
if (restoreTimer) {
LK.clearTimeout(restoreTimer);
}
// Set a timer to restore the stickman to normal position
restoreTimer = LK.setTimeout(function () {
restorePosition();
}, 3000);
}
// Restore stickman to normal position
function restorePosition() {
isRagdolling = false;
// Reset velocities first to prevent further movement
bodyParts.forEach(function (part) {
part.velocityX = 0;
part.velocityY = 0;
part.rotationalVelocity = 0;
});
// Animate each body part back to its original position
bodyParts.forEach(function (part) {
// Stop any ongoing tweens
tween.stop(part, {
x: true,
y: true,
rotation: true
});
// Animate back to original position with staggered timing for more natural movement
var delay = Math.random() * 200; // Stagger the animations slightly
// Animate back to original position
tween(part, {
x: part.originalX,
y: part.originalY,
rotation: part.originalRotation
}, {
duration: 1200,
easing: tween.elasticOut,
delay: delay
});
});
// Only add cosmetics in defeat() method, not here when unragdolling
}
// Update physics simulation
self.update = function () {
if (isRagdolling) {
var gravity = 0.2; // Further reduced gravity for less falling apart
bodyParts.forEach(function (part) {
// Apply gravity - but less than before
part.velocityY += gravity;
// Add random jolts for more realism (every few frames randomly)
if (Math.random() < 0.1) {
// 10% chance each frame for a random jolt
var joltX = (Math.random() - 0.5) * 2.0;
var joltY = (Math.random() - 0.5) * 1.5;
var joltRot = (Math.random() - 0.5) * 0.05;
part.velocityX += joltX;
part.velocityY += joltY;
part.rotationalVelocity += joltRot;
}
// Always add a gentle force toward the center of the screen
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var towardsCenterX = centerX - stickman.x - part.x;
var towardsCenterY = centerY - stickman.y - part.y;
var distance = Math.sqrt(towardsCenterX * towardsCenterX + towardsCenterY * towardsCenterY);
// Only apply centering force if we're not too close to center already
if (distance > 50) {
// Normalize the direction vector
var dirX = towardsCenterX / distance;
var dirY = towardsCenterY / distance;
// Apply a constant stronger force toward center
var centeringForce = 2.0; // Dramatically increased to make sliding to middle super fast
part.velocityX += dirX * centeringForce;
part.velocityY += dirY * centeringForce;
}
// Apply velocities
part.x += part.velocityX;
part.y += part.velocityY;
part.rotation += part.rotationalVelocity;
// Apply more realistic friction system with surface friction
// Ground friction increases as parts get closer to the bottom of the screen
var groundY = 2732 - 200; // Position where ground friction is strongest
var distanceFromGround = Math.max(0, groundY - (stickman.y + part.y));
var groundFrictionFactor = Math.max(0, 1 - distanceFromGround / 1000); // Gradually increase friction near ground
var surfaceFriction = 0.92 + groundFrictionFactor * 0.08; // 0.92 to 1.0 based on ground proximity
// Apply friction with different coefficients for horizontal vs vertical movement
// Less friction on X to allow more sliding on surfaces
part.velocityX *= part.friction + 0.05 * (1 - groundFrictionFactor); // Less friction for X when sliding
part.velocityY *= part.friction * surfaceFriction; // More friction for Y when on surfaces
part.rotationalVelocity *= 0.95;
// Add damping to prevent excessive movement
if (Math.abs(part.velocityX) < 0.1) part.velocityX = 0;
if (Math.abs(part.velocityY) < 0.1) part.velocityY = 0;
if (Math.abs(part.rotationalVelocity) < 0.01) part.rotationalVelocity = 0;
// Constrain rotation
if (part.rotation > Math.PI * 2) {
part.rotation -= Math.PI * 2;
} else if (part.rotation < -Math.PI * 2) {
part.rotation += Math.PI * 2;
}
// Always keep limbs in their original positions by tweening them back
// This ensures limbs stay where they belong while allowing some movement
if (part === head) {
tween(part, {
x: part.originalX,
y: part.originalY - 100 // Make head hover higher by 100 pixels
}, {
duration: 300,
easing: tween.easeOut
});
}
// Body stays in original position with tweening
if (part === body) {
tween(part, {
x: part.originalX,
y: part.originalY,
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
// Arms stay connected to body with tweening but hover closer together and lower
if (part === leftArm || part === rightArm) {
tween(part, {
x: part === leftArm ? part.originalX - 10 : part.originalX + 10,
// Position arms closer together but lower
y: part.originalY - 30,
// Make arms hover lower by only 30 pixels instead of 130
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
// Legs stay connected to body with tweening but hover slightly lower
if (part === leftLeg || part === rightLeg) {
tween(part, {
x: part.originalX,
y: part.originalY + 20,
// Make legs hover lower by adding 20 pixels
rotation: part.originalRotation
}, {
duration: 300,
easing: tween.easeOut
});
}
});
} else {
// Bob animation when not ragdolling
// Use LK.ticks to create a smooth bobbing effect
var bobAmount = Math.sin(LK.ticks / 20) * 15; // Control bob height with multiplier
var armBobAmount = Math.sin(LK.ticks / 15) * 10; // Slightly different timing for arms
// Bob head up and down
tween(head, {
y: head.originalY + bobAmount * 0.8 // Head bobs slightly less than body
}, {
duration: 100,
easing: tween.easeOut
});
// Bob body up and down
tween(body, {
y: body.originalY + bobAmount
}, {
duration: 100,
easing: tween.easeOut
});
// Bob arms with slight delay compared to body for natural swinging effect
// Add arm swing using sin wave with different frequency than bob
var armSwingAmount = Math.sin(LK.ticks / 25) * 0.2; // Swinging rotation
tween(leftArm, {
x: leftArm.originalX - 10,
// Keep arms closer together by moving them further inward
y: leftArm.originalY - 30 + armBobAmount,
// Arms bob with slight offset, hovering lower now
rotation: armSwingAmount // Arms swing around slightly
}, {
duration: 100,
easing: tween.easeOut
});
tween(rightArm, {
x: rightArm.originalX + 10,
// Keep arms closer together by moving them further inward
y: rightArm.originalY - 30 + armBobAmount,
// Arms bob with slight offset, hovering lower now
rotation: -armSwingAmount // Arms swing in opposite direction
}, {
duration: 100,
easing: tween.easeOut
});
// Bob legs with opposite timing to body for natural movement, but lower
// Add leg swing using cos wave for offset from arm swing
var legSwingAmount = Math.cos(LK.ticks / 30) * 0.15; // Smaller swing for legs
tween(leftLeg, {
y: leftLeg.originalY + 50 - bobAmount * 0.5,
// Position legs lower by 50px and move opposite to body
rotation: legSwingAmount // Legs swing around slightly
}, {
duration: 100,
easing: tween.easeOut
});
tween(rightLeg, {
y: rightLeg.originalY + 50 - bobAmount * 0.5,
// Position legs lower by 50px and move opposite to body
rotation: -legSwingAmount // Legs swing in opposite direction
}, {
duration: 100,
easing: tween.easeOut
});
}
};
// Hit reaction
self.hit = function (damage) {
// If health is already zero, don't allow further hits
if (self.health <= 0) {
return false;
}
// Decrease health
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
self.defeat();
}
// Update health UI
updateHealthBar();
// Visual reaction - flash
LK.effects.flashObject(self, 0xFF0000, 300);
// Get random body part for hit location
var parts = [body, head, leftArm, rightArm, leftLeg, rightLeg];
var randomPart = parts[Math.floor(Math.random() * parts.length)];
// Create hit effect
var hitEffect = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: randomPart.x,
y: randomPart.y,
alpha: 0.7,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(hitEffect);
// Animate the hit effect
tween(hitEffect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
hitEffect.destroy();
}
});
// Start ragdoll physics if not already in that state
startRagdoll();
// Apply impulse to the hit body part and make entire stickman slide
var randomDirection = Math.random() * Math.PI * 2; // Random angle in radians
var slideForce = damage * 2.0; // Reduced sliding force for less movement
var slideForceX = Math.cos(randomDirection) * slideForce;
var slideForceY = Math.sin(randomDirection) * slideForce;
// Apply sliding force to all body parts - but with more control
bodyParts.forEach(function (part) {
// Apply more balanced forces between X and Y for better overall movement
// Cap the maximum force to prevent excessive displacement
var maxForce = 30; // Reduced max force for lighter sliding
var forceFactor = 3.0; // Reduced force factor for gentler middle movement
var appliedForceX = Math.min(maxForce, slideForceX * forceFactor);
var appliedForceY = Math.min(maxForce, slideForceY * forceFactor);
// Apply the forces
part.velocityX += appliedForceX;
part.velocityY += appliedForceY;
});
// Apply additional impulse to the specific hit body part
// Apply more controlled force to avoid body parts flying apart
var hitAngle = Math.random() * Math.PI * 2;
var hitMagnitude = Math.min(damage * 1.5, 10); // Further reduced hit magnitude
var hitForceX = Math.cos(hitAngle) * hitMagnitude;
var hitForceY = Math.sin(hitAngle) * hitMagnitude;
var hitTorque = (Math.random() - 0.5) * damage * 0.1; // Significantly reduced torque
// Apply force with special effects based on the body part hit
if (randomPart === head) {
// Head hits are more impactful
applyForce(randomPart, hitForceX * 1.5, hitForceY * 1.5, hitTorque * 2);
// Also make the head wobble
tween(randomPart, {
rotation: randomPart.rotation + Math.PI * (Math.random() - 0.5)
}, {
duration: 500,
easing: tween.elasticOut
});
} else if (randomPart === leftArm || randomPart === rightArm) {
// Arms swing more
applyForce(randomPart, hitForceX, hitForceY, hitTorque * 3);
} else if (randomPart === leftLeg || randomPart === rightLeg) {
// Legs have more mass, less rotation
applyForce(randomPart, hitForceX * 0.8, hitForceY * 0.8, hitTorque * 0.5);
} else if (randomPart === body) {
// Body affects all parts
bodyParts.forEach(function (part) {
applyForce(part, hitForceX * 0.6, hitForceY * 0.6, hitTorque * 0.3);
});
}
// Play sound
LK.getSound('hit').play();
// No chance to get cosmetics on hit - only on defeat
return true;
};
// When stickman is defeated
self.defeat = function () {
// Massive ragdoll effect on defeat
startRagdoll();
// Apply explosive force to all body parts
bodyParts.forEach(function (part) {
var explosiveForceX = (Math.random() - 0.5) * 50;
var explosiveForceY = (Math.random() - 0.5) * 50;
var explosiveTorque = (Math.random() - 0.5) * 2;
applyForce(part, explosiveForceX, explosiveForceY, explosiveTorque);
});
// Make cosmetics fly off dramatically
if (currentHat) {
var hatX = currentHat.x;
var hatY = currentHat.y;
tween(currentHat, {
x: hatX + (Math.random() - 0.5) * 300,
y: hatY - Math.random() * 400,
rotation: Math.PI * (Math.random() * 4 - 2),
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (currentHat) {
currentHat.destroy();
currentHat = null;
}
}
});
}
if (currentAccessory) {
var accX = currentAccessory.x;
var accY = currentAccessory.y;
tween(currentAccessory, {
x: accX + (Math.random() - 0.5) * 300,
y: accY + Math.random() * 400,
rotation: Math.PI * (Math.random() * 4 - 2),
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (currentAccessory) {
currentAccessory.destroy();
currentAccessory = null;
}
}
});
}
// Create explosion effects around each body part
bodyParts.forEach(function (part) {
// Create multiple explosion particles for each body part
for (var i = 0; i < 3; i++) {
var offsetX = (Math.random() - 0.5) * 100;
var offsetY = (Math.random() - 0.5) * 100;
var explosion = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: part.x + offsetX,
y: part.y + offsetY,
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8,
tint: Math.random() > 0.5 ? 0xff0000 : 0xff6600
});
self.addChild(explosion);
// Animate explosion
tween(explosion, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
// Hide body parts temporarily (they'll reappear when respawned)
bodyParts.forEach(function (part) {
tween(part, {
alpha: 0
}, {
duration: 400
});
});
// Level up
self.level++;
storage.level = self.level;
// Reward player with 500 points on defeat
var reward = 500;
addPoints(reward);
// Health will remain the same after each kill (no increase)
// Show level up message
showLevelUpMessage(self.level, reward);
// Reset health with increased max health after a delay (reduced to 2 seconds)
LK.setTimeout(function () {
// Make parts visible again
bodyParts.forEach(function (part) {
part.alpha = 1;
});
// Increase max health by 25% each respawn
self.maxHealth = Math.floor(self.maxHealth * 1.25);
self.health = self.maxHealth;
updateHealthBar();
restorePosition();
// Play respawn sound
LK.getSound('respawn').play();
// Apply new random cosmetics on respawn
self.applyRandomCosmetic("hat");
self.applyRandomCosmetic("accessory");
}, 2000); // Changed from 3000 to 2000 for 2-second respawn
// Play death sound
LK.getSound('death').play();
};
// Make applyRandomCosmetic a public method so it can be accessed from outside the class
self.applyRandomCosmetic = function (cosmeticType) {
// Remove current cosmetic of this type if exists
if (cosmeticType === "hat" && currentHat) {
if (currentHat) {
currentHat.destroy();
currentHat = null;
}
} else if (cosmeticType === "accessory" && currentAccessory) {
if (currentAccessory) {
currentAccessory.destroy();
currentAccessory = null;
}
}
// Available cosmetic items
var hatOptions = ["cowboyHat", "topHat", "partyHat", "crown", "propellerHat", "pirateBandana", "ninjaHeadband", "mohawk"];
var accessoryOptions = ["sunglasses", "mustache", "beard"];
// Choose a random item
var options = cosmeticType === "hat" ? hatOptions : accessoryOptions;
var randomItem = options[Math.floor(Math.random() * options.length)];
// Create and attach the new cosmetic item
var newCosmetic = new Cosmetic(cosmeticType, randomItem);
// Normal cosmetic addition for all items
self.addChild(newCosmetic);
// Store reference to the new item
if (cosmeticType === "hat") {
currentHat = newCosmetic;
} else {
currentAccessory = newCosmetic;
}
// Add entrance animation
newCosmetic.scaleX = 0.1;
newCosmetic.scaleY = 0.1;
newCosmetic.alpha = 0;
tween(newCosmetic, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.elasticOut
});
// Play unlock sound
LK.getSound('unlock').play();
// Show message
var itemName = randomItem.charAt(0).toUpperCase() + randomItem.slice(1);
showMessage("New " + cosmeticType + ": " + itemName);
};
// Event handler when clicking on stickman
self.down = function (x, y, obj) {
// Convert coordinates to local space
var localX = x - self.x;
var localY = y - self.y;
// Find closest body part to the click point
var closestPart = body; // Default to body
var closestDistance = Number.MAX_VALUE;
bodyParts.forEach(function (part) {
var dx = part.x - localX;
var dy = part.y - localY;
var distance = dx * dx + dy * dy;
if (distance < closestDistance) {
closestDistance = distance;
closestPart = part;
}
});
// Use the class method instead of local function
// Now we use self.applyRandomCosmetic which is defined outside of this method
// Apply current tool damage
self.hit(currentDamage);
// Additional force to the specific body part that was clicked - reduced to make knockback gentler
var hitForceX = (Math.random() - 0.5) * currentDamage * 2;
var hitForceY = (Math.random() - 0.5) * currentDamage * 2;
var hitTorque = (Math.random() - 0.5) * currentDamage * 0.1;
applyForce(closestPart, hitForceX, hitForceY, hitTorque);
// Add points based on current damage
addPoints(currentDamage);
};
return self;
});
var Tool = Container.expand(function (toolData) {
var self = Container.call(this);
self.data = toolData;
var toolShape = self.attachAsset(toolData.id, {
anchorX: 0.5,
anchorY: 0.5
});
var nameText = new Text2(toolData.name, {
size: 24,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0);
nameText.y = 80;
self.addChild(nameText);
var damageText = new Text2("+" + toolData.damage + " DMG", {
size: 22,
fill: 0xFFFF00
});
damageText.anchor.set(0.5, 0);
damageText.y = 110;
self.addChild(damageText);
var priceText = new Text2(toolData.price + " PTS", {
size: 22,
fill: 0xFFFFFF
});
priceText.anchor.set(0.5, 0);
priceText.y = 140;
self.addChild(priceText);
self.down = function (x, y, obj) {
// Only interact with the shop when it's open
if (!isShopOpen) return;
if (points >= toolData.price && !isToolOwned(toolData.id)) {
// Purchase the tool
addPoints(-toolData.price);
unlockTool(toolData);
// Play unlock sound
LK.getSound('unlock').play();
// Show purchase confirmation
showMessage("You unlocked " + toolData.name + "!");
// Update shop
updateShop();
} else if (isToolOwned(toolData.id) && points >= toolData.price) {
// Pay for using the tool if already owned
addPoints(-toolData.price);
// Equip the tool
equipTool(toolData.id);
LK.getSound('unlock').play();
showMessage(toolData.name + " equipped for " + toolData.price + " points!");
// Update shop
updateShop();
} else {
// Show not enough points message
showMessage("Not enough points!");
}
};
return self;
});
var Weapon = Container.expand(function () {
var self = Container.call(this);
// Create weapon graphic based on current tool
var toolAsset = equippedTool ? equippedTool.id : 'fist';
var weaponGraphic = self.attachAsset(toolAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Weapon properties
self.velocityX = 0;
self.velocityY = 0;
self.speed = 15;
self.damage = 0;
self.target = null;
self.lastIntersecting = false;
// Update method to move weapon towards target
self.update = function () {
if (!self.target) return;
// Calculate direction to target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If weapon is close to target, hit it
if (distance < 50) {
if (self.target.health > 0) {
self.target.hit(self.damage);
createHitAnimation(self.x, self.y);
}
self.destroy();
return;
}
// Normalize direction vector
var dirX = dx / distance;
var dirY = dy / distance;
// Move towards target
self.x += dirX * self.speed;
self.y += dirY * self.speed;
// Rotate weapon to face movement direction
self.rotation = Math.atan2(dy, dx) + Math.PI / 4;
// Add spinning effect to weapons except for fist
if (toolAsset !== 'fist') {
// Add a continuous rotation on top of the directional rotation
self.rotation += LK.ticks * 0.2; // Controls spin speed
}
// Check intersection with target
var currentIntersecting = self.intersects(self.target);
if (!self.lastIntersecting && currentIntersecting) {
if (self.target.health > 0) {
self.target.hit(self.damage);
createHitAnimation(self.x, self.y);
// Increment score when weapon hits stickman
addPoints(1);
}
self.destroy();
}
self.lastIntersecting = currentIntersecting;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xEEEEEE // Light gray background for better stickman visibility
});
/****
* Game Code
****/
// Face accessory assets
// Hat assets
// Game state variables
// Headphones removed
if (!tween) {
console.error("Failed to import tween library");
// Create fallback implementation
tween = function tween(obj, props, options) {
if (options && options.onFinish) {
setTimeout(options.onFinish, options.duration || 1000);
}
// Apply props immediately as fallback
for (var key in props) {
if (props.hasOwnProperty(key)) {
obj[key] = props[key];
}
}
};
tween.easeOut = function (t) {
return t;
};
tween.easeIn = function (t) {
return t;
};
tween.elasticOut = function (t) {
return t;
};
tween.stop = function () {};
}
var points = storage.points || 0;
var baseDamage = storage.damage || 1;
var currentDamage = baseDamage;
var tools = storage.tools || [];
var equippedTool = null;
var isShopOpen = false;
var shopContainer = null;
var activeWeapons = [];
// Tool definitions
var availableTools = [{
id: 'fist',
name: 'Fist',
damage: 1,
price: 0,
color: 0xFFB380,
owned: true
}, {
id: 'stick',
name: 'Stick',
damage: 2,
price: 300,
color: 0x8B4513
}, {
id: 'bat',
name: 'Baseball Bat',
damage: 5,
price: 800,
color: 0x654321
}, {
id: 'hammer',
name: 'Hammer',
damage: 10,
price: 2500,
color: 0x808080
}, {
id: 'pan',
name: 'Frying Pan',
damage: 15,
price: 5000,
color: 0x404040
}, {
id: 'mallet',
name: 'Cartoon Mallet',
damage: 25,
price: 12000,
color: 0xFF0000
}, {
id: 'anvil',
name: 'Anvil',
damage: 50,
price: 25000,
color: 0x303030
}, {
id: 'piano',
name: 'Grand Piano',
damage: 100,
price: 50000,
color: 0x000000
}];
// Set up the stickman
var stickman = new Stickman();
stickman.x = 2048 / 2;
stickman.y = 2732 / 2;
// Make stickman bigger for better visibility
stickman.scale.set(1.5, 1.5);
game.addChild(stickman);
// Set up UI elements
var pointsText = new Text2("POINTS: 0", {
size: 70,
fill: 0x000000
});
pointsText.anchor.set(0.5, 0);
pointsText.y = 50;
LK.gui.top.addChild(pointsText);
var damageText = new Text2("DAMAGE: 1", {
size: 50,
fill: 0xFF0000
});
damageText.anchor.set(0.5, 0);
damageText.y = 130;
LK.gui.top.addChild(damageText);
// Level counter removed
// Health bar
var healthBarBg = LK.getAsset('stickman', {
anchorX: 0,
anchorY: 0.5,
width: 1000,
height: 60,
tint: 0x888888
});
healthBarBg.x = 2048 / 2 - 500;
healthBarBg.y = 300;
game.addChild(healthBarBg);
var healthBar = LK.getAsset('stickman', {
anchorX: 0,
anchorY: 0.5,
width: 1000,
height: 60,
tint: 0xFF0000
});
healthBar.x = 2048 / 2 - 500;
healthBar.y = 300;
game.addChild(healthBar);
var healthText = new Text2("100/100", {
size: 46,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthText.x = 2048 / 2;
healthText.y = 300;
game.addChild(healthText);
// Shop button
var shopButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
shopButton.x = 2048 - 150;
shopButton.y = 100;
game.addChild(shopButton);
var shopText = new Text2("SHOP", {
size: 60,
fill: 0xFFFFFF
});
shopText.anchor.set(0.5, 0.5);
shopText.x = 2048 - 150;
shopText.y = 100;
game.addChild(shopText);
// Message display
var messageText = new Text2("", {
size: 60,
fill: 0xFF0000
});
messageText.anchor.set(0.5, 0.5);
messageText.x = 2048 / 2;
messageText.y = 500;
messageText.alpha = 0;
game.addChild(messageText);
// Initialize the game
function init() {
// Set points to 0 when game starts
points = 0;
storage.points = 0;
// Reset weapons when game starts
currentDamage = baseDamage;
equippedTool = availableTools[0]; // Reset to fist
activeWeapons = [];
tools = []; // Reset tools array to empty so no weapons are purchased
storage.tools = []; // Update storage to persist the reset
// Set up initial values
updatePointsDisplay();
updateDamageDisplay();
updateHealthBar();
// Give stickman a random hat and/or accessory on start
if (Math.random() < 0.95) {
// 95% chance to start with a hat (increased from 70%)
stickman.applyRandomCosmetic("hat");
}
if (Math.random() < 0.9) {
// 90% chance to start with an accessory (increased from 50%)
stickman.applyRandomCosmetic("accessory");
}
// Preload all cosmetic assets to ensure they're available immediately
var allCosmetics = ["cowboyHat", "topHat", "partyHat", "crown", "propellerHat", "pirateBandana", "ninjaHeadband", "mohawk", "sunglasses", "mustache", "beard"];
allCosmetics.forEach(function (cosmeticId) {
// Create temporary assets to ensure they're loaded into memory
var tempAsset = LK.getAsset(cosmeticId, {
anchorX: 0.5,
anchorY: 0.5
});
// We don't need to add these to the game, just make sure they're loaded
});
// Mark tools as owned if in storage
tools.forEach(function (toolId) {
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolId) {
availableTools[i].owned = true;
}
}
});
// Equip the first owned tool
var ownedTools = availableTools.filter(function (tool) {
return tool.owned;
});
if (ownedTools.length > 0) {
equipTool(ownedTools[0].id);
}
// Set shop button interaction
shopButton.interactive = true;
shopButton.down = function () {
toggleShop();
};
// Play background music
LK.playMusic('gameMusic');
}
// Toggle shop visibility
function toggleShop() {
if (isShopOpen) {
closeShop();
} else {
openShop();
}
}
// Open the shop
function openShop() {
isShopOpen = true;
// Create shop container
shopContainer = new Container();
shopContainer.x = 2048 / 2;
shopContainer.y = 2732 / 2;
game.addChild(shopContainer);
// Shop background
var shopBg = LK.getAsset('stickman', {
anchorX: 0.5,
anchorY: 0.5,
width: 1600,
height: 1800,
tint: 0x333333,
alpha: 0.9
});
shopContainer.addChild(shopBg);
// Shop title
var shopTitle = new Text2("TOOL SHOP", {
size: 80,
fill: 0xFFFFFF
});
shopTitle.anchor.set(0.5, 0);
shopTitle.y = -800;
shopContainer.addChild(shopTitle);
// Close button
var closeButton = LK.getAsset('shopButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 700,
y: -800,
tint: 0xFF0000
});
shopContainer.addChild(closeButton);
var closeText = new Text2("CLOSE", {
size: 40,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 700;
closeText.y = -800;
shopContainer.addChild(closeText);
closeButton.interactive = true;
closeButton.down = function () {
closeShop();
};
// Layout tools in a grid
var toolsPerRow = 3;
var toolWidth = 300;
var toolHeight = 350;
var startX = -(toolsPerRow - 1) * toolWidth / 2;
var startY = -600;
availableTools.forEach(function (toolData, index) {
var row = Math.floor(index / toolsPerRow);
var col = index % toolsPerRow;
var toolItem = new Tool(toolData);
toolItem.x = startX + col * toolWidth;
toolItem.y = startY + row * toolHeight;
shopContainer.addChild(toolItem);
// Show "OWNED" or "EQUIPPED" if applicable
if (toolData.owned) {
var statusText = new Text2(isToolEquipped(toolData.id) ? "EQUIPPED" : "OWNED", {
size: 24,
fill: isToolEquipped(toolData.id) ? "#00FF00" : "#FFFFFF"
});
statusText.anchor.set(0.5, 0);
statusText.y = 170;
toolItem.addChild(statusText);
}
});
// Animate shop opening
shopContainer.alpha = 0;
shopContainer.scaleX = 0.8;
shopContainer.scaleY = 0.8;
tween(shopContainer, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Close the shop
function closeShop() {
if (!isShopOpen || !shopContainer) return;
tween(shopContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (shopContainer) {
shopContainer.destroy();
shopContainer = null;
}
isShopOpen = false;
}
});
}
// Check if a tool is owned
function isToolOwned(toolId) {
return tools.indexOf(toolId) !== -1;
}
// Check if a tool is currently equipped
function isToolEquipped(toolId) {
return equippedTool && equippedTool.id === toolId;
}
// Unlock a new tool
function unlockTool(toolData) {
if (!isToolOwned(toolData.id)) {
tools.push(toolData.id);
storage.tools = tools;
// Mark as owned
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolData.id) {
availableTools[i].owned = true;
break;
}
}
// Equip the newly unlocked tool
equipTool(toolData.id);
}
}
// Equip a tool
function equipTool(toolId) {
if (!isToolOwned(toolId)) return;
// Find the tool data
var toolData = null;
for (var i = 0; i < availableTools.length; i++) {
if (availableTools[i].id === toolId) {
toolData = availableTools[i];
break;
}
}
if (toolData) {
equippedTool = toolData;
currentDamage = baseDamage + toolData.damage;
updateDamageDisplay();
}
}
// Update the shop display
function updateShop() {
if (isShopOpen) {
closeShop();
openShop();
}
}
// Add points to the player's total
function addPoints(amount) {
points += amount;
storage.points = points;
updatePointsDisplay();
}
// Update the points display
function updatePointsDisplay() {
pointsText.setText("POINTS: " + points);
}
// Update the damage display
function updateDamageDisplay() {
damageText.setText("DAMAGE: " + currentDamage);
}
// Level display function removed
// Update the health bar
function updateHealthBar() {
var percentage = stickman.health / stickman.maxHealth;
healthBar.width = 1000 * percentage;
healthText.setText(stickman.health + "/" + stickman.maxHealth);
}
// Show a message temporarily
function showMessage(text) {
messageText.setText(text);
messageText.alpha = 1;
// Clear any existing tweens
tween.stop(messageText, {
alpha: true
});
// Animate message
tween(messageText, {
alpha: 0
}, {
duration: 2000
});
}
// Show level up message
function showLevelUpMessage(level, reward) {
var levelUpText = new Text2("LEVEL UP!\n+" + reward + " Points", {
size: 80,
fill: 0xFFFF00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.x = 2048 / 2;
levelUpText.y = 2732 / 2 - 400;
levelUpText.alpha = 0;
game.addChild(levelUpText);
// Animate the level up text
tween(levelUpText, {
alpha: 1,
y: levelUpText.y - 100
}, {
duration: 500,
onFinish: function onFinish() {
tween(levelUpText, {
alpha: 0
}, {
duration: 1000,
delay: 1000,
onFinish: function onFinish() {
levelUpText.destroy();
}
});
}
});
}
// Create hit animation
function createHitAnimation(x, y) {
var hitEffect = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.7,
scaleX: 0.5,
scaleY: 0.5
});
game.addChild(hitEffect);
tween(hitEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
hitEffect.destroy();
}
});
}
// Track dragging state
var isDragging = false;
var draggedBodyPart = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Mouse/touch handlers for the game
game.down = function (x, y, obj) {
// If the shop is open, don't register clicks on the game
if (isShopOpen) return;
// If stickman is dead, don't allow hitting
if (stickman.health <= 0) {
return;
}
// No longer add points on each click
// Spawn weapon at tap location and make it fly towards stickman
var weapon = new Weapon();
weapon.x = x;
weapon.y = y;
weapon.damage = currentDamage;
weapon.target = stickman;
// Add to game
game.addChild(weapon);
// Create a hit animation at the tap point
createHitAnimation(x, y);
// Play click sound for tapping the screen
LK.getSound('click').play();
};
// Handle move events for dragging
game.move = function (x, y, obj) {
// If stickman is dead, don't allow dragging
if (stickman.health <= 0) {
isDragging = false;
draggedBodyPart = null;
return;
}
if (isDragging && draggedBodyPart && !isShopOpen) {
var localX = x - stickman.x;
var localY = y - stickman.y;
// Update dragged part position
draggedBodyPart.x = localX - dragOffsetX;
draggedBodyPart.y = localY - dragOffsetY;
// Reset velocity for smooth dragging
draggedBodyPart.velocityX = 0;
draggedBodyPart.velocityY = 0;
}
};
// Handle up events for dragging
game.up = function (x, y, obj) {
if (isDragging && draggedBodyPart && !isShopOpen) {
// On release, give the part some velocity based on movement
var releaseVelocityX = (Math.random() - 0.5) * 10;
var releaseVelocityY = (Math.random() - 0.5) * 10;
draggedBodyPart.velocityX = releaseVelocityX;
draggedBodyPart.velocityY = releaseVelocityY;
isDragging = false;
draggedBodyPart = null;
}
};
// Update function called every tick
game.update = function () {
// Clean up destroyed weapons
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child instanceof Weapon) {
if (!child.parent) {
game.children.splice(i, 1);
i--;
}
}
}
};
// Initialize the game
init();
Curved dark gray square with a white outline. In-Game asset. 2d. High contrast. No shadows
A dark gray circle with a white outline with 2 white dots as eyes. In-Game asset. 2d. High contrast. No shadows
Big light brown beard. In-Game asset. 2d. High contrast. No shadows
Cowboy hat. In-Game asset. 2d. High contrast. No shadows
Crown. In-Game asset. 2d. High contrast. No shadows
Anvil. In-Game asset. 2d. High contrast. No shadows
Mustache. In-Game asset. 2d. High contrast. No shadows
Cool sunglasses. In-Game asset. 2d. High contrast. No shadows. Facing camera
Top hat. In-Game asset. 2d. High contrast. No shadows
Party hat. In-Game asset. 2d. High contrast. No shadows
Green block with curved edges. In-Game asset. 2d. High contrast. No shadows
Baseball bat. In-Game asset. 2d. High contrast. No shadows
White glove curled into a fist. In-Game asset. 2d. High contrast. No shadows
Blue block with curved edges. In-Game asset. 2d. High contrast. No shadows
Rubber mallet. In-Game asset. 2d. High contrast. No shadows
Hammer. In-Game asset. 2d. High contrast. No shadows
Frying pan. In-Game asset. 2d. High contrast. No shadows
Stick. In-Game asset. 2d. High contrast. No shadows
Grand piano. In-Game asset. 2d. High contrast. No shadows
Gray circle with white outline. In-Game asset. 2d. High contrast. No shadows
Explosion. In-Game asset. 2d. High contrast. No shadows
Mohawk with no head. In-Game asset. 2d. High contrast. No shadows
Pirate hat. In-Game asset. 2d. High contrast. No shadows
Headband. In-Game asset. 2d. High contrast. No shadows
Propeller hat. In-Game asset. 2d. High contrast. No shadows