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