User prompt
newgame yazısını kaldır oyun açılış sayfasında sadece texture
User prompt
oyun açılış sayfasında oyun adını Pebble Trouble yap new game butonunu newgame texturesi ile yap
Code edit (6 edits merged)
Please save this source code
User prompt
tamam wave 1 e 2 normal düşman koy sadece 7. wave aç buraya bosszombi koy 1 tane canı 150 olsun bosszombinin
Code edit (1 edits merged)
Please save this source code
User prompt
BossZombieEnemy vucut parçaları yerleşimi kodlarını yaz
User prompt
BossZombieEnemy gövdeyi 20 pixel daha, kafayı ise 50 pixel daha yukarı taşı vucut parçaları yerleşiminde
User prompt
BossZombieEnemy gövdeyi 150 pixel, kafayı ise 280 pixel yukarı taşı vucut parçaları yerleşiminde
User prompt
BossZombieEnemy gövdeyi 200 pixel kadayı 300 pixel yukarı taşı vucut parçöa yerleşiminde
User prompt
zombi düşmanın boss versiyonunu yap aynı vucut sistemi 2,5 kat boyut. 10 damage yerine 15 vuruyor ve zehirden etkilenmiyor. wave 1 e koy bi tane
User prompt
wave1 deki zombiyi kaldır normal 2 düzman koy. wave 6 yap 3 zombi düşman koy
User prompt
shiledstone biraz daha ağırlaştır bu kadar yükseğe atılamamalı
User prompt
shiledstone taşı ekle büyük ve ağır.shiledstone texturesi ile kafaya veya vucuda 1 damage vuruyor düşmana. ama düşmanın attığı taşlara çarparsa onları geri fırlatır
User prompt
yeni düşman tipi oluştur zombi olarak zehirden etkilenmiyor. vucut texturu oluşumu hareketleri diğer düşmanlarla aynı. sadece kafa oluşumu zombiehead texturu ile diğer head texturleri gelemez. ilk wave e bi tane ekle
User prompt
ragele düşman vucutlarına enemyBody4 ve enemyBody5 i de ekle
User prompt
%10 daha düşür rüzgar etkisini
User prompt
rüzgarın gücünü yarıya düşür
User prompt
"Please restructure the entire game code to fix the scope and timing conflicts. Follow this architectural pattern precisely: Global Variable Declarations: Move ALL variables that need to be accessed across different parts of the game (like player, enemies, scoreText, itemInfoDisplay, healthBarFill, windLogo, etc.) to the global scope at the top of the file. Declare them with var variableName = null; or an appropriate default value ([] for arrays, 0 for numbers). Global Function Declarations: Move ALL helper functions that are currently defined inside initializeGameContent (like updateItemInfo, createWindPowerSprites, updateHealthBar, animatePlayer) to the global scope, so they can be accessed from anywhere. Initialization Inside initializeGameContent: Modify the initializeGameContent function. Its ONLY job should be to assign values to the globally declared variables. It must NOT redeclare them using the var keyword. Incorrect: var scoreText = new Text2(...) Correct: scoreText = new Text2(...) Fix Function Dependencies: Ensure that when a global function (like updateItemInfo) is called, all the variables it depends on (like itemInfoDisplay) have already been initialized within initializeGameContent. Review the order of initialization inside initializeGameContent to prevent "Cannot read properties of null" errors. For example, itemInfoDisplay must be created before updateItemInfo is ever called. Class Interactions: The InventorySlot class calls updateItemInfo. Ensure this call only happens after the game has been fully initialized by startGame() and initializeGameContent(), preventing race conditions. The current logic to auto-select the first item already handles this, but verify it remains correct after restructuring. In summary, refactor the code to have a clear separation between Declaration (Global) and Initialization (Local to a specific function). This will resolve all 'ReferenceError' and 'TypeError: Cannot read properties of null' issues permanently."
User prompt
Please fix the bug: 'Uncaught ReferenceError: updateItemInfo is not defined' in or related to this line: 'updateItemInfo(self.itemType);' Line Number: 400
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'attachAsset')' in or related to this line: 'infoItemSprite = itemInfoDisplay.attachAsset(itemType, {' Line Number: 661
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BackgroundTile = Container.expand(function () { var self = Container.call(this); // Randomly pick one of two backgrounds for variety var bgTypes = ['background', 'background2']; var bgType = bgTypes[Math.floor(Math.random() * bgTypes.length)]; var graphics = self.attachAsset(bgType, { anchorX: 0, anchorY: 0 }); // Slower speed for new game pace self.speed = 0.7; // Store width for seamless tiling self.tileWidth = graphics.width; self.update = function () { self.x -= self.speed; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); self.health = 30; self.isDead = false; self.speed = 1; self.isMoving = true; self.stopTimer = 0; self.throwTimer = 0; self.throwCooldown = 0; self.isPoisoned = false; self.poisonTimer = 0; // Randomize parts var headTypes = ['enemyHead1', 'enemyHead2', 'enemyHead3']; var bodyTypes = ['enemyBody1', 'enemyBody2', 'enemyBody3']; var legTypes = ['enemyLeg1', 'enemyLeg2']; var headType = headTypes[Math.floor(Math.random() * headTypes.length)]; var bodyType = bodyTypes[Math.floor(Math.random() * bodyTypes.length)]; var legType = legTypes[Math.floor(Math.random() * legTypes.length)]; // Store texture types for later use in poison effects self.headType = headType; self.bodyType = bodyType; self.legType = legType; // Create parts with adjusted positioning var head = self.attachAsset(headType, { anchorX: 0.5, anchorY: 0.5, x: 0, y: -105 }); var body = self.attachAsset(bodyType, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); var leftLeg = self.attachAsset(legType, { anchorX: 0.5, anchorY: 0.5, x: -20, y: 95 }); var rightLeg = self.attachAsset(legType, { anchorX: 0.5, anchorY: 0.5, x: 20, y: 95 }); self.head = head; self.body = body; self.leftLeg = leftLeg; self.rightLeg = rightLeg; // Set up layering - legs under body, head on top self.setChildIndex(leftLeg, 0); self.setChildIndex(rightLeg, 1); self.setChildIndex(body, 2); self.setChildIndex(head, 3); // Walking animation variables self.walkTimer = 0; self.walkSpeed = 0.3; self.update = function () { // Enemy movement - stop when reaching x=1900, then walk random 0-440 pixels more if (self.isMoving) { self.x -= self.speed; self.stopTimer++; // Initialize target stop distance when enemy first reaches x=1900 if (self.x <= 1900 && !self.targetStopDistance) { var randomExtraDistance = Math.random() * 440; // Random 0-440 pixels self.targetStopDistance = 1900 - randomExtraDistance; } // Stop when reaching the calculated target distance if (self.targetStopDistance && self.x <= self.targetStopDistance) { self.isMoving = false; self.stopTimer = 0; } } // Throwing logic when stationary if (!self.isMoving) { self.throwCooldown++; // Random chance to throw every 6-12 seconds (further reduced frequency) if (self.throwCooldown >= 360 + Math.random() * 360) { self.throwCooldown = 0; self.throwProjectile(); } } // Poison damage logic if (self.isPoisoned && !self.isDead) { self.poisonTimer++; // Apply poison damage every 3 seconds (180 ticks at 60 FPS) if (self.poisonTimer >= 180) { self.poisonTimer = 0; self.health -= 3; // Visual poison effect - green flash var parts = [self.head, self.body, self.leftLeg, self.rightLeg]; for (var i = 0; i < parts.length; i++) { var part = parts[i]; tween.stop(part, { tint: true }); part.tint = 0x00FF00; tween(part, { tint: 0xFFFFFF }, { duration: 250 }); } if (self.health <= 0) { self.isDead = true; } } // Poison visual effect - continuous green tint when poisoned var parts = [self.head, self.body, self.leftLeg, self.rightLeg]; for (var i = 0; i < parts.length; i++) { var part = parts[i]; if (self.isPoisoned && !self.isDead) { // Apply constant green tint to show poison status if (part.tint !== 0x00FF00) { part.tint = 0x00FF00; } // Create floating green effect every 180 ticks (3 seconds) if (LK.ticks % 180 === 0) { // Determine which texture to use based on the part var textureType = 'enemyBody1'; // default fallback if (part === self.head) { textureType = self.headType; } else if (part === self.body) { textureType = self.bodyType; } else if (part === self.leftLeg || part === self.rightLeg) { textureType = self.legType; } // Create a temporary green overlay for the floating effect using the correct texture var greenOverlay = game.addChild(LK.getAsset(textureType, { anchorX: 0.5, anchorY: 0.5, x: self.x + part.x, y: self.y + part.y, alpha: 0.9, tint: 0x00FF00 })); // Head part should appear on top of everything if (part === self.head) { game.setChildIndex(greenOverlay, game.children.length - 1); } // Make it bigger and fade out tween(greenOverlay, { scaleX: 1.8, scaleY: 1.8, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { if (greenOverlay.parent) { greenOverlay.destroy(); } } }); } } else { // Remove green tint when not poisoned if (part.tint === 0x00FF00) { part.tint = 0xFFFFFF; } } } } // Walking animation self.walkTimer += self.walkSpeed; var leftLegOffset = Math.sin(self.walkTimer) * 15; var rightLegOffset = Math.sin(self.walkTimer + Math.PI) * 15; // Animate legs - one forward while other goes back tween(self.leftLeg, { x: -20 + leftLegOffset }, { duration: 100 }); tween(self.rightLeg, { x: 20 + rightLegOffset }, { duration: 100 }); }; self.throwProjectile = function () { // Calculate direction to player with low accuracy var targetX = player.x + (Math.random() - 0.5) * 800; // Even more inaccuracy for longer range var targetY = player.y + (Math.random() - 0.5) * 400 - 800; // Aim much higher up for greater arc var deltaX = targetX - self.x; var deltaY = targetY - self.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Create enemy projectile var projectile = new Projectile('stone'); projectile.x = self.x; projectile.y = self.y; projectile.isEnemyProjectile = true; // Initialize collision tracking variables with explicit values - must be done before adding to arrays projectile.lastIntersectingEllipse = false; projectile.hasBouncedOffEllipse = false; projectile.lastX = projectile.x; projectile.lastY = projectile.y; // Ensure projectile has proper physics update from start projectile.isEnemyProjectile = true; // Calculate velocity with much higher speed for longer range and higher arc var speed = 18 + Math.random() * 8; // Further increased enemy projectile speed for more range var angle = Math.atan2(deltaY, deltaX); projectile.velocityX = Math.cos(angle) * speed; projectile.velocityY = Math.sin(angle) * speed; // Ensure projectile is properly added to arrays and game enemyProjectiles.push(projectile); game.addChild(projectile); // Ensure projectile appears in front of background game.setChildIndex(projectile, game.children.length - 1); }; self.takeDamage = function (damage, isPoisoned) { if (self.isDead) return; self.health -= damage; // Apply poison effect if projectile was poison stone if (isPoisoned && !self.isPoisoned) { self.isPoisoned = true; self.poisonTimer = 0; } // --- HIT FEEDBACK --- // 1. Flash all parts red for visual impact var parts = [self.head, self.body, self.leftLeg, self.rightLeg]; for (var i = 0; i < parts.length; i++) { var part = parts[i]; tween.stop(part, { tint: true }); part.tint = 0xFF0000; tween(part, { tint: 0xFFFFFF }, { duration: 250 }); } // 2. Knockback effect for physical impact, only when enemy is stationary if (!self.isMoving) { var knockbackDistance = 25; var originalX = self.x; tween.stop(self, { x: true }); // Quick knockback tween(self, { x: originalX + knockbackDistance }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { // If enemy still exists, tween back with a bouncy effect if (self.parent) { tween(self, { x: originalX }, { duration: 400, easing: tween.elasticOut }); } } }); } if (self.health <= 0) { self.isDead = true; } }; return self; }); var InventorySlot = Container.expand(function (itemType, count) { var self = Container.call(this); self.itemType = itemType || 'empty'; self.count = count || 0; self.maxCount = 99; self.selectionBorder = self.attachAsset('inventorySlot', { anchorX: 0.5, anchorY: 0.5 }); self.selectionBorder.tint = 0xFFFF00; self.selectionBorder.scale.set(1.1); self.selectionBorder.visible = false; var slotBg = self.attachAsset('inventorySlot', { anchorX: 0.5, anchorY: 0.5 }); self.itemSprite = null; if (self.itemType !== 'empty') { self.itemSprite = self.attachAsset(self.itemType, { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.min(slotBg.width / self.itemSprite.width, slotBg.height / self.itemSprite.height) * 0.9; self.itemSprite.scale.set(scale); } self.countText = new Text2(self.count.toString(), { size: 25, fill: '#FFFFFF', stroke: '#000000', strokeThickness: 5 }); self.countText.anchor.set(0, 1); self.countText.x = -slotBg.width / 2 + 5; self.countText.y = slotBg.height / 2 - 5; self.addChild(self.countText); self.updateCount = function () { self.countText.setText(self.count.toString()); if (self.count <= 1 || self.itemType === 'empty') { self.countText.visible = false; } else { self.countText.visible = true; } if (self.itemSprite) { if (self.count <= 0 || self.itemType === 'empty') { self.itemSprite.visible = false; } else { self.itemSprite.visible = true; } } }; self.updateCount(); self.canUse = function () { return self.count > 0 && self.itemType !== 'empty'; }; self.use = function () { if (self.canUse()) { self.count--; self.updateCount(); return true; } return false; }; self.select = function () { self.selectionBorder.visible = true; }; self.deselect = function () { self.selectionBorder.visible = false; }; self.down = function (x, y, obj) { if (self.canUse() && selectedSlot !== self) { if (selectedSlot) { selectedSlot.deselect(); } selectedItem = self.itemType; selectedSlot = self; self.select(); updateItemInfo(self.itemType); } }; return self; }); var Projectile = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = 8; self.hasHit = false; var projectileAsset = 'projectile'; if (self.type === 'stone') { projectileAsset = 'stone'; } else if (self.type === 'poisonstone') { projectileAsset = 'poisonstone'; } var graphics = self.attachAsset(projectileAsset, { anchorX: 0.5, anchorY: 0.5 }); if (self.type === 'stone') { graphics.scale.set(0.5); } self.velocityX = 0; self.velocityY = 0; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Apply gravity - reduced for enemy projectiles to make them move slower through air if (self.isEnemyProjectile) { self.velocityY += 0.24; // Slightly increased gravity for enemy projectiles } else { self.velocityY += 0.3; // Normal gravity for player projectiles } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ // Game state management var gameStarted = false; var mainMenuContainer = null; var player; var enemies = []; var projectiles = []; var enemyProjectiles = []; var backgroundTiles = []; var inventorySlots = []; var trajectoryDots = []; var selectedItem = null; var selectedSlot = null; // Global variables for item info display var itemInfoDisplay = null; var infoItemSprite = null; var infoItemNameText = null; var infoItemNameTextShadow = null; var infoItemDescText = null; var infoItemDescTextShadow = null; var infoEffectText = null; var infoEffectTextShadow = null; var isDragging = false; var dragStartX = 0; var dragStartY = 0; var launchPower = 0; var launchAngle = 0; var enemySpawnTimer = 0; var backgroundSpawnTimer = 0; // Player health system var playerHealth = 70; var maxPlayerHealth = 70; // Wave system variables var currentWave = 1; var maxWaves = 5; var enemiesPerWave = 2; var enemiesSpawnedInWave = 0; var waveCompleted = false; // Wind system variables var windPower = 0; // Range -12 to +12 var windChangeTimer = 0; var windPowerSprites = []; // Player ellipse variable var playerEllipse = null; // Score text variables var scoreText = null; var scoreTextShadow = null; // Health bar variables var healthBarFill = null; var healthText = null; var healthBarContainer = null; // Wind logo variable var windLogo = null; // Create main menu function createMainMenu() { mainMenuContainer = new Container(); game.addChild(mainMenuContainer); // Main menu background var menuBg = mainMenuContainer.attachAsset('mainbackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 20.48, scaleY: 27.32 }); // Game title var titleText = new Text2('STONE WARRIOR', { size: 120, fill: '#FFD700', stroke: '#000000', strokeThickness: 8 }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; mainMenuContainer.addChild(titleText); // New game button background var buttonBg = mainMenuContainer.attachAsset('inventorySlot', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1500, scaleX: 4, scaleY: 1.5, tint: 0x4A4A4A }); // New game button text var buttonText = new Text2('NEW GAME', { size: 60, fill: '#FFFFFF', stroke: '#000000', strokeThickness: 4 }); buttonText.anchor.set(0.5, 0.5); buttonText.x = 1024; buttonText.y = 1500; mainMenuContainer.addChild(buttonText); // Button interaction buttonBg.down = function (x, y, obj) { startGame(); }; buttonText.down = function (x, y, obj) { startGame(); }; } function startGame() { if (gameStarted) return; gameStarted = true; // Remove main menu if (mainMenuContainer) { mainMenuContainer.destroy(); mainMenuContainer = null; } // Initialize game content initializeGameContent(); } function initializeGameContent() { // Initialize background // Use enough tiles to cover the screen horizontally, based on tile width var initialTile = new BackgroundTile(); var tileWidth = initialTile.tileWidth; initialTile.x = 0; initialTile.y = 0; backgroundTiles.push(initialTile); game.addChild(initialTile); var numTiles = Math.ceil(2048 / tileWidth) + 2; // +2 for seamless wrap for (var i = 1; i < numTiles; i++) { var tile = new BackgroundTile(); tile.x = i * tileWidth; tile.y = 0; backgroundTiles.push(tile); game.addChild(tile); } // Add texture1 to the bottom-right corner, flush with the edges var texture1 = game.addChild(LK.getAsset('texture1', { anchorX: 1, anchorY: 1, x: 2048, y: 2732 })); // Item Info Display itemInfoDisplay = game.addChild(new Container()); itemInfoDisplay.x = 2048 - 422 / 2; itemInfoDisplay.y = 2732 - 876 + 250; infoItemSprite = null; infoItemNameTextShadow = new Text2('', { size: 50, fill: '#000000', align: 'center' }); infoItemNameTextShadow.anchor.set(0.5, 0); infoItemNameTextShadow.y = 150 + 2; infoItemNameTextShadow.x = 2; itemInfoDisplay.addChild(infoItemNameTextShadow); infoItemNameText = new Text2('', { size: 50, fill: '#FFFFFF', align: 'center' }); infoItemNameText.anchor.set(0.5, 0); infoItemNameText.y = 150; itemInfoDisplay.addChild(infoItemNameText); infoItemDescTextShadow = new Text2('', { size: 50, fill: '#000000', align: 'center' }); infoItemDescTextShadow.anchor.set(0.5, 0); infoItemDescTextShadow.y = 212 + 2; infoItemDescTextShadow.x = 2; itemInfoDisplay.addChild(infoItemDescTextShadow); infoItemDescText = new Text2('', { size: 50, fill: '#FFFFFF', align: 'center' }); infoItemDescText.anchor.set(0.5, 0); infoItemDescText.y = 212; itemInfoDisplay.addChild(infoItemDescText); infoEffectTextShadow = new Text2('', { size: 50, fill: '#000000', align: 'center' }); infoEffectTextShadow.anchor.set(0.5, 0); infoEffectTextShadow.y = 275 + 2; infoEffectTextShadow.x = 2; itemInfoDisplay.addChild(infoEffectTextShadow); infoEffectText = new Text2('', { size: 50, fill: '#FFFFFF', align: 'center' }); infoEffectText.anchor.set(0.5, 0); infoEffectText.y = 275; itemInfoDisplay.addChild(infoEffectText); // Item info update function - moved to global scope function updateItemInfo(itemType) { if (infoItemSprite) { infoItemSprite.destroy(); infoItemSprite = null; } if (!itemType || itemType === 'empty') { infoItemNameText.setText(''); infoItemDescText.setText(''); infoEffectText.setText(''); infoItemNameTextShadow.setText(''); infoItemDescTextShadow.setText(''); infoEffectTextShadow.setText(''); return; } infoItemSprite = itemInfoDisplay.attachAsset(itemType, { anchorX: 0.5, anchorY: 0.5 }); infoItemSprite.scale.set(2.5); itemInfoDisplay.setChildIndex(infoItemSprite, 0); if (itemType === 'poisonstone') { infoItemNameText.setText('Poison Stone'); infoItemNameTextShadow.setText('Poison Stone'); infoItemDescText.setText('Special projectile'); infoItemDescTextShadow.setText('Special projectile'); infoEffectText.setText('poison effect'); infoEffectTextShadow.setText('poison effect'); } else { infoItemNameText.setText('Normal Stone'); infoItemNameTextShadow.setText('Normal Stone'); infoItemDescText.setText('Basic projectile'); infoItemDescTextShadow.setText('Basic projectile'); infoEffectText.setText('no extra effect'); infoEffectTextShadow.setText('no extra effect'); } } // Add inventory texture to the bottom-left corner, flush with the edges var inventoryTexture = game.addChild(LK.getAsset('inventory', { anchorX: 0, anchorY: 1, x: 0, y: 2732 })); // Create a 9x5 inventory grid on top of the inventory texture // Inventory Grid Configuration var inventoryConfig = { textureWidth: 1624, textureHeight: 876, gridColumns: 9, gridRows: 5, marginTop: 10, marginBottom: 18, marginLeft: 60, marginRight: 60, horizontalSpacing: 10, verticalSpacing: 8 }; // Calculate usable area within the inventory texture var usableWidth = inventoryConfig.textureWidth - inventoryConfig.marginLeft - inventoryConfig.marginRight; var usableHeight = inventoryConfig.textureHeight - inventoryConfig.marginTop - inventoryConfig.marginBottom; // Calculate total spacing between slots var totalHorizontalSpacing = (inventoryConfig.gridColumns - 1) * inventoryConfig.horizontalSpacing; var totalVerticalSpacing = (inventoryConfig.gridRows - 1) * inventoryConfig.verticalSpacing; // Calculate the dimensions for a single slot var slotWidth = (usableWidth - totalHorizontalSpacing) / inventoryConfig.gridColumns; var slotHeight = (usableHeight - totalVerticalSpacing) / inventoryConfig.gridRows; // Position of the inventory texture is at the bottom-left of the screen var inventoryTextureX = 0; var inventoryTextureY = 2732; // Calculate the top-left coordinate of the grid area inside the texture var gridStartX = inventoryTextureX + inventoryConfig.marginLeft; var gridStartY = inventoryTextureY - inventoryConfig.textureHeight + inventoryConfig.marginTop; // Define the initial inventory items var slotIndex = 0; // Create and position the inventory slots in a grid for (var row = 0; row < inventoryConfig.gridRows; row++) { for (var col = 0; col < inventoryConfig.gridColumns; col++) { var itemType = 'empty'; var itemCount = 0; // The first slot gets 20 stones, second gets 10 poison stones if (slotIndex === 0) { itemType = 'stone'; itemCount = 20; } else if (slotIndex === 1) { itemType = 'poisonstone'; itemCount = 10; } var slot = new InventorySlot(itemType, itemCount); // Calculate the center position for the current slot var slotX = gridStartX + col * (slotWidth + inventoryConfig.horizontalSpacing) + slotWidth / 2; var slotY = gridStartY + row * (slotHeight + inventoryConfig.verticalSpacing) + slotHeight / 2; slot.x = slotX; slot.y = slotY; // Scale the slot container to match the calculated dimensions (base asset is 100x100) slot.scale.set(slotWidth / 100, slotHeight / 100); inventorySlots.push(slot); game.addChild(slot); slotIndex++; } } // Initialize player with running animation (after background to appear in front) player = game.addChild(new Container()); var playerGraphics = player.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Create elliptical effect centered on player - enhanced for better collision detection playerEllipse = game.addChild(LK.getAsset('stone', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.33, scaleY: 2.67, alpha: 0, tint: 0x00FFFF })); // Player will use its own container for collision detection // No need for separate hitbox since the player container itself can be used player.x = 440; player.y = 1366; // Position ellipse relative to player playerEllipse.x = player.x; playerEllipse.y = player.y - 20; // 20 pixels above player for better interception // Animation variables player.animationFrame = 0; player.animationFrames = ['player', 'player2', 'player3', 'player2']; player.currentGraphics = playerGraphics; player.isInThrowMode = false; // Start running animation - function now defined in global scope // Start the animation LK.setTimeout(animatePlayer, 200); // Auto-select first available item and display its info if (inventorySlots.length > 0) { var firstSlot = inventorySlots[0]; if (firstSlot && firstSlot.canUse()) { selectedItem = firstSlot.itemType; selectedSlot = firstSlot; firstSlot.select(); updateItemInfo(firstSlot.itemType); } } // Wave display scoreTextShadow = new Text2('Wave: 1', { size: 60, fill: '#000000' }); scoreTextShadow.anchor.set(0.5, 0); scoreTextShadow.x = 3; scoreTextShadow.y = 3; LK.gui.top.addChild(scoreTextShadow); scoreText = new Text2('Wave: 1', { size: 60, fill: '#00FF00' }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Health bar UI - positioned in top-right corner healthBarContainer = new Container(); healthBarContainer.x = -220; // position relative to top-right corner, with 20px margin healthBarContainer.y = 20; // 20px from top LK.gui.topRight.addChild(healthBarContainer); // Black background for health bar var healthBarBg = LK.getAsset('player4', { anchorX: 0.5, anchorY: 0, scaleX: 4, // 100 * 4 = 400px width scaleY: 0.84, // 100 * 0.84 = 84px height (30% thinner) tint: 0x000000 }); healthBarContainer.addChild(healthBarBg); // Red health bar (shows current health percentage) healthBarFill = LK.getAsset('player4', { anchorX: 0.5, anchorY: 0, scaleX: 4, // Will be adjusted based on health percentage scaleY: 0.84, tint: 0xFF0000 }); healthBarContainer.addChild(healthBarFill); // Light pink health text healthText = new Text2('70/70', { size: 40, fill: '#FFB6C1' }); healthText.anchor.set(0.5, 0.5); healthText.x = 0; // centered in container healthText.y = 42; // Center vertically in the 84px bar healthBarContainer.addChild(healthText); // Add windlogo below health bar windLogo = LK.getAsset('windlogo', { anchorX: 0.5, anchorY: 0 }); windLogo.x = 0; // centered horizontally windLogo.y = 94; // positioned just below the health bar (84px + 10px spacing) healthBarContainer.addChild(windLogo); // Initialize health bar display updateHealthBar(); // Initialize wind system windPower = Math.floor(Math.random() * 25) - 12; // Random initial wind -12 to +12 windChangeTimer = 0; // Reset wind change timer createWindPowerSprites(); } // Create wind power indicator sprites function - moved to global scope function createWindPowerSprites() { // Clear existing sprites for (var i = 0; i < windPowerSprites.length; i++) { windPowerSprites[i].destroy(); } windPowerSprites = []; var absWindPower = Math.abs(windPower); var spacing = 12; // Spacing between wind power sprites for (var i = 0; i < absWindPower; i++) { var windSprite = LK.getAsset('windpower', { anchorX: 0.5, anchorY: 0.5 }); if (windPower > 0) { // Right wind (positive) - sprites to the right of windlogo windSprite.x = windLogo.x + 50 + i * spacing; windSprite.y = windLogo.y + windLogo.height / 2; } else { // Left wind (negative) - sprites to the left of windlogo, horizontally flipped windSprite.x = windLogo.x - 50 - i * spacing; windSprite.y = windLogo.y + windLogo.height / 2; windSprite.scale.x = -1; // Horizontal flip for negative wind } healthBarContainer.addChild(windSprite); windPowerSprites.push(windSprite); } } // Health bar update function - moved to global scope function updateHealthBar() { // Update health bar fill based on current health percentage var healthPercentage = playerHealth / maxPlayerHealth; healthBarFill.scale.x = 4 * healthPercentage; // Scale from 0 to 4 (0 to 400px) // Update health text healthText.setText(playerHealth + '/' + maxPlayerHealth); } // Player animation function - moved to global scope function animatePlayer() { // Don't animate if player is in throw mode if (player.isInThrowMode) { return; } // Change to next frame player.animationFrame = (player.animationFrame + 1) % player.animationFrames.length; var nextFrame = player.animationFrames[player.animationFrame]; // Add new frame first var newGraphics = player.attachAsset(nextFrame, { anchorX: 0.5, anchorY: 0.5 }); // Wait 50ms before removing old graphics to prevent disappearing LK.setTimeout(function () { player.removeChild(player.currentGraphics); player.currentGraphics = newGraphics; }, 50); // Schedule next frame change LK.setTimeout(animatePlayer, 200); } // Create main menu on game start createMainMenu(); function updateTrajectory(startX, startY, endX, endY) { // Clear existing trajectory dots for (var i = 0; i < trajectoryDots.length; i++) { trajectoryDots[i].destroy(); } trajectoryDots = []; if (!isDragging) return; var deltaX = startX - endX; var deltaY = startY - endY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); launchPower = Math.min(distance / 10, 30); launchAngle = Math.atan2(deltaY, deltaX); var velocityX = Math.cos(launchAngle) * launchPower; var velocityY = Math.sin(launchAngle) * launchPower; // Create trajectory preview var simX = startX; var simY = startY; var simVelX = velocityX; var simVelY = velocityY; for (var i = 0; i < 60; i++) { if (i % 3 === 0) { var dot = game.addChild(LK.getAsset('trajectoryDot', { anchorX: 0.5, anchorY: 0.5, x: simX, y: simY })); // Ensure trajectory dot appears in front of background game.setChildIndex(dot, game.children.length - 1); trajectoryDots.push(dot); } simX += simVelX; simY += simVelY; simVelY += 0.3; // Gravity if (simY > 2732) break; } } function launchProjectile(startX, startY, endX, endY) { if (!selectedItem || !selectedSlot || !selectedSlot.canUse()) return; var deltaX = startX - endX; var deltaY = startY - endY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); launchPower = Math.min(distance / 10, 30); launchAngle = Math.atan2(deltaY, deltaX); var projectile = new Projectile(selectedItem); projectile.x = startX; projectile.y = startY; projectile.velocityX = Math.cos(launchAngle) * launchPower; projectile.velocityY = Math.sin(launchAngle) * launchPower; projectiles.push(projectile); game.addChild(projectile); // Ensure projectile appears in front of background game.setChildIndex(projectile, game.children.length - 1); selectedSlot.use(); // If the slot is now empty, deselect it. if (!selectedSlot.canUse()) { selectedSlot.deselect(); selectedSlot = null; selectedItem = null; updateItemInfo(null); } LK.getSound('launch').play(); } game.down = function (x, y, obj) { // Only handle game interactions if game has started if (!gameStarted) return; // Check if touch/click is within expanded area around player (200px radius for easier interaction) var distanceToPlayer = Math.sqrt((x - player.x) * (x - player.x) + (y - player.y) * (y - player.y)); if (selectedItem && distanceToPlayer < 200) { isDragging = true; dragStartX = player.x; dragStartY = player.y; // Stop running animation by setting flag player.isInThrowMode = true; // Change player to throw texture player.removeChild(player.currentGraphics); player.currentGraphics = player.attachAsset('playerthrow', { anchorX: 0.5, anchorY: 0.5 }); updateTrajectory(player.x, player.y, x, y); } }; game.move = function (x, y, obj) { // Only handle game interactions if game has started if (!gameStarted) return; if (isDragging) { updateTrajectory(player.x, player.y, x, y); } }; game.up = function (x, y, obj) { // Only handle game interactions if game has started if (!gameStarted) return; if (isDragging) { launchProjectile(player.x, player.y, x, y); isDragging = false; // Exit throw mode and restore running animation player.isInThrowMode = false; player.removeChild(player.currentGraphics); player.currentGraphics = player.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); player.animationFrame = 0; // Reset animation frame // Restart the running animation LK.setTimeout(animatePlayer, 200); // Clear trajectory dots for (var i = 0; i < trajectoryDots.length; i++) { trajectoryDots[i].destroy(); } trajectoryDots = []; } }; game.update = function () { // Only run game logic if game has started if (!gameStarted) return; // Wind system update - change every 30 seconds (1800 ticks) for more dynamic gameplay windChangeTimer++; if (windChangeTimer >= 1800) { // Change wind every 30 seconds for more dynamic gameplay windChangeTimer = 0; var oldWindPower = windPower; // Random wind change between -3 and +3 var windChange = Math.floor(Math.random() * 7) - 3; windPower = Math.max(-12, Math.min(12, windPower + windChange)); // Update wind power sprites if wind changed if (windPower !== oldWindPower) { createWindPowerSprites(); // Add visual feedback for strong wind changes if (Math.abs(windPower) >= 8) { // Flash screen with wind color for strong wind var windColor = windPower > 0 ? 0x00FFFF : 0xFFFF00; // Blue for right wind, yellow for left wind LK.effects.flashScreen(windColor, 500); } } } // Update ellipse position to follow player playerEllipse.x = player.x; playerEllipse.y = player.y - 20; // Keep 20 pixels above player for better interception // Ensure ellipse is always visible in front of projectiles game.setChildIndex(playerEllipse, game.children.length - 1); // Update wave display scoreText.setText('Wave: ' + currentWave); scoreTextShadow.setText('Wave: ' + currentWave); // Wave-based enemy spawning if (currentWave <= maxWaves && enemiesSpawnedInWave < enemiesPerWave) { enemySpawnTimer++; if (enemySpawnTimer > 180) { enemySpawnTimer = 0; var enemy = new Enemy(); enemy.x = 2200; enemy.y = player.y - 80 + Math.random() * 130; enemies.push(enemy); game.addChild(enemy); enemiesSpawnedInWave++; } } // Update background tiles for seamless looping for (var i = 0; i < backgroundTiles.length; i++) { var tile = backgroundTiles[i]; tile.update(); } // If the leftmost tile is fully off screen, move it to the right of the rightmost tile // This creates a seamless infinite scroll if (backgroundTiles.length > 0) { var leftmost = backgroundTiles[0]; var rightmost = backgroundTiles[backgroundTiles.length - 1]; // Use tileWidth from the tile itself for accuracy if (leftmost.x + leftmost.tileWidth < 0) { // Move leftmost tile to the right of the rightmost tile leftmost.x = rightmost.x + rightmost.tileWidth; // Move to end of array backgroundTiles.push(backgroundTiles.shift()); } } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isDead || enemy.x < -200) { enemy.destroy(); enemies.splice(i, 1); } } // Update PLAYER projectiles for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; // Apply enhanced wind effect to projectile projectile.velocityX += windPower * 0.08; // Increased wind effect for more noticeable impact if (projectile.x > 2200 || projectile.y > 2800 || projectile.x < -100) { projectile.destroy(); projectiles.splice(i, 1); continue; } // Collision detection with enemies for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (projectile.hasHit || enemy.isDead) continue; // Check head collision first for damage if (projectile.intersects(enemy.head)) { if (projectile.type === 'stone') { enemy.takeDamage(12); } else if (projectile.type === 'poisonstone') { enemy.takeDamage(10, true); } projectile.hasHit = true; break; // Stop checking other enemies } // Check body/legs collision for damage if (projectile.intersects(enemy.body) || projectile.intersects(enemy.leftLeg) || projectile.intersects(enemy.rightLeg)) { if (projectile.type === 'stone') { enemy.takeDamage(10); } else if (projectile.type === 'poisonstone') { enemy.takeDamage(3, true); } projectile.hasHit = true; break; // Stop checking other enemies } } // If projectile hit something, destroy it and continue to the next projectile if (projectile.hasHit) { projectile.destroy(); projectiles.splice(i, 1); continue; } } // <-- PLAYER PROJECTILE LOOP ENDS HERE // ========================================================================= // Update ENEMY projectiles - NOW INDEPENDENT AND ALWAYS RUNS // ========================================================================= for (var j = enemyProjectiles.length - 1; j >= 0; j--) { var enemyProjectile = enemyProjectiles[j]; // Apply wind effect to enemy projectiles too enemyProjectile.velocityX += windPower * 0.06; // Wind affects enemy projectiles as well // Update projectile position with physics enemyProjectile.update(); // Remove projectiles that are off screen if (enemyProjectile.x > 2200 || enemyProjectile.y > 2800 || enemyProjectile.x < -100) { enemyProjectile.destroy(); enemyProjectiles.splice(j, 1); continue; } // Check collision with player ellipse to bounce stones back var currentIntersectingEllipse = enemyProjectile.intersects(playerEllipse); // Only bounce if we just started intersecting (transition from false to true) and haven't bounced before if (false && !enemyProjectile.lastIntersectingEllipse && currentIntersectingEllipse && !enemyProjectile.hasBouncedOffEllipse) { // Temporarily disabled by adding 'false' // Calculate bounce direction - reverse the velocity with enhanced force enemyProjectile.velocityX = -enemyProjectile.velocityX * 1.8; // Increased bounce force enemyProjectile.velocityY = -enemyProjectile.velocityY * 1.8; // Increased bounce force // Apply additional upward boost to prevent ground collision enemyProjectile.velocityY -= 8; // Move projectile away from ellipse to prevent multiple collisions var deltaX = enemyProjectile.x - playerEllipse.x; var deltaY = enemyProjectile.y - playerEllipse.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance > 0) { deltaX /= distance; deltaY /= distance; enemyProjectile.x += deltaX * 50; // Increased separation distance enemyProjectile.y += deltaY * 50; } // Mark as bounced to prevent multiple bounces enemyProjectile.hasBouncedOffEllipse = true; // Visual feedback - flash the ellipse brighter with tween tween(playerEllipse, { tint: 0xFFFFFF }, { duration: 100, onFinish: function onFinish() { tween(playerEllipse, { tint: 0x00FFFF }, { duration: 200 }); } }); // Add screen shake effect for impact LK.effects.flashScreen(0x00FFFF, 300); } // Update collision tracking enemyProjectile.lastIntersectingEllipse = currentIntersectingEllipse; // Check collision with player (direct hit damage) if (!enemyProjectile.hasBouncedOffEllipse && enemyProjectile.intersects(player)) { // Player takes damage playerHealth -= 10; if (playerHealth < 0) playerHealth = 0; updateHealthBar(); // Visual feedback - flash player red tween(player.currentGraphics, { tint: 0xFF0000 }, { duration: 200, onFinish: function onFinish() { tween(player.currentGraphics, { tint: 0xFFFFFF }, { duration: 300 }); } }); // Remove the projectile enemyProjectile.destroy(); enemyProjectiles.splice(j, 1); // Check for game over if (playerHealth <= 0) { LK.showGameOver(); } continue; } // Check collision with enemies if the projectile has bounced off the ellipse if (enemyProjectile.hasBouncedOffEllipse) { for (var k = enemies.length - 1; k >= 0; k--) { var enemy = enemies[k]; if (enemy.isDead) continue; // Check collision with enemy parts - enhanced damage for bounced projectiles if (enemyProjectile.intersects(enemy.head)) { enemy.takeDamage(30); // Increased damage for bounced projectiles // Visual effect for bounced projectile hit with tween tween(enemy.head, { tint: 0x00FFFF }, { duration: 150, onFinish: function onFinish() { tween(enemy.head, { tint: 0xFFFFFF }, { duration: 250 }); } }); enemyProjectile.destroy(); enemyProjectiles.splice(j, 1); break; } if (enemyProjectile.intersects(enemy.body) || enemyProjectile.intersects(enemy.leftLeg) || enemyProjectile.intersects(enemy.rightLeg)) { enemy.takeDamage(22); // Increased damage for bounced projectiles // Visual effect for bounced projectile hit with tween if (enemyProjectile.intersects(enemy.body)) { tween(enemy.body, { tint: 0x00FFFF }, { duration: 150, onFinish: function onFinish() { tween(enemy.body, { tint: 0xFFFFFF }, { duration: 250 }); } }); } enemyProjectile.destroy(); enemyProjectiles.splice(j, 1); break; } } } } // <-- ENEMY PROJECTILE LOOP ENDS HERE // Check wave completion if (enemies.length === 0 && enemiesSpawnedInWave >= enemiesPerWave && currentWave <= maxWaves) { // Wave completed LK.setScore(currentWave); // Set score to current wave number currentWave++; enemiesSpawnedInWave = 0; enemySpawnTimer = 0; // Change wind when wave changes var oldWindPower = windPower; // Random wind change between -4 and +4 for wave transitions var windChange = Math.floor(Math.random() * 9) - 4; windPower = Math.max(-12, Math.min(12, windPower + windChange)); // Update wind power sprites if wind changed if (windPower !== oldWindPower) { createWindPowerSprites(); } // Reset wind timer when wave changes windChangeTimer = 0; if (currentWave > maxWaves) { // All waves completed - show victory LK.showYouWin(); } } // Replenish inventory occasionally if (LK.ticks % 600 === 0) { for (var i = 0; i < inventorySlots.length; i++) { var slot = inventorySlots[i]; if (slot.count < slot.maxCount) { slot.count = Math.min(slot.count + 1, slot.maxCount); slot.updateCount(); } } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BackgroundTile = Container.expand(function () {
var self = Container.call(this);
// Randomly pick one of two backgrounds for variety
var bgTypes = ['background', 'background2'];
var bgType = bgTypes[Math.floor(Math.random() * bgTypes.length)];
var graphics = self.attachAsset(bgType, {
anchorX: 0,
anchorY: 0
});
// Slower speed for new game pace
self.speed = 0.7;
// Store width for seamless tiling
self.tileWidth = graphics.width;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.health = 30;
self.isDead = false;
self.speed = 1;
self.isMoving = true;
self.stopTimer = 0;
self.throwTimer = 0;
self.throwCooldown = 0;
self.isPoisoned = false;
self.poisonTimer = 0;
// Randomize parts
var headTypes = ['enemyHead1', 'enemyHead2', 'enemyHead3'];
var bodyTypes = ['enemyBody1', 'enemyBody2', 'enemyBody3'];
var legTypes = ['enemyLeg1', 'enemyLeg2'];
var headType = headTypes[Math.floor(Math.random() * headTypes.length)];
var bodyType = bodyTypes[Math.floor(Math.random() * bodyTypes.length)];
var legType = legTypes[Math.floor(Math.random() * legTypes.length)];
// Store texture types for later use in poison effects
self.headType = headType;
self.bodyType = bodyType;
self.legType = legType;
// Create parts with adjusted positioning
var head = self.attachAsset(headType, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -105
});
var body = self.attachAsset(bodyType, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
var leftLeg = self.attachAsset(legType, {
anchorX: 0.5,
anchorY: 0.5,
x: -20,
y: 95
});
var rightLeg = self.attachAsset(legType, {
anchorX: 0.5,
anchorY: 0.5,
x: 20,
y: 95
});
self.head = head;
self.body = body;
self.leftLeg = leftLeg;
self.rightLeg = rightLeg;
// Set up layering - legs under body, head on top
self.setChildIndex(leftLeg, 0);
self.setChildIndex(rightLeg, 1);
self.setChildIndex(body, 2);
self.setChildIndex(head, 3);
// Walking animation variables
self.walkTimer = 0;
self.walkSpeed = 0.3;
self.update = function () {
// Enemy movement - stop when reaching x=1900, then walk random 0-440 pixels more
if (self.isMoving) {
self.x -= self.speed;
self.stopTimer++;
// Initialize target stop distance when enemy first reaches x=1900
if (self.x <= 1900 && !self.targetStopDistance) {
var randomExtraDistance = Math.random() * 440; // Random 0-440 pixels
self.targetStopDistance = 1900 - randomExtraDistance;
}
// Stop when reaching the calculated target distance
if (self.targetStopDistance && self.x <= self.targetStopDistance) {
self.isMoving = false;
self.stopTimer = 0;
}
}
// Throwing logic when stationary
if (!self.isMoving) {
self.throwCooldown++;
// Random chance to throw every 6-12 seconds (further reduced frequency)
if (self.throwCooldown >= 360 + Math.random() * 360) {
self.throwCooldown = 0;
self.throwProjectile();
}
}
// Poison damage logic
if (self.isPoisoned && !self.isDead) {
self.poisonTimer++;
// Apply poison damage every 3 seconds (180 ticks at 60 FPS)
if (self.poisonTimer >= 180) {
self.poisonTimer = 0;
self.health -= 3;
// Visual poison effect - green flash
var parts = [self.head, self.body, self.leftLeg, self.rightLeg];
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
tween.stop(part, {
tint: true
});
part.tint = 0x00FF00;
tween(part, {
tint: 0xFFFFFF
}, {
duration: 250
});
}
if (self.health <= 0) {
self.isDead = true;
}
}
// Poison visual effect - continuous green tint when poisoned
var parts = [self.head, self.body, self.leftLeg, self.rightLeg];
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
if (self.isPoisoned && !self.isDead) {
// Apply constant green tint to show poison status
if (part.tint !== 0x00FF00) {
part.tint = 0x00FF00;
}
// Create floating green effect every 180 ticks (3 seconds)
if (LK.ticks % 180 === 0) {
// Determine which texture to use based on the part
var textureType = 'enemyBody1'; // default fallback
if (part === self.head) {
textureType = self.headType;
} else if (part === self.body) {
textureType = self.bodyType;
} else if (part === self.leftLeg || part === self.rightLeg) {
textureType = self.legType;
}
// Create a temporary green overlay for the floating effect using the correct texture
var greenOverlay = game.addChild(LK.getAsset(textureType, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + part.x,
y: self.y + part.y,
alpha: 0.9,
tint: 0x00FF00
}));
// Head part should appear on top of everything
if (part === self.head) {
game.setChildIndex(greenOverlay, game.children.length - 1);
}
// Make it bigger and fade out
tween(greenOverlay, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (greenOverlay.parent) {
greenOverlay.destroy();
}
}
});
}
} else {
// Remove green tint when not poisoned
if (part.tint === 0x00FF00) {
part.tint = 0xFFFFFF;
}
}
}
}
// Walking animation
self.walkTimer += self.walkSpeed;
var leftLegOffset = Math.sin(self.walkTimer) * 15;
var rightLegOffset = Math.sin(self.walkTimer + Math.PI) * 15;
// Animate legs - one forward while other goes back
tween(self.leftLeg, {
x: -20 + leftLegOffset
}, {
duration: 100
});
tween(self.rightLeg, {
x: 20 + rightLegOffset
}, {
duration: 100
});
};
self.throwProjectile = function () {
// Calculate direction to player with low accuracy
var targetX = player.x + (Math.random() - 0.5) * 800; // Even more inaccuracy for longer range
var targetY = player.y + (Math.random() - 0.5) * 400 - 800; // Aim much higher up for greater arc
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Create enemy projectile
var projectile = new Projectile('stone');
projectile.x = self.x;
projectile.y = self.y;
projectile.isEnemyProjectile = true;
// Initialize collision tracking variables with explicit values - must be done before adding to arrays
projectile.lastIntersectingEllipse = false;
projectile.hasBouncedOffEllipse = false;
projectile.lastX = projectile.x;
projectile.lastY = projectile.y;
// Ensure projectile has proper physics update from start
projectile.isEnemyProjectile = true;
// Calculate velocity with much higher speed for longer range and higher arc
var speed = 18 + Math.random() * 8; // Further increased enemy projectile speed for more range
var angle = Math.atan2(deltaY, deltaX);
projectile.velocityX = Math.cos(angle) * speed;
projectile.velocityY = Math.sin(angle) * speed;
// Ensure projectile is properly added to arrays and game
enemyProjectiles.push(projectile);
game.addChild(projectile);
// Ensure projectile appears in front of background
game.setChildIndex(projectile, game.children.length - 1);
};
self.takeDamage = function (damage, isPoisoned) {
if (self.isDead) return;
self.health -= damage;
// Apply poison effect if projectile was poison stone
if (isPoisoned && !self.isPoisoned) {
self.isPoisoned = true;
self.poisonTimer = 0;
}
// --- HIT FEEDBACK ---
// 1. Flash all parts red for visual impact
var parts = [self.head, self.body, self.leftLeg, self.rightLeg];
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
tween.stop(part, {
tint: true
});
part.tint = 0xFF0000;
tween(part, {
tint: 0xFFFFFF
}, {
duration: 250
});
}
// 2. Knockback effect for physical impact, only when enemy is stationary
if (!self.isMoving) {
var knockbackDistance = 25;
var originalX = self.x;
tween.stop(self, {
x: true
});
// Quick knockback
tween(self, {
x: originalX + knockbackDistance
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
// If enemy still exists, tween back with a bouncy effect
if (self.parent) {
tween(self, {
x: originalX
}, {
duration: 400,
easing: tween.elasticOut
});
}
}
});
}
if (self.health <= 0) {
self.isDead = true;
}
};
return self;
});
var InventorySlot = Container.expand(function (itemType, count) {
var self = Container.call(this);
self.itemType = itemType || 'empty';
self.count = count || 0;
self.maxCount = 99;
self.selectionBorder = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
self.selectionBorder.tint = 0xFFFF00;
self.selectionBorder.scale.set(1.1);
self.selectionBorder.visible = false;
var slotBg = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
self.itemSprite = null;
if (self.itemType !== 'empty') {
self.itemSprite = self.attachAsset(self.itemType, {
anchorX: 0.5,
anchorY: 0.5
});
var scale = Math.min(slotBg.width / self.itemSprite.width, slotBg.height / self.itemSprite.height) * 0.9;
self.itemSprite.scale.set(scale);
}
self.countText = new Text2(self.count.toString(), {
size: 25,
fill: '#FFFFFF',
stroke: '#000000',
strokeThickness: 5
});
self.countText.anchor.set(0, 1);
self.countText.x = -slotBg.width / 2 + 5;
self.countText.y = slotBg.height / 2 - 5;
self.addChild(self.countText);
self.updateCount = function () {
self.countText.setText(self.count.toString());
if (self.count <= 1 || self.itemType === 'empty') {
self.countText.visible = false;
} else {
self.countText.visible = true;
}
if (self.itemSprite) {
if (self.count <= 0 || self.itemType === 'empty') {
self.itemSprite.visible = false;
} else {
self.itemSprite.visible = true;
}
}
};
self.updateCount();
self.canUse = function () {
return self.count > 0 && self.itemType !== 'empty';
};
self.use = function () {
if (self.canUse()) {
self.count--;
self.updateCount();
return true;
}
return false;
};
self.select = function () {
self.selectionBorder.visible = true;
};
self.deselect = function () {
self.selectionBorder.visible = false;
};
self.down = function (x, y, obj) {
if (self.canUse() && selectedSlot !== self) {
if (selectedSlot) {
selectedSlot.deselect();
}
selectedItem = self.itemType;
selectedSlot = self;
self.select();
updateItemInfo(self.itemType);
}
};
return self;
});
var Projectile = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = 8;
self.hasHit = false;
var projectileAsset = 'projectile';
if (self.type === 'stone') {
projectileAsset = 'stone';
} else if (self.type === 'poisonstone') {
projectileAsset = 'poisonstone';
}
var graphics = self.attachAsset(projectileAsset, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.type === 'stone') {
graphics.scale.set(0.5);
}
self.velocityX = 0;
self.velocityY = 0;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Apply gravity - reduced for enemy projectiles to make them move slower through air
if (self.isEnemyProjectile) {
self.velocityY += 0.24; // Slightly increased gravity for enemy projectiles
} else {
self.velocityY += 0.3; // Normal gravity for player projectiles
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game state management
var gameStarted = false;
var mainMenuContainer = null;
var player;
var enemies = [];
var projectiles = [];
var enemyProjectiles = [];
var backgroundTiles = [];
var inventorySlots = [];
var trajectoryDots = [];
var selectedItem = null;
var selectedSlot = null;
// Global variables for item info display
var itemInfoDisplay = null;
var infoItemSprite = null;
var infoItemNameText = null;
var infoItemNameTextShadow = null;
var infoItemDescText = null;
var infoItemDescTextShadow = null;
var infoEffectText = null;
var infoEffectTextShadow = null;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var launchPower = 0;
var launchAngle = 0;
var enemySpawnTimer = 0;
var backgroundSpawnTimer = 0;
// Player health system
var playerHealth = 70;
var maxPlayerHealth = 70;
// Wave system variables
var currentWave = 1;
var maxWaves = 5;
var enemiesPerWave = 2;
var enemiesSpawnedInWave = 0;
var waveCompleted = false;
// Wind system variables
var windPower = 0; // Range -12 to +12
var windChangeTimer = 0;
var windPowerSprites = [];
// Player ellipse variable
var playerEllipse = null;
// Score text variables
var scoreText = null;
var scoreTextShadow = null;
// Health bar variables
var healthBarFill = null;
var healthText = null;
var healthBarContainer = null;
// Wind logo variable
var windLogo = null;
// Create main menu
function createMainMenu() {
mainMenuContainer = new Container();
game.addChild(mainMenuContainer);
// Main menu background
var menuBg = mainMenuContainer.attachAsset('mainbackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 20.48,
scaleY: 27.32
});
// Game title
var titleText = new Text2('STONE WARRIOR', {
size: 120,
fill: '#FFD700',
stroke: '#000000',
strokeThickness: 8
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
mainMenuContainer.addChild(titleText);
// New game button background
var buttonBg = mainMenuContainer.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1500,
scaleX: 4,
scaleY: 1.5,
tint: 0x4A4A4A
});
// New game button text
var buttonText = new Text2('NEW GAME', {
size: 60,
fill: '#FFFFFF',
stroke: '#000000',
strokeThickness: 4
});
buttonText.anchor.set(0.5, 0.5);
buttonText.x = 1024;
buttonText.y = 1500;
mainMenuContainer.addChild(buttonText);
// Button interaction
buttonBg.down = function (x, y, obj) {
startGame();
};
buttonText.down = function (x, y, obj) {
startGame();
};
}
function startGame() {
if (gameStarted) return;
gameStarted = true;
// Remove main menu
if (mainMenuContainer) {
mainMenuContainer.destroy();
mainMenuContainer = null;
}
// Initialize game content
initializeGameContent();
}
function initializeGameContent() {
// Initialize background
// Use enough tiles to cover the screen horizontally, based on tile width
var initialTile = new BackgroundTile();
var tileWidth = initialTile.tileWidth;
initialTile.x = 0;
initialTile.y = 0;
backgroundTiles.push(initialTile);
game.addChild(initialTile);
var numTiles = Math.ceil(2048 / tileWidth) + 2; // +2 for seamless wrap
for (var i = 1; i < numTiles; i++) {
var tile = new BackgroundTile();
tile.x = i * tileWidth;
tile.y = 0;
backgroundTiles.push(tile);
game.addChild(tile);
}
// Add texture1 to the bottom-right corner, flush with the edges
var texture1 = game.addChild(LK.getAsset('texture1', {
anchorX: 1,
anchorY: 1,
x: 2048,
y: 2732
}));
// Item Info Display
itemInfoDisplay = game.addChild(new Container());
itemInfoDisplay.x = 2048 - 422 / 2;
itemInfoDisplay.y = 2732 - 876 + 250;
infoItemSprite = null;
infoItemNameTextShadow = new Text2('', {
size: 50,
fill: '#000000',
align: 'center'
});
infoItemNameTextShadow.anchor.set(0.5, 0);
infoItemNameTextShadow.y = 150 + 2;
infoItemNameTextShadow.x = 2;
itemInfoDisplay.addChild(infoItemNameTextShadow);
infoItemNameText = new Text2('', {
size: 50,
fill: '#FFFFFF',
align: 'center'
});
infoItemNameText.anchor.set(0.5, 0);
infoItemNameText.y = 150;
itemInfoDisplay.addChild(infoItemNameText);
infoItemDescTextShadow = new Text2('', {
size: 50,
fill: '#000000',
align: 'center'
});
infoItemDescTextShadow.anchor.set(0.5, 0);
infoItemDescTextShadow.y = 212 + 2;
infoItemDescTextShadow.x = 2;
itemInfoDisplay.addChild(infoItemDescTextShadow);
infoItemDescText = new Text2('', {
size: 50,
fill: '#FFFFFF',
align: 'center'
});
infoItemDescText.anchor.set(0.5, 0);
infoItemDescText.y = 212;
itemInfoDisplay.addChild(infoItemDescText);
infoEffectTextShadow = new Text2('', {
size: 50,
fill: '#000000',
align: 'center'
});
infoEffectTextShadow.anchor.set(0.5, 0);
infoEffectTextShadow.y = 275 + 2;
infoEffectTextShadow.x = 2;
itemInfoDisplay.addChild(infoEffectTextShadow);
infoEffectText = new Text2('', {
size: 50,
fill: '#FFFFFF',
align: 'center'
});
infoEffectText.anchor.set(0.5, 0);
infoEffectText.y = 275;
itemInfoDisplay.addChild(infoEffectText);
// Item info update function - moved to global scope
function updateItemInfo(itemType) {
if (infoItemSprite) {
infoItemSprite.destroy();
infoItemSprite = null;
}
if (!itemType || itemType === 'empty') {
infoItemNameText.setText('');
infoItemDescText.setText('');
infoEffectText.setText('');
infoItemNameTextShadow.setText('');
infoItemDescTextShadow.setText('');
infoEffectTextShadow.setText('');
return;
}
infoItemSprite = itemInfoDisplay.attachAsset(itemType, {
anchorX: 0.5,
anchorY: 0.5
});
infoItemSprite.scale.set(2.5);
itemInfoDisplay.setChildIndex(infoItemSprite, 0);
if (itemType === 'poisonstone') {
infoItemNameText.setText('Poison Stone');
infoItemNameTextShadow.setText('Poison Stone');
infoItemDescText.setText('Special projectile');
infoItemDescTextShadow.setText('Special projectile');
infoEffectText.setText('poison effect');
infoEffectTextShadow.setText('poison effect');
} else {
infoItemNameText.setText('Normal Stone');
infoItemNameTextShadow.setText('Normal Stone');
infoItemDescText.setText('Basic projectile');
infoItemDescTextShadow.setText('Basic projectile');
infoEffectText.setText('no extra effect');
infoEffectTextShadow.setText('no extra effect');
}
}
// Add inventory texture to the bottom-left corner, flush with the edges
var inventoryTexture = game.addChild(LK.getAsset('inventory', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
}));
// Create a 9x5 inventory grid on top of the inventory texture
// Inventory Grid Configuration
var inventoryConfig = {
textureWidth: 1624,
textureHeight: 876,
gridColumns: 9,
gridRows: 5,
marginTop: 10,
marginBottom: 18,
marginLeft: 60,
marginRight: 60,
horizontalSpacing: 10,
verticalSpacing: 8
};
// Calculate usable area within the inventory texture
var usableWidth = inventoryConfig.textureWidth - inventoryConfig.marginLeft - inventoryConfig.marginRight;
var usableHeight = inventoryConfig.textureHeight - inventoryConfig.marginTop - inventoryConfig.marginBottom;
// Calculate total spacing between slots
var totalHorizontalSpacing = (inventoryConfig.gridColumns - 1) * inventoryConfig.horizontalSpacing;
var totalVerticalSpacing = (inventoryConfig.gridRows - 1) * inventoryConfig.verticalSpacing;
// Calculate the dimensions for a single slot
var slotWidth = (usableWidth - totalHorizontalSpacing) / inventoryConfig.gridColumns;
var slotHeight = (usableHeight - totalVerticalSpacing) / inventoryConfig.gridRows;
// Position of the inventory texture is at the bottom-left of the screen
var inventoryTextureX = 0;
var inventoryTextureY = 2732;
// Calculate the top-left coordinate of the grid area inside the texture
var gridStartX = inventoryTextureX + inventoryConfig.marginLeft;
var gridStartY = inventoryTextureY - inventoryConfig.textureHeight + inventoryConfig.marginTop;
// Define the initial inventory items
var slotIndex = 0;
// Create and position the inventory slots in a grid
for (var row = 0; row < inventoryConfig.gridRows; row++) {
for (var col = 0; col < inventoryConfig.gridColumns; col++) {
var itemType = 'empty';
var itemCount = 0;
// The first slot gets 20 stones, second gets 10 poison stones
if (slotIndex === 0) {
itemType = 'stone';
itemCount = 20;
} else if (slotIndex === 1) {
itemType = 'poisonstone';
itemCount = 10;
}
var slot = new InventorySlot(itemType, itemCount);
// Calculate the center position for the current slot
var slotX = gridStartX + col * (slotWidth + inventoryConfig.horizontalSpacing) + slotWidth / 2;
var slotY = gridStartY + row * (slotHeight + inventoryConfig.verticalSpacing) + slotHeight / 2;
slot.x = slotX;
slot.y = slotY;
// Scale the slot container to match the calculated dimensions (base asset is 100x100)
slot.scale.set(slotWidth / 100, slotHeight / 100);
inventorySlots.push(slot);
game.addChild(slot);
slotIndex++;
}
}
// Initialize player with running animation (after background to appear in front)
player = game.addChild(new Container());
var playerGraphics = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Create elliptical effect centered on player - enhanced for better collision detection
playerEllipse = game.addChild(LK.getAsset('stone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.33,
scaleY: 2.67,
alpha: 0,
tint: 0x00FFFF
}));
// Player will use its own container for collision detection
// No need for separate hitbox since the player container itself can be used
player.x = 440;
player.y = 1366;
// Position ellipse relative to player
playerEllipse.x = player.x;
playerEllipse.y = player.y - 20; // 20 pixels above player for better interception
// Animation variables
player.animationFrame = 0;
player.animationFrames = ['player', 'player2', 'player3', 'player2'];
player.currentGraphics = playerGraphics;
player.isInThrowMode = false;
// Start running animation - function now defined in global scope
// Start the animation
LK.setTimeout(animatePlayer, 200);
// Auto-select first available item and display its info
if (inventorySlots.length > 0) {
var firstSlot = inventorySlots[0];
if (firstSlot && firstSlot.canUse()) {
selectedItem = firstSlot.itemType;
selectedSlot = firstSlot;
firstSlot.select();
updateItemInfo(firstSlot.itemType);
}
}
// Wave display
scoreTextShadow = new Text2('Wave: 1', {
size: 60,
fill: '#000000'
});
scoreTextShadow.anchor.set(0.5, 0);
scoreTextShadow.x = 3;
scoreTextShadow.y = 3;
LK.gui.top.addChild(scoreTextShadow);
scoreText = new Text2('Wave: 1', {
size: 60,
fill: '#00FF00'
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Health bar UI - positioned in top-right corner
healthBarContainer = new Container();
healthBarContainer.x = -220; // position relative to top-right corner, with 20px margin
healthBarContainer.y = 20; // 20px from top
LK.gui.topRight.addChild(healthBarContainer);
// Black background for health bar
var healthBarBg = LK.getAsset('player4', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4,
// 100 * 4 = 400px width
scaleY: 0.84,
// 100 * 0.84 = 84px height (30% thinner)
tint: 0x000000
});
healthBarContainer.addChild(healthBarBg);
// Red health bar (shows current health percentage)
healthBarFill = LK.getAsset('player4', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4,
// Will be adjusted based on health percentage
scaleY: 0.84,
tint: 0xFF0000
});
healthBarContainer.addChild(healthBarFill);
// Light pink health text
healthText = new Text2('70/70', {
size: 40,
fill: '#FFB6C1'
});
healthText.anchor.set(0.5, 0.5);
healthText.x = 0; // centered in container
healthText.y = 42; // Center vertically in the 84px bar
healthBarContainer.addChild(healthText);
// Add windlogo below health bar
windLogo = LK.getAsset('windlogo', {
anchorX: 0.5,
anchorY: 0
});
windLogo.x = 0; // centered horizontally
windLogo.y = 94; // positioned just below the health bar (84px + 10px spacing)
healthBarContainer.addChild(windLogo);
// Initialize health bar display
updateHealthBar();
// Initialize wind system
windPower = Math.floor(Math.random() * 25) - 12; // Random initial wind -12 to +12
windChangeTimer = 0; // Reset wind change timer
createWindPowerSprites();
}
// Create wind power indicator sprites function - moved to global scope
function createWindPowerSprites() {
// Clear existing sprites
for (var i = 0; i < windPowerSprites.length; i++) {
windPowerSprites[i].destroy();
}
windPowerSprites = [];
var absWindPower = Math.abs(windPower);
var spacing = 12; // Spacing between wind power sprites
for (var i = 0; i < absWindPower; i++) {
var windSprite = LK.getAsset('windpower', {
anchorX: 0.5,
anchorY: 0.5
});
if (windPower > 0) {
// Right wind (positive) - sprites to the right of windlogo
windSprite.x = windLogo.x + 50 + i * spacing;
windSprite.y = windLogo.y + windLogo.height / 2;
} else {
// Left wind (negative) - sprites to the left of windlogo, horizontally flipped
windSprite.x = windLogo.x - 50 - i * spacing;
windSprite.y = windLogo.y + windLogo.height / 2;
windSprite.scale.x = -1; // Horizontal flip for negative wind
}
healthBarContainer.addChild(windSprite);
windPowerSprites.push(windSprite);
}
}
// Health bar update function - moved to global scope
function updateHealthBar() {
// Update health bar fill based on current health percentage
var healthPercentage = playerHealth / maxPlayerHealth;
healthBarFill.scale.x = 4 * healthPercentage; // Scale from 0 to 4 (0 to 400px)
// Update health text
healthText.setText(playerHealth + '/' + maxPlayerHealth);
}
// Player animation function - moved to global scope
function animatePlayer() {
// Don't animate if player is in throw mode
if (player.isInThrowMode) {
return;
}
// Change to next frame
player.animationFrame = (player.animationFrame + 1) % player.animationFrames.length;
var nextFrame = player.animationFrames[player.animationFrame];
// Add new frame first
var newGraphics = player.attachAsset(nextFrame, {
anchorX: 0.5,
anchorY: 0.5
});
// Wait 50ms before removing old graphics to prevent disappearing
LK.setTimeout(function () {
player.removeChild(player.currentGraphics);
player.currentGraphics = newGraphics;
}, 50);
// Schedule next frame change
LK.setTimeout(animatePlayer, 200);
}
// Create main menu on game start
createMainMenu();
function updateTrajectory(startX, startY, endX, endY) {
// Clear existing trajectory dots
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
if (!isDragging) return;
var deltaX = startX - endX;
var deltaY = startY - endY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
launchPower = Math.min(distance / 10, 30);
launchAngle = Math.atan2(deltaY, deltaX);
var velocityX = Math.cos(launchAngle) * launchPower;
var velocityY = Math.sin(launchAngle) * launchPower;
// Create trajectory preview
var simX = startX;
var simY = startY;
var simVelX = velocityX;
var simVelY = velocityY;
for (var i = 0; i < 60; i++) {
if (i % 3 === 0) {
var dot = game.addChild(LK.getAsset('trajectoryDot', {
anchorX: 0.5,
anchorY: 0.5,
x: simX,
y: simY
}));
// Ensure trajectory dot appears in front of background
game.setChildIndex(dot, game.children.length - 1);
trajectoryDots.push(dot);
}
simX += simVelX;
simY += simVelY;
simVelY += 0.3; // Gravity
if (simY > 2732) break;
}
}
function launchProjectile(startX, startY, endX, endY) {
if (!selectedItem || !selectedSlot || !selectedSlot.canUse()) return;
var deltaX = startX - endX;
var deltaY = startY - endY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
launchPower = Math.min(distance / 10, 30);
launchAngle = Math.atan2(deltaY, deltaX);
var projectile = new Projectile(selectedItem);
projectile.x = startX;
projectile.y = startY;
projectile.velocityX = Math.cos(launchAngle) * launchPower;
projectile.velocityY = Math.sin(launchAngle) * launchPower;
projectiles.push(projectile);
game.addChild(projectile);
// Ensure projectile appears in front of background
game.setChildIndex(projectile, game.children.length - 1);
selectedSlot.use();
// If the slot is now empty, deselect it.
if (!selectedSlot.canUse()) {
selectedSlot.deselect();
selectedSlot = null;
selectedItem = null;
updateItemInfo(null);
}
LK.getSound('launch').play();
}
game.down = function (x, y, obj) {
// Only handle game interactions if game has started
if (!gameStarted) return;
// Check if touch/click is within expanded area around player (200px radius for easier interaction)
var distanceToPlayer = Math.sqrt((x - player.x) * (x - player.x) + (y - player.y) * (y - player.y));
if (selectedItem && distanceToPlayer < 200) {
isDragging = true;
dragStartX = player.x;
dragStartY = player.y;
// Stop running animation by setting flag
player.isInThrowMode = true;
// Change player to throw texture
player.removeChild(player.currentGraphics);
player.currentGraphics = player.attachAsset('playerthrow', {
anchorX: 0.5,
anchorY: 0.5
});
updateTrajectory(player.x, player.y, x, y);
}
};
game.move = function (x, y, obj) {
// Only handle game interactions if game has started
if (!gameStarted) return;
if (isDragging) {
updateTrajectory(player.x, player.y, x, y);
}
};
game.up = function (x, y, obj) {
// Only handle game interactions if game has started
if (!gameStarted) return;
if (isDragging) {
launchProjectile(player.x, player.y, x, y);
isDragging = false;
// Exit throw mode and restore running animation
player.isInThrowMode = false;
player.removeChild(player.currentGraphics);
player.currentGraphics = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
player.animationFrame = 0; // Reset animation frame
// Restart the running animation
LK.setTimeout(animatePlayer, 200);
// Clear trajectory dots
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
}
};
game.update = function () {
// Only run game logic if game has started
if (!gameStarted) return;
// Wind system update - change every 30 seconds (1800 ticks) for more dynamic gameplay
windChangeTimer++;
if (windChangeTimer >= 1800) {
// Change wind every 30 seconds for more dynamic gameplay
windChangeTimer = 0;
var oldWindPower = windPower;
// Random wind change between -3 and +3
var windChange = Math.floor(Math.random() * 7) - 3;
windPower = Math.max(-12, Math.min(12, windPower + windChange));
// Update wind power sprites if wind changed
if (windPower !== oldWindPower) {
createWindPowerSprites();
// Add visual feedback for strong wind changes
if (Math.abs(windPower) >= 8) {
// Flash screen with wind color for strong wind
var windColor = windPower > 0 ? 0x00FFFF : 0xFFFF00; // Blue for right wind, yellow for left wind
LK.effects.flashScreen(windColor, 500);
}
}
}
// Update ellipse position to follow player
playerEllipse.x = player.x;
playerEllipse.y = player.y - 20; // Keep 20 pixels above player for better interception
// Ensure ellipse is always visible in front of projectiles
game.setChildIndex(playerEllipse, game.children.length - 1);
// Update wave display
scoreText.setText('Wave: ' + currentWave);
scoreTextShadow.setText('Wave: ' + currentWave);
// Wave-based enemy spawning
if (currentWave <= maxWaves && enemiesSpawnedInWave < enemiesPerWave) {
enemySpawnTimer++;
if (enemySpawnTimer > 180) {
enemySpawnTimer = 0;
var enemy = new Enemy();
enemy.x = 2200;
enemy.y = player.y - 80 + Math.random() * 130;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawnedInWave++;
}
}
// Update background tiles for seamless looping
for (var i = 0; i < backgroundTiles.length; i++) {
var tile = backgroundTiles[i];
tile.update();
}
// If the leftmost tile is fully off screen, move it to the right of the rightmost tile
// This creates a seamless infinite scroll
if (backgroundTiles.length > 0) {
var leftmost = backgroundTiles[0];
var rightmost = backgroundTiles[backgroundTiles.length - 1];
// Use tileWidth from the tile itself for accuracy
if (leftmost.x + leftmost.tileWidth < 0) {
// Move leftmost tile to the right of the rightmost tile
leftmost.x = rightmost.x + rightmost.tileWidth;
// Move to end of array
backgroundTiles.push(backgroundTiles.shift());
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.isDead || enemy.x < -200) {
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update PLAYER projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
// Apply enhanced wind effect to projectile
projectile.velocityX += windPower * 0.08; // Increased wind effect for more noticeable impact
if (projectile.x > 2200 || projectile.y > 2800 || projectile.x < -100) {
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
// Collision detection with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (projectile.hasHit || enemy.isDead) continue;
// Check head collision first for damage
if (projectile.intersects(enemy.head)) {
if (projectile.type === 'stone') {
enemy.takeDamage(12);
} else if (projectile.type === 'poisonstone') {
enemy.takeDamage(10, true);
}
projectile.hasHit = true;
break; // Stop checking other enemies
}
// Check body/legs collision for damage
if (projectile.intersects(enemy.body) || projectile.intersects(enemy.leftLeg) || projectile.intersects(enemy.rightLeg)) {
if (projectile.type === 'stone') {
enemy.takeDamage(10);
} else if (projectile.type === 'poisonstone') {
enemy.takeDamage(3, true);
}
projectile.hasHit = true;
break; // Stop checking other enemies
}
}
// If projectile hit something, destroy it and continue to the next projectile
if (projectile.hasHit) {
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
} // <-- PLAYER PROJECTILE LOOP ENDS HERE
// =========================================================================
// Update ENEMY projectiles - NOW INDEPENDENT AND ALWAYS RUNS
// =========================================================================
for (var j = enemyProjectiles.length - 1; j >= 0; j--) {
var enemyProjectile = enemyProjectiles[j];
// Apply wind effect to enemy projectiles too
enemyProjectile.velocityX += windPower * 0.06; // Wind affects enemy projectiles as well
// Update projectile position with physics
enemyProjectile.update();
// Remove projectiles that are off screen
if (enemyProjectile.x > 2200 || enemyProjectile.y > 2800 || enemyProjectile.x < -100) {
enemyProjectile.destroy();
enemyProjectiles.splice(j, 1);
continue;
}
// Check collision with player ellipse to bounce stones back
var currentIntersectingEllipse = enemyProjectile.intersects(playerEllipse);
// Only bounce if we just started intersecting (transition from false to true) and haven't bounced before
if (false && !enemyProjectile.lastIntersectingEllipse && currentIntersectingEllipse && !enemyProjectile.hasBouncedOffEllipse) {
// Temporarily disabled by adding 'false'
// Calculate bounce direction - reverse the velocity with enhanced force
enemyProjectile.velocityX = -enemyProjectile.velocityX * 1.8; // Increased bounce force
enemyProjectile.velocityY = -enemyProjectile.velocityY * 1.8; // Increased bounce force
// Apply additional upward boost to prevent ground collision
enemyProjectile.velocityY -= 8;
// Move projectile away from ellipse to prevent multiple collisions
var deltaX = enemyProjectile.x - playerEllipse.x;
var deltaY = enemyProjectile.y - playerEllipse.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
deltaX /= distance;
deltaY /= distance;
enemyProjectile.x += deltaX * 50; // Increased separation distance
enemyProjectile.y += deltaY * 50;
}
// Mark as bounced to prevent multiple bounces
enemyProjectile.hasBouncedOffEllipse = true;
// Visual feedback - flash the ellipse brighter with tween
tween(playerEllipse, {
tint: 0xFFFFFF
}, {
duration: 100,
onFinish: function onFinish() {
tween(playerEllipse, {
tint: 0x00FFFF
}, {
duration: 200
});
}
});
// Add screen shake effect for impact
LK.effects.flashScreen(0x00FFFF, 300);
}
// Update collision tracking
enemyProjectile.lastIntersectingEllipse = currentIntersectingEllipse;
// Check collision with player (direct hit damage)
if (!enemyProjectile.hasBouncedOffEllipse && enemyProjectile.intersects(player)) {
// Player takes damage
playerHealth -= 10;
if (playerHealth < 0) playerHealth = 0;
updateHealthBar();
// Visual feedback - flash player red
tween(player.currentGraphics, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(player.currentGraphics, {
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
// Remove the projectile
enemyProjectile.destroy();
enemyProjectiles.splice(j, 1);
// Check for game over
if (playerHealth <= 0) {
LK.showGameOver();
}
continue;
}
// Check collision with enemies if the projectile has bounced off the ellipse
if (enemyProjectile.hasBouncedOffEllipse) {
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
if (enemy.isDead) continue;
// Check collision with enemy parts - enhanced damage for bounced projectiles
if (enemyProjectile.intersects(enemy.head)) {
enemy.takeDamage(30); // Increased damage for bounced projectiles
// Visual effect for bounced projectile hit with tween
tween(enemy.head, {
tint: 0x00FFFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(enemy.head, {
tint: 0xFFFFFF
}, {
duration: 250
});
}
});
enemyProjectile.destroy();
enemyProjectiles.splice(j, 1);
break;
}
if (enemyProjectile.intersects(enemy.body) || enemyProjectile.intersects(enemy.leftLeg) || enemyProjectile.intersects(enemy.rightLeg)) {
enemy.takeDamage(22); // Increased damage for bounced projectiles
// Visual effect for bounced projectile hit with tween
if (enemyProjectile.intersects(enemy.body)) {
tween(enemy.body, {
tint: 0x00FFFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(enemy.body, {
tint: 0xFFFFFF
}, {
duration: 250
});
}
});
}
enemyProjectile.destroy();
enemyProjectiles.splice(j, 1);
break;
}
}
}
} // <-- ENEMY PROJECTILE LOOP ENDS HERE
// Check wave completion
if (enemies.length === 0 && enemiesSpawnedInWave >= enemiesPerWave && currentWave <= maxWaves) {
// Wave completed
LK.setScore(currentWave); // Set score to current wave number
currentWave++;
enemiesSpawnedInWave = 0;
enemySpawnTimer = 0;
// Change wind when wave changes
var oldWindPower = windPower;
// Random wind change between -4 and +4 for wave transitions
var windChange = Math.floor(Math.random() * 9) - 4;
windPower = Math.max(-12, Math.min(12, windPower + windChange));
// Update wind power sprites if wind changed
if (windPower !== oldWindPower) {
createWindPowerSprites();
}
// Reset wind timer when wave changes
windChangeTimer = 0;
if (currentWave > maxWaves) {
// All waves completed - show victory
LK.showYouWin();
}
}
// Replenish inventory occasionally
if (LK.ticks % 600 === 0) {
for (var i = 0; i < inventorySlots.length; i++) {
var slot = inventorySlots[i];
if (slot.count < slot.maxCount) {
slot.count = Math.min(slot.count + 1, slot.maxCount);
slot.updateCount();
}
}
}
};
daha sinirli versiyon
kolların bağlanmadığı aşşağı dogru sarktığı
Ayni poz farkli anime kızı kafa dizaynlari
Farkli sac modeli
basit fırlatmalık taş. In-Game asset. 2d. High contrast. No shadows
yosunlu zehirli koku dumanlı versiyon
kameradan uzaklaştır çocuğun tamamı görünsün
wind . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
daha simetrik daha kalın üstü rizgar desenli
yap bişeyler
princess dress same pose
colorful and diffrerent princes style
zombi versiyonu ama hala gülsün
cool shield like captain amarika. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat