User prompt
karakterin blokların içinden geçmemesini sağla
User prompt
oyuna başladığında farklı farklı yerlerde doğalım
User prompt
blok seçme menüsü ön katmana gitsin
User prompt
dünyaya tıklayınca blok koysun haraket etmesin
User prompt
blok seçme menüsü daha büyük olsun
User prompt
blok seçme menüsü ekle
User prompt
blok seçme menüsü ekle
User prompt
kontrol etme tuşları dünyanın altında olsun
User prompt
kontrol etme tuşları siyah olsun
User prompt
karakteri ekrandaki butonlarla kontrol edelim
User prompt
oyuna karakter ekle
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(block).to({' Line Number: 244 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyuna blokları kırma ekle
Code edit (1 edits merged)
Please save this source code
User prompt
Block Builder Adventure
Initial prompt
minecraft
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Achievement = Container.expand(function (achievementData) { var self = Container.call(this); // Background var bgGraphics = self.attachAsset('menuBg', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.3, alpha: 0.9 }); // Title text var titleText = new Text2('Achievement Unlocked!', { size: 36, fill: 0xFFD700 }); titleText.anchor.set(0.5, 0.5); titleText.x = 0; titleText.y = -30; self.addChild(titleText); // Achievement name var nameText = new Text2(achievementData.name, { size: 28, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.x = 0; nameText.y = 0; self.addChild(nameText); // Achievement description var descText = new Text2(achievementData.description, { size: 22, fill: 0xCCCCCC }); descText.anchor.set(0.5, 0.5); descText.x = 0; descText.y = 25; self.addChild(descText); self.show = function () { self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; self.visible = true; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); // Auto hide after 3 seconds LK.setTimeout(function () { tween(self, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 200, onFinish: function onFinish() { self.visible = false; } }); }, 3000); }; // Initially hidden self.visible = false; return self; }); var AchievementMenu = Container.expand(function () { var self = Container.call(this); self.isVisible = false; // Background var bgGraphics = self.attachAsset('menuBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9, scaleX: 1.2, scaleY: 3.0 }); // Title text var titleText = new Text2('Achievements', { size: 48, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 0; titleText.y = -300; self.addChild(titleText); // Close button var closeButton = self.attachAsset('closeButton', { anchorX: 0.5, anchorY: 0.5 }); closeButton.x = 350; closeButton.y = -300; // Achievement list container var achievementList = new Container(); self.addChild(achievementList); // Page navigation buttons var prevPageButton = self.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, scaleX: 0.8, scaleY: 0.5 }); prevPageButton.x = -200; prevPageButton.y = 300; var prevPageText = new Text2('PREV', { size: 28, fill: 0xFFFFFF }); prevPageText.anchor.set(0.5, 0.5); prevPageText.x = -200; prevPageText.y = 300; self.addChild(prevPageText); var nextPageButton = self.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, scaleX: 0.8, scaleY: 0.5 }); nextPageButton.x = 200; nextPageButton.y = 300; var nextPageText = new Text2('NEXT', { size: 28, fill: 0xFFFFFF }); nextPageText.anchor.set(0.5, 0.5); nextPageText.x = 200; nextPageText.y = 300; self.addChild(nextPageText); // Page indicator text var pageIndicatorText = new Text2('', { size: 24, fill: 0xCCCCCC }); pageIndicatorText.anchor.set(0.5, 0.5); pageIndicatorText.x = 0; pageIndicatorText.y = 300; self.addChild(pageIndicatorText); self.updateAchievements = function () { // Clear existing achievement displays while (achievementList.children.length > 0) { achievementList.removeChild(achievementList.children[0]); } // Get all achievement IDs as array for pagination var allAchievementIds = []; for (var achievementId in achievementDefinitions) { allAchievementIds.push(achievementId); } // Calculate pagination var totalPages = Math.ceil(allAchievementIds.length / achievementsPerPage); var startIndex = currentAchievementPage * achievementsPerPage; var endIndex = Math.min(startIndex + achievementsPerPage, allAchievementIds.length); // Update page indicator pageIndicatorText.setText('Page ' + (currentAchievementPage + 1) + ' of ' + totalPages); // Update button visibility prevPageButton.alpha = currentAchievementPage > 0 ? 0.8 : 0.3; prevPageText.fill = currentAchievementPage > 0 ? 0xFFFFFF : 0x666666; nextPageButton.alpha = currentAchievementPage < totalPages - 1 ? 0.8 : 0.3; nextPageText.fill = currentAchievementPage < totalPages - 1 ? 0xFFFFFF : 0x666666; var yOffset = -200; var achievementIndex = 0; // Display achievements for current page for (var i = startIndex; i < endIndex; i++) { var achievementId = allAchievementIds[i]; var achievement = achievementDefinitions[achievementId]; var isUnlocked = achievements[achievementId] || false; // Achievement container var achievementContainer = new Container(); achievementContainer.y = yOffset + achievementIndex * 80; // Achievement background var achievementBg = achievementContainer.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, alpha: isUnlocked ? 0.8 : 0.3, scaleX: 2.8, scaleY: 0.6 }); // Achievement name var nameText = new Text2(achievement.name, { size: 32, fill: isUnlocked ? 0xFFD700 : 0x888888 }); nameText.anchor.set(0.5, 0.5); nameText.x = 0; nameText.y = -15; achievementContainer.addChild(nameText); // Achievement description var descText = new Text2(achievement.description, { size: 24, fill: isUnlocked ? 0xFFFFFF : 0x666666 }); descText.anchor.set(0.5, 0.5); descText.x = 0; descText.y = 10; achievementContainer.addChild(descText); // Status text var statusText = new Text2(isUnlocked ? 'UNLOCKED' : 'LOCKED', { size: 20, fill: isUnlocked ? 0x00FF00 : 0xFF4444 }); statusText.anchor.set(1, 0.5); statusText.x = 150; statusText.y = 0; achievementContainer.addChild(statusText); achievementList.addChild(achievementContainer); achievementIndex++; } }; // Page navigation interactions prevPageButton.down = function (x, y, obj) { if (currentAchievementPage > 0) { prevPageButton.alpha = 1.0; currentAchievementPage--; self.updateAchievements(); } }; prevPageButton.up = function (x, y, obj) { prevPageButton.alpha = currentAchievementPage > 0 ? 0.8 : 0.3; }; nextPageButton.down = function (x, y, obj) { var totalPages = Math.ceil(Object.keys(achievementDefinitions).length / achievementsPerPage); if (currentAchievementPage < totalPages - 1) { nextPageButton.alpha = 1.0; currentAchievementPage++; self.updateAchievements(); } }; nextPageButton.up = function (x, y, obj) { var totalPages = Math.ceil(Object.keys(achievementDefinitions).length / achievementsPerPage); nextPageButton.alpha = currentAchievementPage < totalPages - 1 ? 0.8 : 0.3; }; // Close button interaction closeButton.down = function (x, y, obj) { closeButton.alpha = 0.7; self.hide(); }; closeButton.up = function (x, y, obj) { closeButton.alpha = 1.0; }; self.show = function () { self.isVisible = true; currentAchievementPage = 0; // Reset to first page when opening self.updateAchievements(); self.alpha = 0; self.visible = true; tween(self, { alpha: 1 }, { duration: 200 }); }; self.hide = function () { self.isVisible = false; tween(self, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.visible = false; } }); }; // Initially hidden self.visible = false; return self; }); var Block = Container.expand(function (blockType) { var self = Container.call(this); self.blockType = blockType; self.gridX = 0; self.gridY = 0; var blockGraphics = self.attachAsset(blockType, { anchorX: 0.5, anchorY: 0.5 }); // Add a subtle border effect blockGraphics.alpha = 0.9; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; return self; }); var BlockSelectionMenu = Container.expand(function () { var self = Container.call(this); self.isVisible = false; // Background var bgGraphics = self.attachAsset('menuBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9, scaleY: 4.0 }); // Title text var titleText = new Text2('Select Block Type', { size: 48, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 0; titleText.y = -400; self.addChild(titleText); // Close button var closeButton = self.attachAsset('closeButton', { anchorX: 0.5, anchorY: 0.5 }); closeButton.x = 280; closeButton.y = -400; // Block selection buttons self.menuButtons = []; var allBlockTypes = Object.keys(inventory); var buttonsPerRow = 3; var buttonSpacing = 160; for (var i = 0; i < allBlockTypes.length; i++) { var button = new Container(); // Button background var buttonBg = button.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); // Block preview var blockPreview = button.attachAsset(allBlockTypes[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9 }); // Block name text var nameText = new Text2(allBlockTypes[i], { size: 26, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0); nameText.x = 0; nameText.y = 45; button.addChild(nameText); // Inventory count text var countText = new Text2(inventory[allBlockTypes[i]].toString(), { size: 24, fill: 0xFFDD44 }); countText.anchor.set(0.5, 0); countText.x = 0; countText.y = 65; button.addChild(countText); // Position button var col = i % buttonsPerRow; var row = Math.floor(i / buttonsPerRow); button.x = (col - 1) * buttonSpacing; button.y = row * buttonSpacing - 30; // Store button data button.blockType = allBlockTypes[i]; button.bgGraphics = buttonBg; button.countText = countText; // Button interaction button.down = function (x, y, obj) { this.bgGraphics.alpha = 1.0; selectedBlockType = this.blockType; updateToolbarSelection(); self.hide(); }; button.up = function (x, y, obj) { this.bgGraphics.alpha = 0.8; }; self.menuButtons.push(button); self.addChild(button); } // Close button interaction closeButton.down = function (x, y, obj) { closeButton.alpha = 0.7; self.hide(); }; closeButton.up = function (x, y, obj) { closeButton.alpha = 1.0; }; self.show = function () { self.isVisible = true; self.alpha = 0; self.visible = true; tween(self, { alpha: 1 }, { duration: 200 }); }; self.hide = function () { self.isVisible = false; tween(self, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.visible = false; } }); }; self.updateInventoryCounts = function () { for (var i = 0; i < self.menuButtons.length; i++) { var button = self.menuButtons[i]; if (button.countText && button.blockType) { button.countText.setText(inventory[button.blockType].toString()); } } }; // Initially hidden self.visible = false; return self; }); var Character = Container.expand(function () { var self = Container.call(this); var characterGraphics = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.targetX = 0; self.targetY = 0; self.isMoving = false; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.targetX = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.targetY = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; if (!self.isMoving) { self.x = self.targetX; self.y = self.targetY; } }; self.moveToGrid = function (gridX, gridY) { if (self.isMoving) return false; if (!isValidGridPosition(gridX, gridY)) return false; // Check if there's a block at the target position (only block movement if not flying) // Allow passing through water and lava blocks if (!isFlying && grid[gridX] && grid[gridX][gridY] !== null) { var targetBlock = grid[gridX][gridY]; if (targetBlock.blockType !== 'water' && targetBlock.blockType !== 'lava') { return false; } } self.isMoving = true; self.gridX = gridX; self.gridY = gridY; self.targetX = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.targetY = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; // Reset gravity when manually moving (especially important in flying mode) if (isFlying) { characterGravity = 0; } // Adjust movement speed based on flying state var moveDuration = isFlying ? 100 : 200; tween(self, { x: self.targetX, y: self.targetY }, { duration: moveDuration, easing: isFlying ? tween.easeOut : tween.easeInOut, onFinish: function onFinish() { self.isMoving = false; // Track movement for achievements moveCount++; storage.moveCount = moveCount; // Track jumping (moving up) for boooing achievement if (gridY < self.gridY) { jumpCount++; storage.jumpCount = jumpCount; } // Track distance traveled var distance = Math.abs(gridX - self.gridX) + Math.abs(gridY - self.gridY); totalDistanceTraveled += distance; storage.totalDistanceTraveled = totalDistanceTraveled; // Track corner visits if ((gridX === 0 || gridX === GRID_WIDTH - 1) && (gridY === 0 || gridY === GRID_HEIGHT - 1)) { var corner = gridX + '_' + gridY; visitedCorners[corner] = true; storage.visitedCorners = visitedCorners; } // Track top/bottom row visits if (gridY === 0) { topRowVisits++; storage.topRowVisits = topRowVisits; } if (gridY === GRID_HEIGHT - 1) { bottomRowVisits++; storage.bottomRowVisits = bottomRowVisits; } checkAllAchievements(); } }); return true; }; self.update = function () { // Reset gravity when flying - do this first if (isFlying) { characterGravity = 0; } else { // Apply gravity if character is not moving, not supported, and not flying if (!self.isMoving) { var hasSupport = false; // Check if character has support below (block or ground) var belowY = self.gridY + 1; if (belowY >= GRID_HEIGHT) { // At bottom of world, has support hasSupport = true; } else if (grid[self.gridX] && grid[self.gridX][belowY] !== null) { // Block below, has support hasSupport = true; } if (!hasSupport) { // Character should fall characterGravity += characterGravityAcceleration; if (characterGravity > characterMaxFallSpeed) { characterGravity = characterMaxFallSpeed; } // Find where character should fall to var fallToY = self.gridY; for (var checkY = self.gridY + 1; checkY < GRID_HEIGHT; checkY++) { if (grid[self.gridX][checkY] !== null) { break; } fallToY = checkY; } // Move character down if there's a place to fall if (fallToY > self.gridY) { var targetY = Math.min(fallToY, self.gridY + Math.floor(characterGravity)); if (targetY !== self.gridY) { self.moveToGrid(self.gridX, targetY); } } } else { // Character has support, reset gravity characterGravity = 0; } } } // Check if character is touching a portal if (!self.isMoving) { for (var i = 0; i < placedBlocks.length; i++) { var block = placedBlocks[i]; if (block.blockType === 'portal' && block.gridX === self.gridX && block.gridY === self.gridY) { block.teleportCharacter(); break; } } } // Check if character is passing through lava and add effect if (grid[self.gridX] && grid[self.gridX][self.gridY] !== null) { var currentBlock = grid[self.gridX][self.gridY]; if (currentBlock.blockType === 'lava') { // Flash character red with tween effect tween(characterGraphics, { tint: 0xff0000 }, { duration: 200, easing: tween.easeOut }); tween(characterGraphics, { tint: 0xffffff }, { duration: 200, easing: tween.easeOut }); // Add screen flash effect LK.effects.flashScreen(0xff4500, 300); // Track lava touches for survivalist achievement lavaTouches++; storage.lavaTouches = lavaTouches; checkAllAchievements(); } } // Track time spent at world center for centerist achievement var centerX = Math.floor(GRID_WIDTH / 2); var centerY = Math.floor(GRID_HEIGHT / 2); if (Math.abs(self.gridX - centerX) <= 2 && Math.abs(self.gridY - centerY) <= 2) { centerTime++; } // Character is always on top if (self.parent) { var parent = self.parent; parent.removeChild(self); parent.addChild(self); } }; return self; }); var ControlButton = Container.expand(function (direction) { var self = Container.call(this); self.direction = direction; var bgGraphics = self.attachAsset('controlBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); var arrowGraphics = self.attachAsset('arrow' + direction.charAt(0).toUpperCase() + direction.slice(1), { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); self.down = function (x, y, obj) { bgGraphics.alpha = 1.0; var newX = character.gridX; var newY = character.gridY; if (direction === 'up') newY--;else if (direction === 'down') newY++;else if (direction === 'left') newX--;else if (direction === 'right') newX++; character.moveToGrid(newX, newY); }; self.up = function (x, y, obj) { bgGraphics.alpha = 0.7; }; return self; }); var GridCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, alpha: 0.1 }); self.gridX = 0; self.gridY = 0; self.isEmpty = true; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.highlight = function () { cellGraphics.alpha = 0.3; }; self.unhighlight = function () { cellGraphics.alpha = 0.1; }; return self; }); var Lava = Container.expand(function () { var self = Container.call(this); self.blockType = 'lava'; self.gridX = 0; self.gridY = 0; self.isFlowing = false; self.flowCooldown = 0; self.damageTimer = 0; var lavaGraphics = self.attachAsset('lava', { anchorX: 0.5, anchorY: 0.5 }); // Add glowing effect lavaGraphics.alpha = 0.9; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.update = function () { // Pulsing glow effect lavaGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.15) * 0.15; lavaGraphics.tint = 0xff4500 + Math.floor(Math.sin(LK.ticks * 0.1) * 20) * 0x010000; // Handle damage to character if (character && character.gridX === self.gridX && character.gridY === self.gridY && !character.isMoving) { self.damageTimer++; if (self.damageTimer >= 60) { // 1 second of contact // Flash character red and teleport away from lava LK.effects.flashObject(character, 0xff0000, 500); LK.effects.flashScreen(0xff0000, 300); // Move character to nearest safe position self.teleportCharacterToSafety(); self.damageTimer = 0; } } else { self.damageTimer = 0; } // Lava flowing mechanics disabled - no spreading // if (self.flowCooldown > 0) { // self.flowCooldown--; // } else { // self.tryToFlow(); // self.flowCooldown = 120; // Flow every 2 seconds // } }; self.teleportCharacterToSafety = function () { // Find nearest safe position var safePositions = []; for (var radius = 1; radius <= 5; radius++) { for (var dx = -radius; dx <= radius; dx++) { for (var dy = -radius; dy <= radius; dy++) { var checkX = self.gridX + dx; var checkY = self.gridY + dy; if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] === null) { // Check if position has ground support below var hasSupport = false; if (checkY + 1 >= GRID_HEIGHT) { hasSupport = true; } else if (grid[checkX][checkY + 1] !== null && grid[checkX][checkY + 1].blockType !== 'lava') { hasSupport = true; } if (hasSupport) { safePositions.push({ x: checkX, y: checkY }); } } } } if (safePositions.length > 0) break; } if (safePositions.length > 0) { var safePos = safePositions[0]; // Take closest safe position character.setGridPosition(safePos.x, safePos.y); } }; self.tryToFlow = function () { // Lava flows down first, then horizontally var flowDirections = [{ x: 0, y: 1 }, // Down { x: -1, y: 0 }, // Left { x: 1, y: 0 }, // Right { x: -1, y: 1 }, // Down-left { x: 1, y: 1 } // Down-right ]; for (var i = 0; i < flowDirections.length; i++) { var dir = flowDirections[i]; var targetX = self.gridX + dir.x; var targetY = self.gridY + dir.y; if (isValidGridPosition(targetX, targetY) && grid[targetX][targetY] === null) { // Only flow if there are less than 3 lava blocks already var nearbyLavaCount = 0; for (var checkX = targetX - 1; checkX <= targetX + 1; checkX++) { for (var checkY = targetY - 1; checkY <= targetY + 1; checkY++) { if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] !== null && grid[checkX][checkY].blockType === 'lava') { nearbyLavaCount++; } } } if (nearbyLavaCount < 3) { // Create new lava block var newLava = new Lava(); newLava.setGridPosition(targetX, targetY); game.addChild(newLava); grid[targetX][targetY] = newLava; placedBlocks.push(newLava); LK.effects.flashObject(newLava, 0xffff00, 300); break; } } } }; return self; }); var Portal = Container.expand(function () { var self = Container.call(this); self.blockType = 'portal'; self.gridX = 0; self.gridY = 0; self.isActive = true; self.cooldownTime = 0; var portalGraphics = self.attachAsset('portal', { anchorX: 0.5, anchorY: 0.5 }); // Add glowing effect portalGraphics.alpha = 0.8; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.teleportCharacter = function () { if (!self.isActive || self.cooldownTime > 0) return false; if (!character || character.isMoving) return false; // Find all other active portals var otherPortals = []; for (var i = 0; i < placedBlocks.length; i++) { var block = placedBlocks[i]; if (block !== self && block.blockType === 'portal' && block.isActive && block.cooldownTime <= 0) { otherPortals.push(block); } } if (otherPortals.length === 0) { // No other portals available - teleport to heaven! // Create heaven effect with golden background and clouds game.setBackgroundColor(0xFFD700); // Golden sky // Flash screen with heavenly light LK.effects.flashScreen(0xFFFFFF, 2000); // Teleport character to top of world center var heavenX = Math.floor(GRID_WIDTH / 2); var heavenY = 0; // Top of the world character.setGridPosition(heavenX, heavenY); // Add visual effects LK.effects.flashObject(self, 0xFFD700, 1000); LK.effects.flashObject(character, 0xFFFFFF, 1000); // Play portal sound LK.getSound('portal').play(); // Set cooldown self.cooldownTime = 300; // 5 seconds cooldown // Track portal usage and heaven visits portalUses++; heavenVisits++; storage.portalUses = portalUses; storage.heavenVisits = heavenVisits; checkAllAchievements(); // Reset background after effect LK.setTimeout(function () { game.setBackgroundColor(0x87CEEB); // Back to sky blue }, 3000); return true; } // Choose random portal to teleport to var targetPortal = otherPortals[Math.floor(Math.random() * otherPortals.length)]; // Teleport character character.setGridPosition(targetPortal.gridX, targetPortal.gridY); // Add visual effects LK.effects.flashObject(self, 0x9400d3, 500); LK.effects.flashObject(targetPortal, 0x9400d3, 500); LK.effects.flashObject(character, 0x00ffff, 300); // Play portal sound LK.getSound('portal').play(); // Set cooldown for both portals self.cooldownTime = 180; // 3 seconds at 60fps targetPortal.cooldownTime = 180; // Track portal usage portalUses++; storage.portalUses = portalUses; checkAllAchievements(); return true; }; self.update = function () { // Handle cooldown if (self.cooldownTime > 0) { self.cooldownTime--; portalGraphics.alpha = 0.3 + self.cooldownTime / 180 * 0.5; } else { portalGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.1) * 0.2; // Pulsing effect } // Check if character is on portal if (character && character.gridX === self.gridX && character.gridY === self.gridY && !character.isMoving) { self.teleportCharacter(); } }; return self; }); var Sand = Container.expand(function () { var self = Container.call(this); self.blockType = 'sand'; self.gridX = 0; self.gridY = 0; self.isFalling = false; self.fallSpeed = 0; self.maxFallSpeed = 8; self.fallAcceleration = 0.8; var sandGraphics = self.attachAsset('sand', { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.update = function () { // Check if sand should fall if (!self.isFalling) { var hasSupport = false; var belowY = self.gridY + 1; // Check if at bottom or has block below if (belowY >= GRID_HEIGHT) { hasSupport = true; } else if (grid[self.gridX] && grid[self.gridX][belowY] !== null) { hasSupport = true; } // Start falling if no support if (!hasSupport) { self.isFalling = true; self.fallSpeed = 0; } } // Handle falling physics if (self.isFalling) { self.fallSpeed += self.fallAcceleration; if (self.fallSpeed > self.maxFallSpeed) { self.fallSpeed = self.maxFallSpeed; } // Calculate new grid position var newGridY = self.gridY + Math.floor(self.fallSpeed / 4); // Check if we can fall to new position if (newGridY < GRID_HEIGHT && (grid[self.gridX][newGridY] === null || grid[self.gridX][newGridY] === self)) { // Update grid if (grid[self.gridX][self.gridY] === self) { grid[self.gridX][self.gridY] = null; } self.gridY = newGridY; grid[self.gridX][self.gridY] = self; self.setGridPosition(self.gridX, self.gridY); } else { // Stop falling - hit something or reached bottom self.isFalling = false; self.fallSpeed = 0; } } }; return self; }); var ToolbarButton = Container.expand(function (blockType) { var self = Container.call(this); self.blockType = blockType; self.isSelected = false; var bgGraphics = self.attachAsset('toolbarBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var blockGraphics = self.attachAsset(blockType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); var selectedBorder = self.attachAsset('selectedBorder', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.setSelected = function (selected) { self.isSelected = selected; selectedBorder.alpha = selected ? 0.8 : 0; bgGraphics.alpha = selected ? 1.0 : 0.8; }; self.down = function (x, y, obj) { selectedBlockType = self.blockType; updateToolbarSelection(); }; return self; }); var Villager = Container.expand(function () { var self = Container.call(this); self.gridX = 0; self.gridY = 0; self.moveTimer = 0; self.moveDirection = 1; self.isAlive = true; self.gravity = 0; self.maxFallSpeed = 8; self.gravityAcceleration = 0.5; self.isFalling = false; self.aiState = 'normal'; // 'normal', 'fleeing', 'attacking' self.fleeTimer = 0; // How long to flee self.attackTimer = 0; // Cooldown between attacks self.lastPlayerAttackDistance = 999; // Distance to last player attack var villagerGraphics = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5 }); villagerGraphics.tint = 0x8B4513; // Brown color for villagers villagerGraphics.scaleX = 0.8; villagerGraphics.scaleY = 0.8; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; self.tryFleeFromPlayer = function () { if (!character) return; // Calculate direction away from player var playerX = character.gridX; var playerY = character.gridY; var deltaX = self.gridX - playerX; var deltaY = self.gridY - playerY; // Determine flee direction (away from player) var fleeX = self.gridX; var fleeY = self.gridY; if (Math.abs(deltaX) > Math.abs(deltaY)) { // Move horizontally away from player fleeX += deltaX > 0 ? 1 : -1; } else { // Move vertically away from player fleeY += deltaY > 0 ? 1 : -1; } // Try to move to flee position if (isValidGridPosition(fleeX, fleeY) && grid[fleeX][fleeY] === null) { // Check if villager has support at new position var hasSupport = false; if (fleeY + 1 >= GRID_HEIGHT) { hasSupport = true; } else if (grid[fleeX] && grid[fleeX][fleeY + 1] !== null) { hasSupport = true; } if (hasSupport) { self.setGridPosition(fleeX, fleeY); // Add movement effect LK.effects.flashObject(self, 0x00FFFF, 200); } } }; self.tryAttackPlayer = function () { if (!character) return; // Move toward player var playerX = character.gridX; var playerY = character.gridY; var deltaX = playerX - self.gridX; var deltaY = playerY - self.gridY; // Determine attack direction (toward player) var attackX = self.gridX; var attackY = self.gridY; if (Math.abs(deltaX) > Math.abs(deltaY)) { // Move horizontally toward player attackX += deltaX > 0 ? 1 : -1; } else { // Move vertically toward player attackY += deltaY > 0 ? 1 : -1; } // Try to move toward player if (isValidGridPosition(attackX, attackY) && grid[attackX][attackY] === null) { // Check if villager has support at new position var hasSupport = false; if (attackY + 1 >= GRID_HEIGHT) { hasSupport = true; } else if (grid[attackX] && grid[attackX][attackY + 1] !== null) { hasSupport = true; } if (hasSupport) { self.setGridPosition(attackX, attackY); // Add aggressive movement effect LK.effects.flashObject(self, 0xFF4444, 200); } } // If close to player, try to break blocks around them or attack var distanceToPlayer = Math.abs(self.gridX - playerX) + Math.abs(self.gridY - playerY); if (distanceToPlayer <= 2 && self.attackTimer <= 0) { // Try to break blocks near player or attack player directly var attackPositions = [{ x: playerX - 1, y: playerY }, { x: playerX + 1, y: playerY }, { x: playerX, y: playerY - 1 }, { x: playerX, y: playerY + 1 }]; for (var i = 0; i < attackPositions.length; i++) { var pos = attackPositions[i]; if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) { var block = grid[pos.x][pos.y]; if (block.blockType !== 'portal' && block.blockType !== 'lava') { // Break block near player self.breakBlockAt(pos.x, pos.y); self.attackTimer = 120; // 2 second cooldown // Visual attack effect LK.effects.flashObject(self, 0xFF0000, 400); LK.effects.flashScreen(0xFF4444, 200); break; } } } // If no blocks to break, directly attack player (flash effect) if (self.attackTimer <= 0) { LK.effects.flashObject(character, 0xFF0000, 300); LK.effects.flashScreen(0xFF4444, 150); self.attackTimer = 180; // 3 second cooldown } } }; self.breakBlockAt = function (gridX, gridY) { if (!isValidGridPosition(gridX, gridY) || grid[gridX][gridY] === null) return; var block = grid[gridX][gridY]; var blockType = block.blockType; // Create particle effects for breaking for (var p = 0; p < 4; p++) { var particle = new Container(); var particleGraphics = particle.attachAsset(blockType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); particle.x = block.x; particle.y = block.y; game.addChild(particle); // Random direction for each particle var angle = p / 4 * Math.PI * 2; var speed = 50 + Math.random() * 30; var targetX = particle.x + Math.cos(angle) * speed; var targetY = particle.y + Math.sin(angle) * speed; // Animate particle flying out and fading tween(particle, { x: targetX, y: targetY, scaleX: 0.05, scaleY: 0.05, alpha: 0, rotation: Math.random() * Math.PI }, { duration: 200 + Math.random() * 100, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Remove from placedBlocks array for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === block) { placedBlocks.splice(j, 1); break; } } // Destroy the block block.destroy(); grid[gridX][gridY] = null; // Play break sound LK.getSound('break').play(); }; }; self.kill = function () { if (!self.isAlive) return; self.isAlive = false; // Create death effects LK.effects.flashObject(self, 0xff0000, 500); // Create particle effects for death for (var p = 0; p < 6; p++) { var particle = new Container(); var particleGraphics = particle.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); particleGraphics.tint = 0x8B4513; particle.x = self.x; particle.y = self.y; game.addChild(particle); // Random direction for each particle var angle = p / 6 * Math.PI * 2; var speed = 60 + Math.random() * 40; var targetX = particle.x + Math.cos(angle) * speed; var targetY = particle.y + Math.sin(angle) * speed; // Animate particle flying out and fading tween(particle, { x: targetX, y: targetY, scaleX: 0.05, scaleY: 0.05, alpha: 0, rotation: Math.random() * Math.PI * 2 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Remove from villagers array for (var i = villagers.length - 1; i >= 0; i--) { if (villagers[i] === self) { villagers.splice(i, 1); break; } } // Destroy villager after effects LK.setTimeout(function () { self.destroy(); }, 500); }; self.update = function () { if (!self.isAlive) return; // Apply gravity to villager var hasSupport = false; if (self.gridY + 1 >= GRID_HEIGHT) { hasSupport = true; } else if (grid[self.gridX] && grid[self.gridX][self.gridY + 1] !== null) { hasSupport = true; } if (!hasSupport) { // Villager should fall self.gravity += self.gravityAcceleration; if (self.gravity > self.maxFallSpeed) { self.gravity = self.maxFallSpeed; } // Find where villager should fall to var fallToY = self.gridY; for (var checkY = self.gridY + 1; checkY < GRID_HEIGHT; checkY++) { if (grid[self.gridX][checkY] !== null) { break; } fallToY = checkY; } // Move villager down if there's a place to fall if (fallToY > self.gridY) { var targetY = Math.min(fallToY, self.gridY + Math.floor(self.gravity)); if (targetY !== self.gridY) { self.setGridPosition(self.gridX, targetY); } } } else { // Villager has support, reset gravity self.gravity = 0; } // Check for player attacks and respond accordingly if (playerAttackPosition && playerAttackTime > 0) { // Calculate distance to player attack var attackDistance = Math.abs(self.gridX - playerAttackPosition.x) + Math.abs(self.gridY - playerAttackPosition.y); // If attack was recent (within 5 seconds) and close (within 5 blocks) var timeSinceAttack = LK.ticks - playerAttackTime; if (timeSinceAttack < 300 && attackDistance <= 5) { // Decide whether to flee or attack based on villager personality and distance var shouldFlee = Math.random() < 0.7 || attackDistance <= 2; // 70% flee, or always flee if very close if (shouldFlee && self.aiState !== 'fleeing') { self.aiState = 'fleeing'; self.fleeTimer = 300 + Math.random() * 180; // Flee for 5-8 seconds // Visual indication of fear LK.effects.flashObject(self, 0xFFFF00, 300); } else if (!shouldFlee && self.aiState !== 'attacking' && attackDistance >= 2) { self.aiState = 'attacking'; self.attackTimer = 0; // Visual indication of anger LK.effects.flashObject(self, 0xFF0000, 500); } } } // Update AI state timers if (self.fleeTimer > 0) { self.fleeTimer--; if (self.fleeTimer <= 0) { self.aiState = 'normal'; } } if (self.attackTimer > 0) { self.attackTimer--; } self.moveTimer++; // AI behavior based on current state if (self.aiState === 'fleeing') { // Flee behavior - move away from player/attack position every 1 second if (self.moveTimer >= 60) { self.moveTimer = 0; self.tryFleeFromPlayer(); } } else if (self.aiState === 'attacking') { // Attack behavior - move toward player and try to break blocks near them every 2 seconds if (self.moveTimer >= 120) { self.moveTimer = 0; self.tryAttackPlayer(); } } else { // Normal AI behavior every 3 seconds if (self.moveTimer >= 180) { self.moveTimer = 0; // 30% chance to try building/breaking, 70% chance to move if (Math.random() < 0.3) { // Building/breaking behavior var actionType = Math.random(); if (actionType < 0.4) { // Try to place a block (40% chance) self.tryPlaceBlock(); } else if (actionType < 0.7) { // Try to break a block (30% chance) self.tryBreakBlock(); } else { // Try to move (30% chance) self.tryMove(); } } else { // Regular movement behavior self.tryMove(); } } } self.tryMove = function () { // Try to move in current direction var newX = self.gridX + self.moveDirection; // Check bounds and obstacles if (newX < 0 || newX >= GRID_WIDTH || grid[newX] && grid[newX][self.gridY] !== null) { self.moveDirection *= -1; // Reverse direction } else { // Move to new position if valid var hasSupport = false; if (self.gridY + 1 >= GRID_HEIGHT) { hasSupport = true; } else if (grid[newX] && grid[newX][self.gridY + 1] !== null) { hasSupport = true; } if (hasSupport) { self.setGridPosition(newX, self.gridY); } } // Add jumping behavior - 20% chance to jump when on solid ground if (Math.random() < 0.2 && self.gravity === 0 && !self.isFalling) { // Check if villager can jump (has support below and space above) var hasGroundSupport = false; if (self.gridY + 1 >= GRID_HEIGHT) { hasGroundSupport = true; } else if (grid[self.gridX] && grid[self.gridX][self.gridY + 1] !== null) { hasGroundSupport = true; } // Check if there's space above to jump var canJump = true; if (self.gridY - 1 < 0) { canJump = false; } else if (grid[self.gridX] && grid[self.gridX][self.gridY - 1] !== null) { canJump = false; } if (hasGroundSupport && canJump) { // Make villager jump by moving up 1 block self.setGridPosition(self.gridX, self.gridY - 1); // Add visual effect for jump LK.effects.flashObject(self, 0x00FF00, 200); } } }; self.tryPlaceBlock = function () { // Get available block types that villagers can place var villagerBlockTypes = ['dirt', 'stone', 'wood', 'grass']; var blockType = villagerBlockTypes[Math.floor(Math.random() * villagerBlockTypes.length)]; // Try to place block in adjacent positions var placePositions = [{ x: self.gridX - 1, y: self.gridY }, // Left { x: self.gridX + 1, y: self.gridY }, // Right { x: self.gridX, y: self.gridY - 1 }, // Up { x: self.gridX, y: self.gridY + 1 }, // Down { x: self.gridX - 1, y: self.gridY + 1 }, // Down-left { x: self.gridX + 1, y: self.gridY + 1 } // Down-right ]; for (var i = 0; i < placePositions.length; i++) { var pos = placePositions[i]; if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] === null) { // Don't place block on character position if (character && pos.x === character.gridX && pos.y === character.gridY) continue; // Create and place block var block = new Block(blockType); block.setGridPosition(pos.x, pos.y); game.addChild(block); grid[pos.x][pos.y] = block; placedBlocks.push(block); // Visual effect for villager placing block LK.effects.flashObject(self, 0x00FF00, 300); LK.getSound('place').play(); break; } } }; self.tryBreakBlock = function () { // Try to break blocks in adjacent positions var breakPositions = [{ x: self.gridX - 1, y: self.gridY }, // Left { x: self.gridX + 1, y: self.gridY }, // Right { x: self.gridX, y: self.gridY - 1 }, // Up { x: self.gridX, y: self.gridY + 1 }, // Down { x: self.gridX - 1, y: self.gridY - 1 }, // Up-left { x: self.gridX + 1, y: self.gridY - 1 } // Up-right ]; for (var i = 0; i < breakPositions.length; i++) { var pos = breakPositions[i]; if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) { var block = grid[pos.x][pos.y]; var blockType = block.blockType; // Don't break certain special blocks if (blockType === 'portal' || blockType === 'lava' || blockType === 'villagerEgg') continue; // Create particle effects for breaking for (var p = 0; p < 4; p++) { var particle = new Container(); var particleGraphics = particle.attachAsset(blockType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); particle.x = block.x; particle.y = block.y; game.addChild(particle); // Random direction for each particle var angle = p / 4 * Math.PI * 2; var speed = 50 + Math.random() * 30; var targetX = particle.x + Math.cos(angle) * speed; var targetY = particle.y + Math.sin(angle) * speed; // Animate particle flying out and fading tween(particle, { x: targetX, y: targetY, scaleX: 0.05, scaleY: 0.05, alpha: 0, rotation: Math.random() * Math.PI }, { duration: 200 + Math.random() * 100, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Remove from placedBlocks array for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === block) { placedBlocks.splice(j, 1); break; } } // Destroy the block block.destroy(); grid[pos.x][pos.y] = null; // Visual effect for villager breaking block LK.effects.flashObject(self, 0xFF0000, 300); LK.getSound('break').play(); break; } } }; // Keep villager on top of other objects if (self.parent) { var parent = self.parent; parent.removeChild(self); parent.addChild(self); } }; return self; }); var VillagerEgg = Container.expand(function () { var self = Container.call(this); self.blockType = 'villagerEgg'; self.gridX = 0; self.gridY = 0; self.hatchTimer = 0; self.hatchTime = 1800; // 30 seconds at 60fps var eggGraphics = self.attachAsset('villagerEgg', { anchorX: 0.5, anchorY: 0.5 }); // Add glowing effect eggGraphics.alpha = 0.9; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.update = function () { // Gentle pulsing effect eggGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.1) * 0.1; eggGraphics.scaleX = 1.0 + Math.sin(LK.ticks * 0.08) * 0.05; eggGraphics.scaleY = 1.0 + Math.sin(LK.ticks * 0.08) * 0.05; // Increment hatch timer self.hatchTimer++; // Change color as it gets closer to hatching var hatchProgress = self.hatchTimer / self.hatchTime; if (hatchProgress > 0.7) { eggGraphics.tint = 0xFFE4B5; // Warmer color when close to hatching } else if (hatchProgress > 0.5) { eggGraphics.tint = 0xF5DEB3; // Slightly warmer } // Hatch when timer reaches hatch time if (self.hatchTimer >= self.hatchTime) { self.hatchVillager(); } }; self.hatchVillager = function () { // Create hatching effects LK.effects.flashObject(self, 0xFFFFFF, 500); LK.effects.flashScreen(0xFFD700, 200); // Create a new villager at the egg position var newVillager = new Villager(); newVillager.setGridPosition(self.gridX, self.gridY); game.addChild(newVillager); villagers.push(newVillager); // Remove egg from grid and placedBlocks grid[self.gridX][self.gridY] = null; for (var i = placedBlocks.length - 1; i >= 0; i--) { if (placedBlocks[i] === self) { placedBlocks.splice(i, 1); break; } } // Play hatching sound LK.getSound('place').play(); // Create particle effects for hatching for (var p = 0; p < 8; p++) { var particle = new Container(); var particleGraphics = particle.attachAsset('villagerEgg', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); // Random direction for each particle var angle = p / 8 * Math.PI * 2; var speed = 60 + Math.random() * 40; var targetX = particle.x + Math.cos(angle) * speed; var targetY = particle.y + Math.sin(angle) * speed; // Animate particle flying out and fading tween(particle, { x: targetX, y: targetY, scaleX: 0.05, scaleY: 0.05, alpha: 0, rotation: Math.random() * Math.PI * 2 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Destroy the egg self.destroy(); }; return self; }); var Water = Container.expand(function () { var self = Container.call(this); self.blockType = 'water'; self.gridX = 0; self.gridY = 0; self.isFlowing = false; self.flowCooldown = 0; var waterGraphics = self.attachAsset('water', { anchorX: 0.5, anchorY: 0.5 }); // Add water effects waterGraphics.alpha = 0.7; // Semi-transparent water self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X; self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y; }; self.update = function () { // Gentle wave effect waterGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.08) * 0.1; waterGraphics.tint = 0x4169e1 + Math.floor(Math.sin(LK.ticks * 0.05) * 10) * 0x000100; // Check for lava collision in adjacent positions var checkPositions = [{ x: self.gridX - 1, y: self.gridY }, // Left { x: self.gridX + 1, y: self.gridY }, // Right { x: self.gridX, y: self.gridY - 1 }, // Up { x: self.gridX, y: self.gridY + 1 }, // Down { x: self.gridX - 1, y: self.gridY - 1 }, // Up-left { x: self.gridX + 1, y: self.gridY - 1 }, // Up-right { x: self.gridX - 1, y: self.gridY + 1 }, // Down-left { x: self.gridX + 1, y: self.gridY + 1 } // Down-right ]; for (var i = 0; i < checkPositions.length; i++) { var pos = checkPositions[i]; if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) { var adjacentBlock = grid[pos.x][pos.y]; if (adjacentBlock.blockType === 'lava') { // Water and lava reaction - create steam explosion effect LK.effects.flashScreen(0xFFFFFF, 800); LK.effects.flashObject(self, 0xFFFFFF, 600); LK.effects.flashObject(adjacentBlock, 0xFFFFFF, 600); // Create stone block at water position var stoneBlock = new Block('stone'); stoneBlock.setGridPosition(self.gridX, self.gridY); game.addChild(stoneBlock); grid[self.gridX][self.gridY] = stoneBlock; placedBlocks.push(stoneBlock); // Remove water block self.destroy(); for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === self) { placedBlocks.splice(j, 1); break; } } // Remove lava block adjacentBlock.destroy(); grid[pos.x][pos.y] = null; for (var k = placedBlocks.length - 1; k >= 0; k--) { if (placedBlocks[k] === adjacentBlock) { placedBlocks.splice(k, 1); break; } } // Create stone block at lava position too var stoneBlock2 = new Block('stone'); stoneBlock2.setGridPosition(pos.x, pos.y); game.addChild(stoneBlock2); grid[pos.x][pos.y] = stoneBlock2; placedBlocks.push(stoneBlock2); // Play reaction sound LK.getSound('break').play(); // Track water-lava reactions waterLavaReactions++; storage.waterLavaReactions = waterLavaReactions; checkAllAchievements(); return; // Exit after reaction } } } // Water flowing mechanics disabled - no spreading // if (self.flowCooldown > 0) { // self.flowCooldown--; // } else { // self.tryToFlow(); // self.flowCooldown = 90; // Flow every 1.5 seconds // } }; self.tryToFlow = function () { // Water flows down first, then horizontally var flowDirections = [{ x: 0, y: 1 }, // Down { x: -1, y: 0 }, // Left { x: 1, y: 0 }, // Right { x: -1, y: 1 }, // Down-left { x: 1, y: 1 } // Down-right ]; for (var i = 0; i < flowDirections.length; i++) { var dir = flowDirections[i]; var targetX = self.gridX + dir.x; var targetY = self.gridY + dir.y; if (isValidGridPosition(targetX, targetY) && grid[targetX][targetY] === null) { // Only flow if there are less than 4 water blocks nearby var nearbyWaterCount = 0; for (var checkX = targetX - 1; checkX <= targetX + 1; checkX++) { for (var checkY = targetY - 1; checkY <= targetY + 1; checkY++) { if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] !== null && grid[checkX][checkY].blockType === 'water') { nearbyWaterCount++; } } } if (nearbyWaterCount < 4) { // Create new water block var newWater = new Water(); newWater.setGridPosition(targetX, targetY); game.addChild(newWater); grid[targetX][targetY] = newWater; placedBlocks.push(newWater); LK.effects.flashObject(newWater, 0x87ceeb, 300); break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var GRID_SIZE = 80; var GRID_WIDTH = 20; var GRID_HEIGHT = 20; var GRID_OFFSET_X = (2048 - GRID_WIDTH * GRID_SIZE) / 2; var GRID_OFFSET_Y = 200; var selectedBlockType = 'dirt'; var blockTypes = ['dirt', 'stone', 'wood', 'grass', 'grassDark', 'grassLight', 'sand', 'diamond', 'gold', 'portal', 'lava', 'water', 'villagerEgg', 'villager']; var inventory = { dirt: 50, stone: 30, wood: 25, grass: 20, grassDark: 15, grassLight: 15, sand: 25, diamond: 5, gold: 8, portal: 3, lava: 10, water: 15, villagerEgg: 3, villager: 5 }; // Achievement system var achievements = storage.achievements || {}; var currentAchievementPage = 0; var achievementsPerPage = 8; var achievementDefinitions = { firstBlock: { name: "First Builder", description: "Place your first block", unlocked: false }, destroyer: { name: "Destroyer", description: "Break 10 blocks", unlocked: false }, architect: { name: "Architect", description: "Place 50 blocks", unlocked: false }, miner: { name: "Miner", description: "Break 50 blocks", unlocked: false }, collector: { name: "Collector", description: "Collect all block types", unlocked: false }, explorer: { name: "Explorer", description: "Move 100 times", unlocked: false }, masterBuilder: { name: "Master Builder", description: "Place 200 blocks", unlocked: false }, demolitionExpert: { name: "Demolition Expert", description: "Break 200 blocks", unlocked: false }, goldRush: { name: "Gold Rush", description: "Collect 20 gold blocks", unlocked: false }, diamondCollector: { name: "Diamond Collector", description: "Collect 15 diamond blocks", unlocked: false }, woodsman: { name: "Woodsman", description: "Collect 100 wood blocks", unlocked: false }, stoneAge: { name: "Stone Age", description: "Collect 150 stone blocks", unlocked: false }, adventurer: { name: "Adventurer", description: "Move 500 times", unlocked: false }, nomad: { name: "Nomad", description: "Move 1000 times", unlocked: false }, flightTime: { name: "Taking Flight", description: "Use flying mode 10 times", unlocked: false }, skyExplorer: { name: "Sky Explorer", description: "Spend 60 seconds in flying mode", unlocked: false }, areCrazy: { name: "are you crazy?", description: "Break each block type 10 times", unlocked: false }, boooing: { name: "boooing", description: "Jump 100 times", unlocked: false }, lavaLord: { name: "Lava Lord", description: "Place 25 lava blocks", unlocked: false }, waterMaster: { name: "Water Master", description: "Place 30 water blocks", unlocked: false }, speedBuilder: { name: "Speed Builder", description: "Place 20 blocks in 30 seconds", unlocked: false }, speedDestroyer: { name: "Speed Destroyer", description: "Break 15 blocks in 20 seconds", unlocked: false }, portalMaster: { name: "Portal Master", description: "Use portals 25 times", unlocked: false }, heavenReacher: { name: "Heaven Reacher", description: "Reach heaven biome 5 times", unlocked: false }, sandCastleKing: { name: "Sand Castle King", description: "Place 40 sand blocks", unlocked: false }, grassGardener: { name: "Grass Gardener", description: "Place 100 grass blocks total", unlocked: false }, undergroundExplorer: { name: "Underground Explorer", description: "Place blocks below Y level 15", unlocked: false }, skyBuilder: { name: "Sky Builder", description: "Place blocks above Y level 5", unlocked: false }, elementalReactor: { name: "Elemental Reactor", description: "Trigger 10 water-lava reactions", unlocked: false }, survivalist: { name: "Survivalist", description: "Survive 5 lava touches", unlocked: false }, teleporter: { name: "Teleporter", description: "Travel 1000 grid units total", unlocked: false }, perfectionist: { name: "Perfectionist", description: "Maintain max inventory for 5 minutes", unlocked: false }, nightOwl: { name: "Night Owl", description: "Play for 10 minutes straight", unlocked: false }, marathonPlayer: { name: "Marathon Player", description: "Play for 30 minutes straight", unlocked: false }, blockChain: { name: "Block Chain", description: "Place 10 blocks in a row without breaking", unlocked: false }, demolitionChain: { name: "Demolition Chain", description: "Break 10 blocks in a row without placing", unlocked: false }, rainbowBuilder: { name: "Rainbow Builder", description: "Place all 12 block types in one session", unlocked: false }, cornerExplorer: { name: "Corner Explorer", description: "Visit all 4 corners of the world", unlocked: false }, centerist: { name: "Centerist", description: "Spend 2 minutes at world center", unlocked: false }, pacifist: { name: "Pacifist", description: "Place 100 blocks without breaking any", unlocked: false }, destroyer2: { name: "World Destroyer", description: "Break 100 blocks without placing any", unlocked: false }, inventoryManager: { name: "Inventory Manager", description: "Use every block type at least once", unlocked: false }, highFlyer: { name: "High Flyer", description: "Fly to the top row 10 times", unlocked: false }, deepDigger: { name: "Deep Digger", description: "Reach the bottom row 10 times", unlocked: false } }; // Achievement counters var blocksPlaced = storage.blocksPlaced || 0; var blocksBroken = storage.blocksBroken || 0; var moveCount = storage.moveCount || 0; var goldCollected = storage.goldCollected || 0; var diamondCollected = storage.diamondCollected || 0; var woodCollected = storage.woodCollected || 0; var stoneCollected = storage.stoneCollected || 0; var flyingActivations = storage.flyingActivations || 0; var flyingStartTime = 0; var totalFlyingTime = storage.totalFlyingTime || 0; var blockTypeBrokenCount = storage.blockTypeBrokenCount || {}; var jumpCount = storage.jumpCount || 0; var lavaPlaced = storage.lavaPlaced || 0; var waterPlaced = storage.waterPlaced || 0; var portalUses = storage.portalUses || 0; var heavenVisits = storage.heavenVisits || 0; var sandPlaced = storage.sandPlaced || 0; var grassPlaced = storage.grassPlaced || 0; var waterLavaReactions = storage.waterLavaReactions || 0; var lavaTouches = storage.lavaTouches || 0; var totalDistanceTraveled = storage.totalDistanceTraveled || 0; var gameStartTime = LK.ticks; var maxInventoryStartTime = 0; var totalMaxInventoryTime = storage.totalMaxInventoryTime || 0; var consecutiveBlocksPlaced = 0; var consecutiveBlocksBroken = 0; var usedBlockTypesThisSession = {}; var visitedCorners = storage.visitedCorners || {}; var centerTime = 0; var consecutivePlacingStreak = storage.consecutivePlacingStreak || 0; var consecutiveBreakingStreak = storage.consecutiveBreakingStreak || 0; var topRowVisits = storage.topRowVisits || 0; var bottomRowVisits = storage.bottomRowVisits || 0; var undergroundBuilding = storage.undergroundBuilding || 0; var skyBuilding = storage.skyBuilding || 0; var buildingSpeedStart = 0; var buildingSpeedCount = 0; var breakingSpeedStart = 0; var breakingSpeedCount = 0; var grid = []; var placedBlocks = []; var villagers = []; var toolbarButtons = []; var controlButtons = []; var pressTimer = null; var isLongPress = false; var pressStartTime = 0; var playerAttackPosition = null; // Track where player last attacked var playerAttackTime = 0; // When the attack happened var character = null; var blockSelectionMenu = null; var menuButton = null; var achievementNotification = null; var achievementMenu = null; var characterGravity = 0; // Current falling speed var characterMaxFallSpeed = 8; // Maximum falling speed var characterGravityAcceleration = 0.5; // How fast gravity accelerates var isFlying = false; // Flying mode toggle var flyingSpeed = 4; // Speed when flying var flyingVerticalSpeed = 3; // Vertical movement speed when flying function checkAchievement(achievementId) { if (achievements[achievementId]) return; var achieved = false; switch (achievementId) { case 'firstBlock': achieved = blocksPlaced >= 1; break; case 'destroyer': achieved = blocksBroken >= 10; break; case 'architect': achieved = blocksPlaced >= 50; break; case 'miner': achieved = blocksBroken >= 50; break; case 'collector': // Check if player has collected all block types var hasAllTypes = true; for (var blockType in inventory) { if (inventory[blockType] <= 0) { hasAllTypes = false; break; } } achieved = hasAllTypes; break; case 'explorer': achieved = moveCount >= 100; break; case 'masterBuilder': achieved = blocksPlaced >= 200; break; case 'demolitionExpert': achieved = blocksBroken >= 200; break; case 'goldRush': achieved = goldCollected >= 20; break; case 'diamondCollector': achieved = diamondCollected >= 15; break; case 'woodsman': achieved = woodCollected >= 100; break; case 'stoneAge': achieved = stoneCollected >= 150; break; case 'adventurer': achieved = moveCount >= 500; break; case 'nomad': achieved = moveCount >= 1000; break; case 'flightTime': achieved = flyingActivations >= 10; break; case 'skyExplorer': achieved = totalFlyingTime >= 60000; // 60 seconds in milliseconds break; case 'areCrazy': // Check if each block type has been broken at least 10 times achieved = true; for (var i = 0; i < blockTypes.length; i++) { var blockType = blockTypes[i]; var brokenCount = blockTypeBrokenCount[blockType] || 0; if (brokenCount < 10) { achieved = false; break; } } break; case 'boooing': achieved = jumpCount >= 100; break; case 'lavaLord': achieved = lavaPlaced >= 25; break; case 'waterMaster': achieved = waterPlaced >= 30; break; case 'speedBuilder': achieved = buildingSpeedCount >= 20; break; case 'speedDestroyer': achieved = breakingSpeedCount >= 15; break; case 'portalMaster': achieved = portalUses >= 25; break; case 'heavenReacher': achieved = heavenVisits >= 5; break; case 'sandCastleKing': achieved = sandPlaced >= 40; break; case 'grassGardener': achieved = grassPlaced >= 100; break; case 'undergroundExplorer': achieved = undergroundBuilding >= 1; break; case 'skyBuilder': achieved = skyBuilding >= 1; break; case 'elementalReactor': achieved = waterLavaReactions >= 10; break; case 'survivalist': achieved = lavaTouches >= 5; break; case 'teleporter': achieved = totalDistanceTraveled >= 1000; break; case 'perfectionist': achieved = totalMaxInventoryTime >= 300000; // 5 minutes in milliseconds break; case 'nightOwl': var currentTime = LK.ticks; achieved = currentTime - gameStartTime >= 36000; // 10 minutes at 60fps break; case 'marathonPlayer': var currentTime = LK.ticks; achieved = currentTime - gameStartTime >= 108000; // 30 minutes at 60fps break; case 'blockChain': achieved = consecutivePlacingStreak >= 10; break; case 'demolitionChain': achieved = consecutiveBreakingStreak >= 10; break; case 'rainbowBuilder': achieved = Object.keys(usedBlockTypesThisSession).length >= 12; break; case 'cornerExplorer': achieved = Object.keys(visitedCorners).length >= 4; break; case 'centerist': achieved = centerTime >= 7200; // 2 minutes at 60fps break; case 'pacifist': achieved = consecutivePlacingStreak >= 100; break; case 'destroyer2': achieved = consecutiveBreakingStreak >= 100; break; case 'inventoryManager': var usedAllTypes = true; for (var i = 0; i < blockTypes.length; i++) { if (!usedBlockTypesThisSession[blockTypes[i]]) { usedAllTypes = false; break; } } achieved = usedAllTypes; break; case 'highFlyer': achieved = topRowVisits >= 10; break; case 'deepDigger': achieved = bottomRowVisits >= 10; break; } if (achieved) { unlockAchievement(achievementId); } } function unlockAchievement(achievementId) { if (achievements[achievementId]) return; achievements[achievementId] = true; storage.achievements = achievements; // Update the achievement menu to show the newly unlocked achievement if (achievementMenu) { achievementMenu.updateAchievements(); // Show the achievement menu temporarily if not already visible if (!achievementMenu.isVisible) { achievementMenu.show(); // Auto-hide after a few seconds LK.setTimeout(function () { if (achievementMenu && achievementMenu.isVisible) { achievementMenu.hide(); } }, 3000); } } } function checkAllAchievements() { for (var achievementId in achievementDefinitions) { checkAchievement(achievementId); } } // Initialize grid for (var x = 0; x < GRID_WIDTH; x++) { grid[x] = []; for (var y = 0; y < GRID_HEIGHT; y++) { grid[x][y] = null; var gridCell = new GridCell(); gridCell.setGridPosition(x, y); game.addChild(gridCell); } } // Initialize character at top of world character = new Character(); var randomX = Math.floor(Math.random() * GRID_WIDTH); var topY = 0; // Always start at the very top character.setGridPosition(randomX, topY); game.addChild(character); // Create block selection menu blockSelectionMenu = new BlockSelectionMenu(); blockSelectionMenu.x = 1024; blockSelectionMenu.y = 800; LK.gui.addChild(blockSelectionMenu); // Create achievement menu at top layer achievementMenu = new AchievementMenu(); achievementMenu.x = 1024; achievementMenu.y = 400; LK.gui.addChild(achievementMenu); // Create menu button menuButton = new Container(); var menuBg = menuButton.attachAsset('toolbarBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var menuText = new Text2('MENU', { size: 36, fill: 0xFFFFFF }); menuText.anchor.set(0.5, 0.5); menuText.x = 0; menuText.y = 0; menuButton.addChild(menuText); menuButton.x = 2048 - 80; menuButton.y = 150; menuButton.down = function (x, y, obj) { menuBg.alpha = 1.0; if (!blockSelectionMenu.isVisible) { blockSelectionMenu.show(); } }; menuButton.up = function (x, y, obj) { menuBg.alpha = 0.8; }; game.addChild(menuButton); // Create achievements button at the top var achievementsButton = new Container(); var achievementsBg = achievementsButton.attachAsset('toolbarBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var achievementsText = new Text2('ACHIEVEMENTS', { size: 28, fill: 0xFFFFFF }); achievementsText.anchor.set(0.5, 0.5); achievementsText.x = 0; achievementsText.y = 0; achievementsButton.addChild(achievementsText); achievementsButton.x = 1024; achievementsButton.y = 50; achievementsButton.down = function (x, y, obj) { achievementsBg.alpha = 1.0; if (!achievementMenu.isVisible) { achievementMenu.show(); } }; achievementsButton.up = function (x, y, obj) { achievementsBg.alpha = 0.8; }; game.addChild(achievementsButton); // Create flying toggle button var flyingButton = new Container(); var flyingBg = flyingButton.attachAsset('toolbarBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var flyingText = new Text2('FLY', { size: 32, fill: 0xFFFFFF }); flyingText.anchor.set(0.5, 0.5); flyingText.x = 0; flyingText.y = 0; flyingButton.addChild(flyingText); flyingButton.x = 2048 - 80; flyingButton.y = 350; flyingButton.down = function (x, y, obj) { flyingBg.alpha = 1.0; // Toggle flying mode var wasFlying = isFlying; isFlying = !isFlying; // Track flying time and activations if (isFlying && !wasFlying) { // Started flying flyingActivations++; storage.flyingActivations = flyingActivations; flyingStartTime = LK.ticks; checkAllAchievements(); } else if (!isFlying && wasFlying) { // Stopped flying if (flyingStartTime > 0) { var flyingDuration = (LK.ticks - flyingStartTime) * (1000 / 60); // Convert ticks to milliseconds totalFlyingTime += flyingDuration; storage.totalFlyingTime = totalFlyingTime; flyingStartTime = 0; checkAllAchievements(); } } // Update button appearance - recreate the background with new color flyingButton.removeChild(flyingBg); flyingBg = flyingButton.attachAsset('toolbarBg', { anchorX: 0.5, anchorY: 0.5, alpha: 1.0 }); if (isFlying) { flyingBg.tint = 0x00FF00; } else { flyingBg.tint = 0xFFFFFF; } flyingText.setText(isFlying ? 'LAND' : 'FLY'); // Visual feedback if (isFlying) { LK.effects.flashObject(character, 0x00FFFF, 500); } }; flyingButton.up = function (x, y, obj) { flyingBg.alpha = 0.8; }; game.addChild(flyingButton); // Create flying status display var flyingStatusText = new Text2('', { size: 28, fill: 0x00FFFF }); flyingStatusText.anchor.set(0.5, 0); flyingStatusText.x = 1024; flyingStatusText.y = 100; LK.gui.addChild(flyingStatusText); // Create toolbar var toolbarY = 2732 - 120; var toolbarStartX = (2048 - blockTypes.length * 120) / 2; for (var i = 0; i < blockTypes.length; i++) { var button = new ToolbarButton(blockTypes[i]); button.x = toolbarStartX + i * 120 + 60; button.y = toolbarY; toolbarButtons.push(button); LK.gui.addChild(button); } // Create inventory display var inventoryTexts = {}; for (var i = 0; i < blockTypes.length; i++) { var blockType = blockTypes[i]; var inventoryText = new Text2(inventory[blockType].toString(), { size: 36, fill: 0xFFFFFF }); inventoryText.anchor.set(0.5, 0); inventoryText.x = toolbarStartX + i * 120 + 60; inventoryText.y = toolbarY + 60; inventoryTexts[blockType] = inventoryText; LK.gui.addChild(inventoryText); } // Create control buttons var controlButtonSize = 100; var controlPadding = 20; var controlCenterX = 150; var controlCenterY = GRID_OFFSET_Y + GRID_HEIGHT * GRID_SIZE + 150; var directions = [{ dir: 'up', x: 0, y: -1 }, { dir: 'down', x: 0, y: 1 }, { dir: 'left', x: -1, y: 0 }, { dir: 'right', x: 1, y: 0 }]; for (var i = 0; i < directions.length; i++) { var dirInfo = directions[i]; var button = new ControlButton(dirInfo.dir); button.x = controlCenterX + dirInfo.x * (controlButtonSize + controlPadding); button.y = controlCenterY + dirInfo.y * (controlButtonSize + controlPadding); controlButtons.push(button); game.addChild(button); } function updateToolbarSelection() { for (var i = 0; i < toolbarButtons.length; i++) { toolbarButtons[i].setSelected(toolbarButtons[i].blockType === selectedBlockType); } } function updateInventoryDisplay() { for (var blockType in inventoryTexts) { inventoryTexts[blockType].setText(inventory[blockType].toString()); } // Update block selection menu inventory counts if (blockSelectionMenu) { blockSelectionMenu.updateInventoryCounts(); } } function getGridPosition(worldX, worldY) { var gridX = Math.floor((worldX - GRID_OFFSET_X) / GRID_SIZE); var gridY = Math.floor((worldY - GRID_OFFSET_Y) / GRID_SIZE); return { x: gridX, y: gridY }; } function isValidGridPosition(gridX, gridY) { return gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT; } function placeBlock(gridX, gridY) { if (!isValidGridPosition(gridX, gridY)) return false; if (grid[gridX][gridY] !== null) return false; if (inventory[selectedBlockType] <= 0) return false; // Don't place block on character position if (character && gridX === character.gridX && gridY === character.gridY) return false; var block; if (selectedBlockType === 'sand') { block = new Sand(); } else if (selectedBlockType === 'portal') { block = new Portal(); } else if (selectedBlockType === 'lava') { block = new Lava(); } else if (selectedBlockType === 'water') { block = new Water(); } else if (selectedBlockType === 'villagerEgg') { block = new VillagerEgg(); } else if (selectedBlockType === 'villager') { // Create a villager directly instead of a block var newVillager = new Villager(); newVillager.setGridPosition(gridX, gridY); game.addChild(newVillager); villagers.push(newVillager); inventory[selectedBlockType]--; updateInventoryDisplay(); LK.getSound('place').play(); // Track achievement progress blocksPlaced++; storage.blocksPlaced = blocksPlaced; consecutivePlacingStreak++; storage.consecutivePlacingStreak = consecutivePlacingStreak; consecutiveBreakingStreak = 0; // Reset breaking streak storage.consecutiveBreakingStreak = 0; // Track used block types this session usedBlockTypesThisSession[selectedBlockType] = true; checkAllAchievements(); return true; } else { block = new Block(selectedBlockType); } block.setGridPosition(gridX, gridY); game.addChild(block); grid[gridX][gridY] = block; placedBlocks.push(block); inventory[selectedBlockType]--; updateInventoryDisplay(); LK.getSound('place').play(); // Track achievement progress blocksPlaced++; storage.blocksPlaced = blocksPlaced; consecutivePlacingStreak++; storage.consecutivePlacingStreak = consecutivePlacingStreak; consecutiveBreakingStreak = 0; // Reset breaking streak storage.consecutiveBreakingStreak = 0; // Track used block types this session usedBlockTypesThisSession[selectedBlockType] = true; // Track specific block type placements if (selectedBlockType === 'lava') { lavaPlaced++; storage.lavaPlaced = lavaPlaced; } if (selectedBlockType === 'water') { waterPlaced++; storage.waterPlaced = waterPlaced; } if (selectedBlockType === 'sand') { sandPlaced++; storage.sandPlaced = sandPlaced; } if (selectedBlockType === 'grass' || selectedBlockType === 'grassDark' || selectedBlockType === 'grassLight') { grassPlaced++; storage.grassPlaced = grassPlaced; } // Track building location achievements if (gridY >= 15) { undergroundBuilding++; storage.undergroundBuilding = undergroundBuilding; } if (gridY <= 5) { skyBuilding++; storage.skyBuilding = skyBuilding; } // Track speed building if (buildingSpeedStart === 0) { buildingSpeedStart = LK.ticks; buildingSpeedCount = 1; } else { var timeDiff = LK.ticks - buildingSpeedStart; if (timeDiff <= 1800) { // 30 seconds at 60fps buildingSpeedCount++; } else { buildingSpeedStart = LK.ticks; buildingSpeedCount = 1; } } checkAllAchievements(); return true; } function removeBlock(gridX, gridY) { if (!isValidGridPosition(gridX, gridY)) return false; if (grid[gridX][gridY] === null) return false; var block = grid[gridX][gridY]; var blockType = block.blockType; // If breaking a portal, teleport character to heaven biome if (blockType === 'portal') { // Transition to heaven biome permanently game.setBackgroundColor(0xFFD700); // Golden heaven sky // Flash screen with heavenly light LK.effects.flashScreen(0xFFFFFF, 2000); // Teleport character to top of world center var heavenX = Math.floor(GRID_WIDTH / 2); var heavenY = 0; // Top of the world character.setGridPosition(heavenX, heavenY); // Add visual effects LK.effects.flashObject(block, 0xFFD700, 1000); LK.effects.flashObject(character, 0xFFFFFF, 1000); // Play portal sound LK.getSound('portal').play(); // Clear all existing blocks to create clean heaven environment for (var x = 0; x < GRID_WIDTH; x++) { for (var y = 0; y < GRID_HEIGHT; y++) { if (grid[x][y] !== null && grid[x][y] !== block) { var existingBlock = grid[x][y]; existingBlock.destroy(); grid[x][y] = null; // Remove from placedBlocks array for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === existingBlock) { placedBlocks.splice(j, 1); break; } } } } } // Generate heaven environment with golden blocks and clouds selectedBlockType = 'gold'; // Create golden platform in heaven var heavenPlatformY = Math.floor(GRID_HEIGHT * 0.8); for (var x = 0; x < GRID_WIDTH; x++) { placeBlock(x, heavenPlatformY); if (Math.random() < 0.3) { placeBlock(x, heavenPlatformY - 1); // Add some elevation } } // Add diamond clouds scattered around selectedBlockType = 'diamond'; for (var i = 0; i < 15; i++) { var cloudX = Math.floor(Math.random() * GRID_WIDTH); var cloudY = Math.floor(Math.random() * heavenPlatformY * 0.7); if (isValidGridPosition(cloudX, cloudY) && grid[cloudX][cloudY] === null) { placeBlock(cloudX, cloudY); } } // Reset selection selectedBlockType = 'dirt'; updateToolbarSelection(); } // Create particle effects - multiple small pieces flying out for (var p = 0; p < 6; p++) { var particle = new Container(); var particleGraphics = particle.attachAsset(blockType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particle.x = block.x; particle.y = block.y; game.addChild(particle); // Random direction for each particle var angle = p / 6 * Math.PI * 2 + (Math.random() - 0.5) * 0.5; var speed = 100 + Math.random() * 80; var targetX = particle.x + Math.cos(angle) * speed; var targetY = particle.y + Math.sin(angle) * speed; // Animate particle flying out and fading tween(particle, { x: targetX, y: targetY, scaleX: 0.1, scaleY: 0.1, alpha: 0, rotation: Math.random() * Math.PI * 2 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Add breaking animation with flash effect and rotation LK.effects.flashObject(block, 0xFFFFFF, 200); // Add screen shake effect for impact var originalX = game.x; var originalY = game.y; var shakeIntensity = 8; tween(game, { x: originalX + shakeIntensity }, { duration: 30, easing: tween.easeOut }); LK.setTimeout(function () { tween(game, { x: originalX - shakeIntensity }, { duration: 30, easing: tween.easeOut }); }, 30); LK.setTimeout(function () { tween(game, { x: originalX, y: originalY }, { duration: 40, easing: tween.easeOut }); }, 60); // Remove from placedBlocks array for (var i = placedBlocks.length - 1; i >= 0; i--) { if (placedBlocks[i] === block) { placedBlocks.splice(i, 1); break; } } // Animate block destruction with scaling and rotation tween(block, { scaleX: 0, scaleY: 0, alpha: 0, rotation: Math.PI * 0.5 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { block.destroy(); } }); grid[gridX][gridY] = null; inventory[blockType]++; updateInventoryDisplay(); LK.getSound('break').play(); // Track achievement progress blocksBroken++; storage.blocksBroken = blocksBroken; consecutiveBreakingStreak++; storage.consecutiveBreakingStreak = consecutiveBreakingStreak; consecutivePlacingStreak = 0; // Reset placing streak storage.consecutivePlacingStreak = 0; // Track specific resource collection if (blockType === 'gold') { goldCollected++; storage.goldCollected = goldCollected; } else if (blockType === 'diamond') { diamondCollected++; storage.diamondCollected = diamondCollected; } else if (blockType === 'wood') { woodCollected++; storage.woodCollected = woodCollected; } else if (blockType === 'stone') { stoneCollected++; storage.stoneCollected = stoneCollected; } // Track block type breaking for 'are you crazy?' achievement if (!blockTypeBrokenCount[blockType]) { blockTypeBrokenCount[blockType] = 0; } blockTypeBrokenCount[blockType]++; storage.blockTypeBrokenCount = blockTypeBrokenCount; // Track speed breaking if (breakingSpeedStart === 0) { breakingSpeedStart = LK.ticks; breakingSpeedCount = 1; } else { var timeDiff = LK.ticks - breakingSpeedStart; if (timeDiff <= 1200) { // 20 seconds at 60fps breakingSpeedCount++; } else { breakingSpeedStart = LK.ticks; breakingSpeedCount = 1; } } checkAllAchievements(); return true; } // Initialize toolbar selection updateToolbarSelection(); updateInventoryDisplay(); game.down = function (x, y, obj) { var gridPos = getGridPosition(x, y); pressStartTime = LK.ticks; isLongPress = false; // Check if clicking on a villager first for (var i = 0; i < villagers.length; i++) { var villager = villagers[i]; if (villager.isAlive && villager.gridX === gridPos.x && villager.gridY === gridPos.y) { // Record player attack for other villagers to react to playerAttackPosition = { x: gridPos.x, y: gridPos.y }; playerAttackTime = LK.ticks; villager.kill(); return; // Exit early if we killed a villager } } // Start long press timer for breaking blocks if (pressTimer) { LK.clearTimeout(pressTimer); } pressTimer = LK.setTimeout(function () { isLongPress = true; // Break only the specific block at clicked position if (isValidGridPosition(gridPos.x, gridPos.y) && grid[gridPos.x][gridPos.y] !== null) { removeBlock(gridPos.x, gridPos.y); } }, 500); // 500ms long press }; game.up = function (x, y, obj) { // Clear the long press timer if (pressTimer) { LK.clearTimeout(pressTimer); pressTimer = null; } // If it wasn't a long press, place a block if (!isLongPress) { var gridPos = getGridPosition(x, y); if (isValidGridPosition(gridPos.x, gridPos.y)) { placeBlock(gridPos.x, gridPos.y); } } }; // Generate procedural world with biomes function generateWorld() { // Reset selected block type to dirt for generation selectedBlockType = 'dirt'; updateToolbarSelection(); // Create height map for smooth terrain var heightMap = []; var baseHeightLevel = Math.floor(GRID_HEIGHT * 0.7); // Base terrain level for (var x = 0; x < GRID_WIDTH; x++) { // Generate smooth terrain using sine waves var primary = Math.sin(x / GRID_WIDTH * Math.PI * 2) * 3; var secondary = Math.sin(x / GRID_WIDTH * Math.PI * 6) * 1.5; var noise = (Math.random() - 0.5) * 2; var height = baseHeightLevel + primary + secondary + noise; heightMap[x] = Math.floor(Math.max(5, Math.min(height, GRID_HEIGHT - 3))); } // Smooth the height map to reduce abrupt changes for (var pass = 0; pass < 2; pass++) { for (var x = 1; x < GRID_WIDTH - 1; x++) { var avg = (heightMap[x - 1] + heightMap[x] + heightMap[x + 1]) / 3; heightMap[x] = Math.floor(avg); } } // Define biome regions - now with 5 sections for more biomes var biome1 = Math.floor(GRID_WIDTH * 0.2); var biome2 = Math.floor(GRID_WIDTH * 0.4); var biome3 = Math.floor(GRID_WIDTH * 0.6); var biome4 = Math.floor(GRID_WIDTH * 0.8); var biomes = ['plains', 'forest', 'mountains', 'lava_pools', 'village']; var currentBiomes = [biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)]]; // Generate terrain layers for (var x = 0; x < GRID_WIDTH; x++) { var currentBiome; if (x < biome1) { currentBiome = currentBiomes[0]; } else if (x < biome2) { currentBiome = currentBiomes[1]; } else if (x < biome3) { currentBiome = currentBiomes[2]; } else if (x < biome4) { currentBiome = currentBiomes[3]; } else { currentBiome = currentBiomes[4]; } var baseHeight = heightMap[x]; var surfaceBlock = 'grass'; var treeChance = 0.15; var stoneChance = 0.1; // Biome-specific properties if (currentBiome === 'plains') { surfaceBlock = 'grass'; treeChance = 0.08; stoneChance = 0.05; } else if (currentBiome === 'forest') { surfaceBlock = 'grass'; treeChance = 0.25; // More trees stoneChance = 0.08; } else if (currentBiome === 'mountains') { surfaceBlock = 'stone'; // Rocky surface treeChance = 0.05; // Few trees at high altitude stoneChance = 0.25; // Very rocky // Adjust height for mountains baseHeight = Math.max(baseHeight - 3, Math.floor(GRID_HEIGHT * 0.5)); } else if (currentBiome === 'lava_pools') { surfaceBlock = 'stone'; // Rocky volcanic surface treeChance = 0.02; // Almost no trees stoneChance = 0.4; // Very rocky volcanic terrain // Slightly lower terrain for lava pools baseHeight = Math.max(baseHeight + 1, Math.floor(GRID_HEIGHT * 0.75)); } else if (currentBiome === 'village') { surfaceBlock = 'grass'; // Nice grassy village surface treeChance = 0.1; // Moderate trees stoneChance = 0.03; // Clean village area // Flatten terrain for village baseHeight = Math.floor(GRID_HEIGHT * 0.7); } // Place surface layer selectedBlockType = surfaceBlock; placeBlock(x, baseHeight); // Place sub-surface layers consistently selectedBlockType = 'dirt'; var dirtLayers = currentBiome === 'mountains' ? 2 : 3; for (var y = baseHeight + 1; y < Math.min(baseHeight + dirtLayers + 1, GRID_HEIGHT); y++) { placeBlock(x, y); } // Place stone in deeper layers consistently selectedBlockType = 'stone'; for (var y = baseHeight + dirtLayers + 1; y < GRID_HEIGHT; y++) { var stoneProb = currentBiome === 'mountains' ? 0.9 : 0.8; if (Math.random() < stoneProb) { placeBlock(x, y); } } // Add trees in groups for natural look if (Math.random() < treeChance && baseHeight > 2) { selectedBlockType = 'wood'; var treeHeight; if (currentBiome === 'forest') { treeHeight = Math.floor(Math.random() * 3) + 3; // Tall trees } else { treeHeight = Math.floor(Math.random() * 2) + 2; // Normal trees } for (var i = 0; i < treeHeight; i++) { var treeY = baseHeight - i - 1; if (treeY >= 0) { placeBlock(x, treeY); } } // Add tree leaves/branches if (currentBiome === 'forest' && treeHeight >= 3) { selectedBlockType = 'grassLight'; if (x > 0 && baseHeight - treeHeight >= 0) { placeBlock(x - 1, baseHeight - treeHeight + 1); } if (x < GRID_WIDTH - 1 && baseHeight - treeHeight >= 0) { placeBlock(x + 1, baseHeight - treeHeight + 1); } } } // Add surface features sparingly if (Math.random() < stoneChance && baseHeight > 0) { selectedBlockType = 'stone'; placeBlock(x, baseHeight - 1); } // Add biome-specific features occasionally if (currentBiome === 'mountains' && Math.random() < 0.1) { selectedBlockType = 'stone'; if (baseHeight > 1) { placeBlock(x, baseHeight - 1); } } else if (currentBiome === 'lava_pools' && Math.random() < 0.15) { // Create lava pools selectedBlockType = 'lava'; placeBlock(x, baseHeight + 1); if (Math.random() < 0.6) { placeBlock(x, baseHeight + 2); // Deeper lava } } else if (currentBiome === 'village' && Math.random() < 0.08) { // Create simple village structures selectedBlockType = 'wood'; for (var houseHeight = 0; houseHeight < 3; houseHeight++) { var houseY = baseHeight - houseHeight - 1; if (houseY >= 0) { placeBlock(x, houseY); } } } } // Add minimal scattered resources in logical places for (var i = 0; i < 15; i++) { var randX = Math.floor(Math.random() * GRID_WIDTH); var surfaceY = heightMap[randX]; // Place resources near surface or in underground areas var randY = Math.random() < 0.6 ? surfaceY + Math.floor(Math.random() * 3) + 1 : Math.floor(Math.random() * (GRID_HEIGHT - surfaceY - 5)) + surfaceY + 3; if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) { var resourceType; if (randY > surfaceY + 2) { // Underground resources resourceType = Math.random() < 0.6 ? 'stone' : 'dirt'; } else { // Surface resources resourceType = blockTypes[Math.floor(Math.random() * blockTypes.length)]; } selectedBlockType = resourceType; placeBlock(randX, randY); } } // Add rare precious blocks deep underground for (var i = 0; i < 3; i++) { var randX = Math.floor(Math.random() * GRID_WIDTH); var surfaceY = heightMap[randX]; // Place deep underground only var randY = Math.floor(Math.random() * 3) + GRID_HEIGHT - 5; if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) { var preciousType = Math.random() < 0.4 ? 'diamond' : 'gold'; selectedBlockType = preciousType; placeBlock(randX, randY); } } // Add portals with 10% chance - place 1-2 portals randomly if (Math.random() < 0.1) { var numPortals = Math.floor(Math.random() * 2) + 1; // 1 or 2 portals for (var p = 0; p < numPortals; p++) { var attempts = 0; var portalPlaced = false; while (attempts < 20 && !portalPlaced) { var randX = Math.floor(Math.random() * GRID_WIDTH); var surfaceY = heightMap[randX]; var randY = surfaceY - Math.floor(Math.random() * 3) - 1; // Place above surface if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) { selectedBlockType = 'portal'; if (placeBlock(randX, randY)) { portalPlaced = true; } } attempts++; } } } // Create a few small, deliberate caves for (var caveCount = 0; caveCount < 2; caveCount++) { var caveX = Math.floor(Math.random() * (GRID_WIDTH - 4)) + 2; var surfaceHeight = heightMap[caveX]; var caveY = surfaceHeight + Math.floor(Math.random() * 4) + 3; var caveSize = 1; // Create small hollow areas for (var cx = caveX - caveSize; cx <= caveX + caveSize; cx++) { for (var cy = caveY - caveSize; cy <= caveY + caveSize; cy++) { if (isValidGridPosition(cx, cy) && grid[cx][cy] !== null) { var block = grid[cx][cy]; if (block) { block.destroy(); grid[cx][cy] = null; // Remove from placedBlocks array for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === block) { placedBlocks.splice(j, 1); break; } } } } } } } // Spawn villagers in village biomes for (var x = 0; x < GRID_WIDTH; x++) { var currentBiome; if (x < biome1) { currentBiome = currentBiomes[0]; } else if (x < biome2) { currentBiome = currentBiomes[1]; } else if (x < biome3) { currentBiome = currentBiomes[2]; } else if (x < biome4) { currentBiome = currentBiomes[3]; } else { currentBiome = currentBiomes[4]; } if (currentBiome === 'village' && Math.random() < 0.05) { // Find surface level for villager placement var surfaceY = heightMap[x]; var villagerY = surfaceY - 1; // Make sure villager has ground to stand on if (villagerY >= 0 && villagerY < GRID_HEIGHT) { var villager = new Villager(); villager.setGridPosition(x, villagerY); game.addChild(villager); villagers.push(villager); } } else if (currentBiome === 'village' && Math.random() < 0.03) { // Sometimes place villager eggs instead of villagers var surfaceY = heightMap[x]; var eggY = surfaceY - 1; if (eggY >= 0 && eggY < GRID_HEIGHT && grid[x][eggY] === null) { selectedBlockType = 'villagerEgg'; placeBlock(x, eggY); } } } // Create enhanced cave systems for (var caveCount = 0; caveCount < 4; caveCount++) { var caveX = Math.floor(Math.random() * (GRID_WIDTH - 6)) + 3; var surfaceHeight = heightMap[caveX]; var caveY = surfaceHeight + Math.floor(Math.random() * 6) + 4; var caveSize = Math.floor(Math.random() * 2) + 1; // 1-2 size caves // Create larger cave chambers for (var cx = caveX - caveSize; cx <= caveX + caveSize; cx++) { for (var cy = caveY - caveSize; cy <= caveY + caveSize; cy++) { if (isValidGridPosition(cx, cy) && grid[cx][cy] !== null) { var block = grid[cx][cy]; if (block) { block.destroy(); grid[cx][cy] = null; // Remove from placedBlocks array for (var j = placedBlocks.length - 1; j >= 0; j--) { if (placedBlocks[j] === block) { placedBlocks.splice(j, 1); break; } } } } } } // Add cave decorations - stalactites and stalagmites if (caveSize > 1) { selectedBlockType = 'stone'; // Stalactites from ceiling for (var i = 0; i < 3; i++) { var stalX = caveX + Math.floor(Math.random() * 3) - 1; var stalY = caveY - caveSize; if (isValidGridPosition(stalX, stalY) && grid[stalX][stalY] === null) { placeBlock(stalX, stalY); } } // Stalagmites from floor for (var i = 0; i < 2; i++) { var stalagX = caveX + Math.floor(Math.random() * 3) - 1; var stalagY = caveY + caveSize; if (isValidGridPosition(stalagX, stalagY) && grid[stalagX][stalagY] === null) { placeBlock(stalagX, stalagY); } } } } // Reset to dirt selection selectedBlockType = 'dirt'; updateToolbarSelection(); } // Generate the world after a short delay LK.setTimeout(function () { generateWorld(); }, 500); function applyGravity() { var blocksToMove = []; // Check all blocks from bottom to top for (var y = GRID_HEIGHT - 2; y >= 0; y--) { for (var x = 0; x < GRID_WIDTH; x++) { var block = grid[x][y]; if (block !== null) { // Check if block has support below var hasSupport = false; for (var checkY = y + 1; checkY < GRID_HEIGHT; checkY++) { if (grid[x][checkY] !== null) { hasSupport = true; break; } } // If no support, mark for falling if (!hasSupport) { var fallY = y; // Find where it should fall to for (var targetY = y + 1; targetY < GRID_HEIGHT; targetY++) { if (grid[x][targetY] !== null) { break; } fallY = targetY; } if (fallY !== y) { blocksToMove.push({ block: block, fromX: x, fromY: y, toX: x, toY: fallY }); } } } } } // Move blocks that need to fall for (var i = 0; i < blocksToMove.length; i++) { var moveData = blocksToMove[i]; var block = moveData.block; // Clear old position grid[moveData.fromX][moveData.fromY] = null; // Set new position grid[moveData.toX][moveData.toY] = block; block.setGridPosition(moveData.toX, moveData.toY); } } game.update = function () { // Apply gravity every few ticks to make blocks fall if (LK.ticks % 10 === 0) { applyGravity(); } // Update flying status display if (isFlying) { flyingStatusText.setText('FLYING MODE'); flyingStatusText.visible = true; // Track flying time for achievements if (flyingStartTime > 0) { var currentFlyingTime = (LK.ticks - flyingStartTime) * (1000 / 60); if (currentFlyingTime >= 1000) { // Check every second totalFlyingTime += 1000; storage.totalFlyingTime = totalFlyingTime; flyingStartTime = LK.ticks; checkAllAchievements(); } } } else { flyingStatusText.visible = false; } // Check if inventory is at maximum for perfectionist achievement var isMaxInventory = true; for (var blockType in inventory) { if (inventory[blockType] < 50) { isMaxInventory = false; break; } } if (isMaxInventory) { if (maxInventoryStartTime === 0) { maxInventoryStartTime = LK.ticks; } else { var maxInventoryDuration = (LK.ticks - maxInventoryStartTime) * (1000 / 60); totalMaxInventoryTime += 1000 / 60; // Add one frame worth of time storage.totalMaxInventoryTime = totalMaxInventoryTime; } } else { maxInventoryStartTime = 0; } // Update villagers for (var i = 0; i < villagers.length; i++) { villagers[i].update(); } // Replenish inventory slowly over time if (LK.ticks % 600 === 0) { // Every 10 seconds for (var blockType in inventory) { if (inventory[blockType] < 50) { inventory[blockType]++; } } updateInventoryDisplay(); checkAllAchievements(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Achievement = Container.expand(function (achievementData) {
var self = Container.call(this);
// Background
var bgGraphics = self.attachAsset('menuBg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.3,
alpha: 0.9
});
// Title text
var titleText = new Text2('Achievement Unlocked!', {
size: 36,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 0;
titleText.y = -30;
self.addChild(titleText);
// Achievement name
var nameText = new Text2(achievementData.name, {
size: 28,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 0;
nameText.y = 0;
self.addChild(nameText);
// Achievement description
var descText = new Text2(achievementData.description, {
size: 22,
fill: 0xCCCCCC
});
descText.anchor.set(0.5, 0.5);
descText.x = 0;
descText.y = 25;
self.addChild(descText);
self.show = function () {
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
self.visible = true;
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
// Auto hide after 3 seconds
LK.setTimeout(function () {
tween(self, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
self.visible = false;
}
});
}, 3000);
};
// Initially hidden
self.visible = false;
return self;
});
var AchievementMenu = Container.expand(function () {
var self = Container.call(this);
self.isVisible = false;
// Background
var bgGraphics = self.attachAsset('menuBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 1.2,
scaleY: 3.0
});
// Title text
var titleText = new Text2('Achievements', {
size: 48,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 0;
titleText.y = -300;
self.addChild(titleText);
// Close button
var closeButton = self.attachAsset('closeButton', {
anchorX: 0.5,
anchorY: 0.5
});
closeButton.x = 350;
closeButton.y = -300;
// Achievement list container
var achievementList = new Container();
self.addChild(achievementList);
// Page navigation buttons
var prevPageButton = self.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 0.8,
scaleY: 0.5
});
prevPageButton.x = -200;
prevPageButton.y = 300;
var prevPageText = new Text2('PREV', {
size: 28,
fill: 0xFFFFFF
});
prevPageText.anchor.set(0.5, 0.5);
prevPageText.x = -200;
prevPageText.y = 300;
self.addChild(prevPageText);
var nextPageButton = self.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 0.8,
scaleY: 0.5
});
nextPageButton.x = 200;
nextPageButton.y = 300;
var nextPageText = new Text2('NEXT', {
size: 28,
fill: 0xFFFFFF
});
nextPageText.anchor.set(0.5, 0.5);
nextPageText.x = 200;
nextPageText.y = 300;
self.addChild(nextPageText);
// Page indicator text
var pageIndicatorText = new Text2('', {
size: 24,
fill: 0xCCCCCC
});
pageIndicatorText.anchor.set(0.5, 0.5);
pageIndicatorText.x = 0;
pageIndicatorText.y = 300;
self.addChild(pageIndicatorText);
self.updateAchievements = function () {
// Clear existing achievement displays
while (achievementList.children.length > 0) {
achievementList.removeChild(achievementList.children[0]);
}
// Get all achievement IDs as array for pagination
var allAchievementIds = [];
for (var achievementId in achievementDefinitions) {
allAchievementIds.push(achievementId);
}
// Calculate pagination
var totalPages = Math.ceil(allAchievementIds.length / achievementsPerPage);
var startIndex = currentAchievementPage * achievementsPerPage;
var endIndex = Math.min(startIndex + achievementsPerPage, allAchievementIds.length);
// Update page indicator
pageIndicatorText.setText('Page ' + (currentAchievementPage + 1) + ' of ' + totalPages);
// Update button visibility
prevPageButton.alpha = currentAchievementPage > 0 ? 0.8 : 0.3;
prevPageText.fill = currentAchievementPage > 0 ? 0xFFFFFF : 0x666666;
nextPageButton.alpha = currentAchievementPage < totalPages - 1 ? 0.8 : 0.3;
nextPageText.fill = currentAchievementPage < totalPages - 1 ? 0xFFFFFF : 0x666666;
var yOffset = -200;
var achievementIndex = 0;
// Display achievements for current page
for (var i = startIndex; i < endIndex; i++) {
var achievementId = allAchievementIds[i];
var achievement = achievementDefinitions[achievementId];
var isUnlocked = achievements[achievementId] || false;
// Achievement container
var achievementContainer = new Container();
achievementContainer.y = yOffset + achievementIndex * 80;
// Achievement background
var achievementBg = achievementContainer.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: isUnlocked ? 0.8 : 0.3,
scaleX: 2.8,
scaleY: 0.6
});
// Achievement name
var nameText = new Text2(achievement.name, {
size: 32,
fill: isUnlocked ? 0xFFD700 : 0x888888
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 0;
nameText.y = -15;
achievementContainer.addChild(nameText);
// Achievement description
var descText = new Text2(achievement.description, {
size: 24,
fill: isUnlocked ? 0xFFFFFF : 0x666666
});
descText.anchor.set(0.5, 0.5);
descText.x = 0;
descText.y = 10;
achievementContainer.addChild(descText);
// Status text
var statusText = new Text2(isUnlocked ? 'UNLOCKED' : 'LOCKED', {
size: 20,
fill: isUnlocked ? 0x00FF00 : 0xFF4444
});
statusText.anchor.set(1, 0.5);
statusText.x = 150;
statusText.y = 0;
achievementContainer.addChild(statusText);
achievementList.addChild(achievementContainer);
achievementIndex++;
}
};
// Page navigation interactions
prevPageButton.down = function (x, y, obj) {
if (currentAchievementPage > 0) {
prevPageButton.alpha = 1.0;
currentAchievementPage--;
self.updateAchievements();
}
};
prevPageButton.up = function (x, y, obj) {
prevPageButton.alpha = currentAchievementPage > 0 ? 0.8 : 0.3;
};
nextPageButton.down = function (x, y, obj) {
var totalPages = Math.ceil(Object.keys(achievementDefinitions).length / achievementsPerPage);
if (currentAchievementPage < totalPages - 1) {
nextPageButton.alpha = 1.0;
currentAchievementPage++;
self.updateAchievements();
}
};
nextPageButton.up = function (x, y, obj) {
var totalPages = Math.ceil(Object.keys(achievementDefinitions).length / achievementsPerPage);
nextPageButton.alpha = currentAchievementPage < totalPages - 1 ? 0.8 : 0.3;
};
// Close button interaction
closeButton.down = function (x, y, obj) {
closeButton.alpha = 0.7;
self.hide();
};
closeButton.up = function (x, y, obj) {
closeButton.alpha = 1.0;
};
self.show = function () {
self.isVisible = true;
currentAchievementPage = 0; // Reset to first page when opening
self.updateAchievements();
self.alpha = 0;
self.visible = true;
tween(self, {
alpha: 1
}, {
duration: 200
});
};
self.hide = function () {
self.isVisible = false;
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Initially hidden
self.visible = false;
return self;
});
var Block = Container.expand(function (blockType) {
var self = Container.call(this);
self.blockType = blockType;
self.gridX = 0;
self.gridY = 0;
var blockGraphics = self.attachAsset(blockType, {
anchorX: 0.5,
anchorY: 0.5
});
// Add a subtle border effect
blockGraphics.alpha = 0.9;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
return self;
});
var BlockSelectionMenu = Container.expand(function () {
var self = Container.call(this);
self.isVisible = false;
// Background
var bgGraphics = self.attachAsset('menuBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleY: 4.0
});
// Title text
var titleText = new Text2('Select Block Type', {
size: 48,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 0;
titleText.y = -400;
self.addChild(titleText);
// Close button
var closeButton = self.attachAsset('closeButton', {
anchorX: 0.5,
anchorY: 0.5
});
closeButton.x = 280;
closeButton.y = -400;
// Block selection buttons
self.menuButtons = [];
var allBlockTypes = Object.keys(inventory);
var buttonsPerRow = 3;
var buttonSpacing = 160;
for (var i = 0; i < allBlockTypes.length; i++) {
var button = new Container();
// Button background
var buttonBg = button.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Block preview
var blockPreview = button.attachAsset(allBlockTypes[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9
});
// Block name text
var nameText = new Text2(allBlockTypes[i], {
size: 26,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0);
nameText.x = 0;
nameText.y = 45;
button.addChild(nameText);
// Inventory count text
var countText = new Text2(inventory[allBlockTypes[i]].toString(), {
size: 24,
fill: 0xFFDD44
});
countText.anchor.set(0.5, 0);
countText.x = 0;
countText.y = 65;
button.addChild(countText);
// Position button
var col = i % buttonsPerRow;
var row = Math.floor(i / buttonsPerRow);
button.x = (col - 1) * buttonSpacing;
button.y = row * buttonSpacing - 30;
// Store button data
button.blockType = allBlockTypes[i];
button.bgGraphics = buttonBg;
button.countText = countText;
// Button interaction
button.down = function (x, y, obj) {
this.bgGraphics.alpha = 1.0;
selectedBlockType = this.blockType;
updateToolbarSelection();
self.hide();
};
button.up = function (x, y, obj) {
this.bgGraphics.alpha = 0.8;
};
self.menuButtons.push(button);
self.addChild(button);
}
// Close button interaction
closeButton.down = function (x, y, obj) {
closeButton.alpha = 0.7;
self.hide();
};
closeButton.up = function (x, y, obj) {
closeButton.alpha = 1.0;
};
self.show = function () {
self.isVisible = true;
self.alpha = 0;
self.visible = true;
tween(self, {
alpha: 1
}, {
duration: 200
});
};
self.hide = function () {
self.isVisible = false;
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.visible = false;
}
});
};
self.updateInventoryCounts = function () {
for (var i = 0; i < self.menuButtons.length; i++) {
var button = self.menuButtons[i];
if (button.countText && button.blockType) {
button.countText.setText(inventory[button.blockType].toString());
}
}
};
// Initially hidden
self.visible = false;
return self;
});
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.targetX = 0;
self.targetY = 0;
self.isMoving = false;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.targetX = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.targetY = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
if (!self.isMoving) {
self.x = self.targetX;
self.y = self.targetY;
}
};
self.moveToGrid = function (gridX, gridY) {
if (self.isMoving) return false;
if (!isValidGridPosition(gridX, gridY)) return false;
// Check if there's a block at the target position (only block movement if not flying)
// Allow passing through water and lava blocks
if (!isFlying && grid[gridX] && grid[gridX][gridY] !== null) {
var targetBlock = grid[gridX][gridY];
if (targetBlock.blockType !== 'water' && targetBlock.blockType !== 'lava') {
return false;
}
}
self.isMoving = true;
self.gridX = gridX;
self.gridY = gridY;
self.targetX = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.targetY = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
// Reset gravity when manually moving (especially important in flying mode)
if (isFlying) {
characterGravity = 0;
}
// Adjust movement speed based on flying state
var moveDuration = isFlying ? 100 : 200;
tween(self, {
x: self.targetX,
y: self.targetY
}, {
duration: moveDuration,
easing: isFlying ? tween.easeOut : tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
// Track movement for achievements
moveCount++;
storage.moveCount = moveCount;
// Track jumping (moving up) for boooing achievement
if (gridY < self.gridY) {
jumpCount++;
storage.jumpCount = jumpCount;
}
// Track distance traveled
var distance = Math.abs(gridX - self.gridX) + Math.abs(gridY - self.gridY);
totalDistanceTraveled += distance;
storage.totalDistanceTraveled = totalDistanceTraveled;
// Track corner visits
if ((gridX === 0 || gridX === GRID_WIDTH - 1) && (gridY === 0 || gridY === GRID_HEIGHT - 1)) {
var corner = gridX + '_' + gridY;
visitedCorners[corner] = true;
storage.visitedCorners = visitedCorners;
}
// Track top/bottom row visits
if (gridY === 0) {
topRowVisits++;
storage.topRowVisits = topRowVisits;
}
if (gridY === GRID_HEIGHT - 1) {
bottomRowVisits++;
storage.bottomRowVisits = bottomRowVisits;
}
checkAllAchievements();
}
});
return true;
};
self.update = function () {
// Reset gravity when flying - do this first
if (isFlying) {
characterGravity = 0;
} else {
// Apply gravity if character is not moving, not supported, and not flying
if (!self.isMoving) {
var hasSupport = false;
// Check if character has support below (block or ground)
var belowY = self.gridY + 1;
if (belowY >= GRID_HEIGHT) {
// At bottom of world, has support
hasSupport = true;
} else if (grid[self.gridX] && grid[self.gridX][belowY] !== null) {
// Block below, has support
hasSupport = true;
}
if (!hasSupport) {
// Character should fall
characterGravity += characterGravityAcceleration;
if (characterGravity > characterMaxFallSpeed) {
characterGravity = characterMaxFallSpeed;
}
// Find where character should fall to
var fallToY = self.gridY;
for (var checkY = self.gridY + 1; checkY < GRID_HEIGHT; checkY++) {
if (grid[self.gridX][checkY] !== null) {
break;
}
fallToY = checkY;
}
// Move character down if there's a place to fall
if (fallToY > self.gridY) {
var targetY = Math.min(fallToY, self.gridY + Math.floor(characterGravity));
if (targetY !== self.gridY) {
self.moveToGrid(self.gridX, targetY);
}
}
} else {
// Character has support, reset gravity
characterGravity = 0;
}
}
}
// Check if character is touching a portal
if (!self.isMoving) {
for (var i = 0; i < placedBlocks.length; i++) {
var block = placedBlocks[i];
if (block.blockType === 'portal' && block.gridX === self.gridX && block.gridY === self.gridY) {
block.teleportCharacter();
break;
}
}
}
// Check if character is passing through lava and add effect
if (grid[self.gridX] && grid[self.gridX][self.gridY] !== null) {
var currentBlock = grid[self.gridX][self.gridY];
if (currentBlock.blockType === 'lava') {
// Flash character red with tween effect
tween(characterGraphics, {
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut
});
tween(characterGraphics, {
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeOut
});
// Add screen flash effect
LK.effects.flashScreen(0xff4500, 300);
// Track lava touches for survivalist achievement
lavaTouches++;
storage.lavaTouches = lavaTouches;
checkAllAchievements();
}
}
// Track time spent at world center for centerist achievement
var centerX = Math.floor(GRID_WIDTH / 2);
var centerY = Math.floor(GRID_HEIGHT / 2);
if (Math.abs(self.gridX - centerX) <= 2 && Math.abs(self.gridY - centerY) <= 2) {
centerTime++;
}
// Character is always on top
if (self.parent) {
var parent = self.parent;
parent.removeChild(self);
parent.addChild(self);
}
};
return self;
});
var ControlButton = Container.expand(function (direction) {
var self = Container.call(this);
self.direction = direction;
var bgGraphics = self.attachAsset('controlBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
var arrowGraphics = self.attachAsset('arrow' + direction.charAt(0).toUpperCase() + direction.slice(1), {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
self.down = function (x, y, obj) {
bgGraphics.alpha = 1.0;
var newX = character.gridX;
var newY = character.gridY;
if (direction === 'up') newY--;else if (direction === 'down') newY++;else if (direction === 'left') newX--;else if (direction === 'right') newX++;
character.moveToGrid(newX, newY);
};
self.up = function (x, y, obj) {
bgGraphics.alpha = 0.7;
};
return self;
});
var GridCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.1
});
self.gridX = 0;
self.gridY = 0;
self.isEmpty = true;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.highlight = function () {
cellGraphics.alpha = 0.3;
};
self.unhighlight = function () {
cellGraphics.alpha = 0.1;
};
return self;
});
var Lava = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'lava';
self.gridX = 0;
self.gridY = 0;
self.isFlowing = false;
self.flowCooldown = 0;
self.damageTimer = 0;
var lavaGraphics = self.attachAsset('lava', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glowing effect
lavaGraphics.alpha = 0.9;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.update = function () {
// Pulsing glow effect
lavaGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.15) * 0.15;
lavaGraphics.tint = 0xff4500 + Math.floor(Math.sin(LK.ticks * 0.1) * 20) * 0x010000;
// Handle damage to character
if (character && character.gridX === self.gridX && character.gridY === self.gridY && !character.isMoving) {
self.damageTimer++;
if (self.damageTimer >= 60) {
// 1 second of contact
// Flash character red and teleport away from lava
LK.effects.flashObject(character, 0xff0000, 500);
LK.effects.flashScreen(0xff0000, 300);
// Move character to nearest safe position
self.teleportCharacterToSafety();
self.damageTimer = 0;
}
} else {
self.damageTimer = 0;
}
// Lava flowing mechanics disabled - no spreading
// if (self.flowCooldown > 0) {
// self.flowCooldown--;
// } else {
// self.tryToFlow();
// self.flowCooldown = 120; // Flow every 2 seconds
// }
};
self.teleportCharacterToSafety = function () {
// Find nearest safe position
var safePositions = [];
for (var radius = 1; radius <= 5; radius++) {
for (var dx = -radius; dx <= radius; dx++) {
for (var dy = -radius; dy <= radius; dy++) {
var checkX = self.gridX + dx;
var checkY = self.gridY + dy;
if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] === null) {
// Check if position has ground support below
var hasSupport = false;
if (checkY + 1 >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[checkX][checkY + 1] !== null && grid[checkX][checkY + 1].blockType !== 'lava') {
hasSupport = true;
}
if (hasSupport) {
safePositions.push({
x: checkX,
y: checkY
});
}
}
}
}
if (safePositions.length > 0) break;
}
if (safePositions.length > 0) {
var safePos = safePositions[0]; // Take closest safe position
character.setGridPosition(safePos.x, safePos.y);
}
};
self.tryToFlow = function () {
// Lava flows down first, then horizontally
var flowDirections = [{
x: 0,
y: 1
},
// Down
{
x: -1,
y: 0
},
// Left
{
x: 1,
y: 0
},
// Right
{
x: -1,
y: 1
},
// Down-left
{
x: 1,
y: 1
} // Down-right
];
for (var i = 0; i < flowDirections.length; i++) {
var dir = flowDirections[i];
var targetX = self.gridX + dir.x;
var targetY = self.gridY + dir.y;
if (isValidGridPosition(targetX, targetY) && grid[targetX][targetY] === null) {
// Only flow if there are less than 3 lava blocks already
var nearbyLavaCount = 0;
for (var checkX = targetX - 1; checkX <= targetX + 1; checkX++) {
for (var checkY = targetY - 1; checkY <= targetY + 1; checkY++) {
if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] !== null && grid[checkX][checkY].blockType === 'lava') {
nearbyLavaCount++;
}
}
}
if (nearbyLavaCount < 3) {
// Create new lava block
var newLava = new Lava();
newLava.setGridPosition(targetX, targetY);
game.addChild(newLava);
grid[targetX][targetY] = newLava;
placedBlocks.push(newLava);
LK.effects.flashObject(newLava, 0xffff00, 300);
break;
}
}
}
};
return self;
});
var Portal = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'portal';
self.gridX = 0;
self.gridY = 0;
self.isActive = true;
self.cooldownTime = 0;
var portalGraphics = self.attachAsset('portal', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glowing effect
portalGraphics.alpha = 0.8;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.teleportCharacter = function () {
if (!self.isActive || self.cooldownTime > 0) return false;
if (!character || character.isMoving) return false;
// Find all other active portals
var otherPortals = [];
for (var i = 0; i < placedBlocks.length; i++) {
var block = placedBlocks[i];
if (block !== self && block.blockType === 'portal' && block.isActive && block.cooldownTime <= 0) {
otherPortals.push(block);
}
}
if (otherPortals.length === 0) {
// No other portals available - teleport to heaven!
// Create heaven effect with golden background and clouds
game.setBackgroundColor(0xFFD700); // Golden sky
// Flash screen with heavenly light
LK.effects.flashScreen(0xFFFFFF, 2000);
// Teleport character to top of world center
var heavenX = Math.floor(GRID_WIDTH / 2);
var heavenY = 0; // Top of the world
character.setGridPosition(heavenX, heavenY);
// Add visual effects
LK.effects.flashObject(self, 0xFFD700, 1000);
LK.effects.flashObject(character, 0xFFFFFF, 1000);
// Play portal sound
LK.getSound('portal').play();
// Set cooldown
self.cooldownTime = 300; // 5 seconds cooldown
// Track portal usage and heaven visits
portalUses++;
heavenVisits++;
storage.portalUses = portalUses;
storage.heavenVisits = heavenVisits;
checkAllAchievements();
// Reset background after effect
LK.setTimeout(function () {
game.setBackgroundColor(0x87CEEB); // Back to sky blue
}, 3000);
return true;
}
// Choose random portal to teleport to
var targetPortal = otherPortals[Math.floor(Math.random() * otherPortals.length)];
// Teleport character
character.setGridPosition(targetPortal.gridX, targetPortal.gridY);
// Add visual effects
LK.effects.flashObject(self, 0x9400d3, 500);
LK.effects.flashObject(targetPortal, 0x9400d3, 500);
LK.effects.flashObject(character, 0x00ffff, 300);
// Play portal sound
LK.getSound('portal').play();
// Set cooldown for both portals
self.cooldownTime = 180; // 3 seconds at 60fps
targetPortal.cooldownTime = 180;
// Track portal usage
portalUses++;
storage.portalUses = portalUses;
checkAllAchievements();
return true;
};
self.update = function () {
// Handle cooldown
if (self.cooldownTime > 0) {
self.cooldownTime--;
portalGraphics.alpha = 0.3 + self.cooldownTime / 180 * 0.5;
} else {
portalGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.1) * 0.2; // Pulsing effect
}
// Check if character is on portal
if (character && character.gridX === self.gridX && character.gridY === self.gridY && !character.isMoving) {
self.teleportCharacter();
}
};
return self;
});
var Sand = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'sand';
self.gridX = 0;
self.gridY = 0;
self.isFalling = false;
self.fallSpeed = 0;
self.maxFallSpeed = 8;
self.fallAcceleration = 0.8;
var sandGraphics = self.attachAsset('sand', {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.update = function () {
// Check if sand should fall
if (!self.isFalling) {
var hasSupport = false;
var belowY = self.gridY + 1;
// Check if at bottom or has block below
if (belowY >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[self.gridX] && grid[self.gridX][belowY] !== null) {
hasSupport = true;
}
// Start falling if no support
if (!hasSupport) {
self.isFalling = true;
self.fallSpeed = 0;
}
}
// Handle falling physics
if (self.isFalling) {
self.fallSpeed += self.fallAcceleration;
if (self.fallSpeed > self.maxFallSpeed) {
self.fallSpeed = self.maxFallSpeed;
}
// Calculate new grid position
var newGridY = self.gridY + Math.floor(self.fallSpeed / 4);
// Check if we can fall to new position
if (newGridY < GRID_HEIGHT && (grid[self.gridX][newGridY] === null || grid[self.gridX][newGridY] === self)) {
// Update grid
if (grid[self.gridX][self.gridY] === self) {
grid[self.gridX][self.gridY] = null;
}
self.gridY = newGridY;
grid[self.gridX][self.gridY] = self;
self.setGridPosition(self.gridX, self.gridY);
} else {
// Stop falling - hit something or reached bottom
self.isFalling = false;
self.fallSpeed = 0;
}
}
};
return self;
});
var ToolbarButton = Container.expand(function (blockType) {
var self = Container.call(this);
self.blockType = blockType;
self.isSelected = false;
var bgGraphics = self.attachAsset('toolbarBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var blockGraphics = self.attachAsset(blockType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var selectedBorder = self.attachAsset('selectedBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.setSelected = function (selected) {
self.isSelected = selected;
selectedBorder.alpha = selected ? 0.8 : 0;
bgGraphics.alpha = selected ? 1.0 : 0.8;
};
self.down = function (x, y, obj) {
selectedBlockType = self.blockType;
updateToolbarSelection();
};
return self;
});
var Villager = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.moveTimer = 0;
self.moveDirection = 1;
self.isAlive = true;
self.gravity = 0;
self.maxFallSpeed = 8;
self.gravityAcceleration = 0.5;
self.isFalling = false;
self.aiState = 'normal'; // 'normal', 'fleeing', 'attacking'
self.fleeTimer = 0; // How long to flee
self.attackTimer = 0; // Cooldown between attacks
self.lastPlayerAttackDistance = 999; // Distance to last player attack
var villagerGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
villagerGraphics.tint = 0x8B4513; // Brown color for villagers
villagerGraphics.scaleX = 0.8;
villagerGraphics.scaleY = 0.8;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
self.tryFleeFromPlayer = function () {
if (!character) return;
// Calculate direction away from player
var playerX = character.gridX;
var playerY = character.gridY;
var deltaX = self.gridX - playerX;
var deltaY = self.gridY - playerY;
// Determine flee direction (away from player)
var fleeX = self.gridX;
var fleeY = self.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Move horizontally away from player
fleeX += deltaX > 0 ? 1 : -1;
} else {
// Move vertically away from player
fleeY += deltaY > 0 ? 1 : -1;
}
// Try to move to flee position
if (isValidGridPosition(fleeX, fleeY) && grid[fleeX][fleeY] === null) {
// Check if villager has support at new position
var hasSupport = false;
if (fleeY + 1 >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[fleeX] && grid[fleeX][fleeY + 1] !== null) {
hasSupport = true;
}
if (hasSupport) {
self.setGridPosition(fleeX, fleeY);
// Add movement effect
LK.effects.flashObject(self, 0x00FFFF, 200);
}
}
};
self.tryAttackPlayer = function () {
if (!character) return;
// Move toward player
var playerX = character.gridX;
var playerY = character.gridY;
var deltaX = playerX - self.gridX;
var deltaY = playerY - self.gridY;
// Determine attack direction (toward player)
var attackX = self.gridX;
var attackY = self.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Move horizontally toward player
attackX += deltaX > 0 ? 1 : -1;
} else {
// Move vertically toward player
attackY += deltaY > 0 ? 1 : -1;
}
// Try to move toward player
if (isValidGridPosition(attackX, attackY) && grid[attackX][attackY] === null) {
// Check if villager has support at new position
var hasSupport = false;
if (attackY + 1 >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[attackX] && grid[attackX][attackY + 1] !== null) {
hasSupport = true;
}
if (hasSupport) {
self.setGridPosition(attackX, attackY);
// Add aggressive movement effect
LK.effects.flashObject(self, 0xFF4444, 200);
}
}
// If close to player, try to break blocks around them or attack
var distanceToPlayer = Math.abs(self.gridX - playerX) + Math.abs(self.gridY - playerY);
if (distanceToPlayer <= 2 && self.attackTimer <= 0) {
// Try to break blocks near player or attack player directly
var attackPositions = [{
x: playerX - 1,
y: playerY
}, {
x: playerX + 1,
y: playerY
}, {
x: playerX,
y: playerY - 1
}, {
x: playerX,
y: playerY + 1
}];
for (var i = 0; i < attackPositions.length; i++) {
var pos = attackPositions[i];
if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) {
var block = grid[pos.x][pos.y];
if (block.blockType !== 'portal' && block.blockType !== 'lava') {
// Break block near player
self.breakBlockAt(pos.x, pos.y);
self.attackTimer = 120; // 2 second cooldown
// Visual attack effect
LK.effects.flashObject(self, 0xFF0000, 400);
LK.effects.flashScreen(0xFF4444, 200);
break;
}
}
}
// If no blocks to break, directly attack player (flash effect)
if (self.attackTimer <= 0) {
LK.effects.flashObject(character, 0xFF0000, 300);
LK.effects.flashScreen(0xFF4444, 150);
self.attackTimer = 180; // 3 second cooldown
}
}
};
self.breakBlockAt = function (gridX, gridY) {
if (!isValidGridPosition(gridX, gridY) || grid[gridX][gridY] === null) return;
var block = grid[gridX][gridY];
var blockType = block.blockType;
// Create particle effects for breaking
for (var p = 0; p < 4; p++) {
var particle = new Container();
var particleGraphics = particle.attachAsset(blockType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particle.x = block.x;
particle.y = block.y;
game.addChild(particle);
// Random direction for each particle
var angle = p / 4 * Math.PI * 2;
var speed = 50 + Math.random() * 30;
var targetX = particle.x + Math.cos(angle) * speed;
var targetY = particle.y + Math.sin(angle) * speed;
// Animate particle flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI
}, {
duration: 200 + Math.random() * 100,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Remove from placedBlocks array
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === block) {
placedBlocks.splice(j, 1);
break;
}
}
// Destroy the block
block.destroy();
grid[gridX][gridY] = null;
// Play break sound
LK.getSound('break').play();
};
};
self.kill = function () {
if (!self.isAlive) return;
self.isAlive = false;
// Create death effects
LK.effects.flashObject(self, 0xff0000, 500);
// Create particle effects for death
for (var p = 0; p < 6; p++) {
var particle = new Container();
var particleGraphics = particle.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particleGraphics.tint = 0x8B4513;
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
// Random direction for each particle
var angle = p / 6 * Math.PI * 2;
var speed = 60 + Math.random() * 40;
var targetX = particle.x + Math.cos(angle) * speed;
var targetY = particle.y + Math.sin(angle) * speed;
// Animate particle flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Remove from villagers array
for (var i = villagers.length - 1; i >= 0; i--) {
if (villagers[i] === self) {
villagers.splice(i, 1);
break;
}
}
// Destroy villager after effects
LK.setTimeout(function () {
self.destroy();
}, 500);
};
self.update = function () {
if (!self.isAlive) return;
// Apply gravity to villager
var hasSupport = false;
if (self.gridY + 1 >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[self.gridX] && grid[self.gridX][self.gridY + 1] !== null) {
hasSupport = true;
}
if (!hasSupport) {
// Villager should fall
self.gravity += self.gravityAcceleration;
if (self.gravity > self.maxFallSpeed) {
self.gravity = self.maxFallSpeed;
}
// Find where villager should fall to
var fallToY = self.gridY;
for (var checkY = self.gridY + 1; checkY < GRID_HEIGHT; checkY++) {
if (grid[self.gridX][checkY] !== null) {
break;
}
fallToY = checkY;
}
// Move villager down if there's a place to fall
if (fallToY > self.gridY) {
var targetY = Math.min(fallToY, self.gridY + Math.floor(self.gravity));
if (targetY !== self.gridY) {
self.setGridPosition(self.gridX, targetY);
}
}
} else {
// Villager has support, reset gravity
self.gravity = 0;
}
// Check for player attacks and respond accordingly
if (playerAttackPosition && playerAttackTime > 0) {
// Calculate distance to player attack
var attackDistance = Math.abs(self.gridX - playerAttackPosition.x) + Math.abs(self.gridY - playerAttackPosition.y);
// If attack was recent (within 5 seconds) and close (within 5 blocks)
var timeSinceAttack = LK.ticks - playerAttackTime;
if (timeSinceAttack < 300 && attackDistance <= 5) {
// Decide whether to flee or attack based on villager personality and distance
var shouldFlee = Math.random() < 0.7 || attackDistance <= 2; // 70% flee, or always flee if very close
if (shouldFlee && self.aiState !== 'fleeing') {
self.aiState = 'fleeing';
self.fleeTimer = 300 + Math.random() * 180; // Flee for 5-8 seconds
// Visual indication of fear
LK.effects.flashObject(self, 0xFFFF00, 300);
} else if (!shouldFlee && self.aiState !== 'attacking' && attackDistance >= 2) {
self.aiState = 'attacking';
self.attackTimer = 0;
// Visual indication of anger
LK.effects.flashObject(self, 0xFF0000, 500);
}
}
}
// Update AI state timers
if (self.fleeTimer > 0) {
self.fleeTimer--;
if (self.fleeTimer <= 0) {
self.aiState = 'normal';
}
}
if (self.attackTimer > 0) {
self.attackTimer--;
}
self.moveTimer++;
// AI behavior based on current state
if (self.aiState === 'fleeing') {
// Flee behavior - move away from player/attack position every 1 second
if (self.moveTimer >= 60) {
self.moveTimer = 0;
self.tryFleeFromPlayer();
}
} else if (self.aiState === 'attacking') {
// Attack behavior - move toward player and try to break blocks near them every 2 seconds
if (self.moveTimer >= 120) {
self.moveTimer = 0;
self.tryAttackPlayer();
}
} else {
// Normal AI behavior every 3 seconds
if (self.moveTimer >= 180) {
self.moveTimer = 0;
// 30% chance to try building/breaking, 70% chance to move
if (Math.random() < 0.3) {
// Building/breaking behavior
var actionType = Math.random();
if (actionType < 0.4) {
// Try to place a block (40% chance)
self.tryPlaceBlock();
} else if (actionType < 0.7) {
// Try to break a block (30% chance)
self.tryBreakBlock();
} else {
// Try to move (30% chance)
self.tryMove();
}
} else {
// Regular movement behavior
self.tryMove();
}
}
}
self.tryMove = function () {
// Try to move in current direction
var newX = self.gridX + self.moveDirection;
// Check bounds and obstacles
if (newX < 0 || newX >= GRID_WIDTH || grid[newX] && grid[newX][self.gridY] !== null) {
self.moveDirection *= -1; // Reverse direction
} else {
// Move to new position if valid
var hasSupport = false;
if (self.gridY + 1 >= GRID_HEIGHT) {
hasSupport = true;
} else if (grid[newX] && grid[newX][self.gridY + 1] !== null) {
hasSupport = true;
}
if (hasSupport) {
self.setGridPosition(newX, self.gridY);
}
}
// Add jumping behavior - 20% chance to jump when on solid ground
if (Math.random() < 0.2 && self.gravity === 0 && !self.isFalling) {
// Check if villager can jump (has support below and space above)
var hasGroundSupport = false;
if (self.gridY + 1 >= GRID_HEIGHT) {
hasGroundSupport = true;
} else if (grid[self.gridX] && grid[self.gridX][self.gridY + 1] !== null) {
hasGroundSupport = true;
}
// Check if there's space above to jump
var canJump = true;
if (self.gridY - 1 < 0) {
canJump = false;
} else if (grid[self.gridX] && grid[self.gridX][self.gridY - 1] !== null) {
canJump = false;
}
if (hasGroundSupport && canJump) {
// Make villager jump by moving up 1 block
self.setGridPosition(self.gridX, self.gridY - 1);
// Add visual effect for jump
LK.effects.flashObject(self, 0x00FF00, 200);
}
}
};
self.tryPlaceBlock = function () {
// Get available block types that villagers can place
var villagerBlockTypes = ['dirt', 'stone', 'wood', 'grass'];
var blockType = villagerBlockTypes[Math.floor(Math.random() * villagerBlockTypes.length)];
// Try to place block in adjacent positions
var placePositions = [{
x: self.gridX - 1,
y: self.gridY
},
// Left
{
x: self.gridX + 1,
y: self.gridY
},
// Right
{
x: self.gridX,
y: self.gridY - 1
},
// Up
{
x: self.gridX,
y: self.gridY + 1
},
// Down
{
x: self.gridX - 1,
y: self.gridY + 1
},
// Down-left
{
x: self.gridX + 1,
y: self.gridY + 1
} // Down-right
];
for (var i = 0; i < placePositions.length; i++) {
var pos = placePositions[i];
if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] === null) {
// Don't place block on character position
if (character && pos.x === character.gridX && pos.y === character.gridY) continue;
// Create and place block
var block = new Block(blockType);
block.setGridPosition(pos.x, pos.y);
game.addChild(block);
grid[pos.x][pos.y] = block;
placedBlocks.push(block);
// Visual effect for villager placing block
LK.effects.flashObject(self, 0x00FF00, 300);
LK.getSound('place').play();
break;
}
}
};
self.tryBreakBlock = function () {
// Try to break blocks in adjacent positions
var breakPositions = [{
x: self.gridX - 1,
y: self.gridY
},
// Left
{
x: self.gridX + 1,
y: self.gridY
},
// Right
{
x: self.gridX,
y: self.gridY - 1
},
// Up
{
x: self.gridX,
y: self.gridY + 1
},
// Down
{
x: self.gridX - 1,
y: self.gridY - 1
},
// Up-left
{
x: self.gridX + 1,
y: self.gridY - 1
} // Up-right
];
for (var i = 0; i < breakPositions.length; i++) {
var pos = breakPositions[i];
if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) {
var block = grid[pos.x][pos.y];
var blockType = block.blockType;
// Don't break certain special blocks
if (blockType === 'portal' || blockType === 'lava' || blockType === 'villagerEgg') continue;
// Create particle effects for breaking
for (var p = 0; p < 4; p++) {
var particle = new Container();
var particleGraphics = particle.attachAsset(blockType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particle.x = block.x;
particle.y = block.y;
game.addChild(particle);
// Random direction for each particle
var angle = p / 4 * Math.PI * 2;
var speed = 50 + Math.random() * 30;
var targetX = particle.x + Math.cos(angle) * speed;
var targetY = particle.y + Math.sin(angle) * speed;
// Animate particle flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI
}, {
duration: 200 + Math.random() * 100,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Remove from placedBlocks array
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === block) {
placedBlocks.splice(j, 1);
break;
}
}
// Destroy the block
block.destroy();
grid[pos.x][pos.y] = null;
// Visual effect for villager breaking block
LK.effects.flashObject(self, 0xFF0000, 300);
LK.getSound('break').play();
break;
}
}
};
// Keep villager on top of other objects
if (self.parent) {
var parent = self.parent;
parent.removeChild(self);
parent.addChild(self);
}
};
return self;
});
var VillagerEgg = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'villagerEgg';
self.gridX = 0;
self.gridY = 0;
self.hatchTimer = 0;
self.hatchTime = 1800; // 30 seconds at 60fps
var eggGraphics = self.attachAsset('villagerEgg', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glowing effect
eggGraphics.alpha = 0.9;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.update = function () {
// Gentle pulsing effect
eggGraphics.alpha = 0.8 + Math.sin(LK.ticks * 0.1) * 0.1;
eggGraphics.scaleX = 1.0 + Math.sin(LK.ticks * 0.08) * 0.05;
eggGraphics.scaleY = 1.0 + Math.sin(LK.ticks * 0.08) * 0.05;
// Increment hatch timer
self.hatchTimer++;
// Change color as it gets closer to hatching
var hatchProgress = self.hatchTimer / self.hatchTime;
if (hatchProgress > 0.7) {
eggGraphics.tint = 0xFFE4B5; // Warmer color when close to hatching
} else if (hatchProgress > 0.5) {
eggGraphics.tint = 0xF5DEB3; // Slightly warmer
}
// Hatch when timer reaches hatch time
if (self.hatchTimer >= self.hatchTime) {
self.hatchVillager();
}
};
self.hatchVillager = function () {
// Create hatching effects
LK.effects.flashObject(self, 0xFFFFFF, 500);
LK.effects.flashScreen(0xFFD700, 200);
// Create a new villager at the egg position
var newVillager = new Villager();
newVillager.setGridPosition(self.gridX, self.gridY);
game.addChild(newVillager);
villagers.push(newVillager);
// Remove egg from grid and placedBlocks
grid[self.gridX][self.gridY] = null;
for (var i = placedBlocks.length - 1; i >= 0; i--) {
if (placedBlocks[i] === self) {
placedBlocks.splice(i, 1);
break;
}
}
// Play hatching sound
LK.getSound('place').play();
// Create particle effects for hatching
for (var p = 0; p < 8; p++) {
var particle = new Container();
var particleGraphics = particle.attachAsset('villagerEgg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
// Random direction for each particle
var angle = p / 8 * Math.PI * 2;
var speed = 60 + Math.random() * 40;
var targetX = particle.x + Math.cos(angle) * speed;
var targetY = particle.y + Math.sin(angle) * speed;
// Animate particle flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Destroy the egg
self.destroy();
};
return self;
});
var Water = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'water';
self.gridX = 0;
self.gridY = 0;
self.isFlowing = false;
self.flowCooldown = 0;
var waterGraphics = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5
});
// Add water effects
waterGraphics.alpha = 0.7; // Semi-transparent water
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridX * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_X;
self.y = gridY * GRID_SIZE + GRID_SIZE / 2 + GRID_OFFSET_Y;
};
self.update = function () {
// Gentle wave effect
waterGraphics.alpha = 0.6 + Math.sin(LK.ticks * 0.08) * 0.1;
waterGraphics.tint = 0x4169e1 + Math.floor(Math.sin(LK.ticks * 0.05) * 10) * 0x000100;
// Check for lava collision in adjacent positions
var checkPositions = [{
x: self.gridX - 1,
y: self.gridY
},
// Left
{
x: self.gridX + 1,
y: self.gridY
},
// Right
{
x: self.gridX,
y: self.gridY - 1
},
// Up
{
x: self.gridX,
y: self.gridY + 1
},
// Down
{
x: self.gridX - 1,
y: self.gridY - 1
},
// Up-left
{
x: self.gridX + 1,
y: self.gridY - 1
},
// Up-right
{
x: self.gridX - 1,
y: self.gridY + 1
},
// Down-left
{
x: self.gridX + 1,
y: self.gridY + 1
} // Down-right
];
for (var i = 0; i < checkPositions.length; i++) {
var pos = checkPositions[i];
if (isValidGridPosition(pos.x, pos.y) && grid[pos.x][pos.y] !== null) {
var adjacentBlock = grid[pos.x][pos.y];
if (adjacentBlock.blockType === 'lava') {
// Water and lava reaction - create steam explosion effect
LK.effects.flashScreen(0xFFFFFF, 800);
LK.effects.flashObject(self, 0xFFFFFF, 600);
LK.effects.flashObject(adjacentBlock, 0xFFFFFF, 600);
// Create stone block at water position
var stoneBlock = new Block('stone');
stoneBlock.setGridPosition(self.gridX, self.gridY);
game.addChild(stoneBlock);
grid[self.gridX][self.gridY] = stoneBlock;
placedBlocks.push(stoneBlock);
// Remove water block
self.destroy();
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === self) {
placedBlocks.splice(j, 1);
break;
}
}
// Remove lava block
adjacentBlock.destroy();
grid[pos.x][pos.y] = null;
for (var k = placedBlocks.length - 1; k >= 0; k--) {
if (placedBlocks[k] === adjacentBlock) {
placedBlocks.splice(k, 1);
break;
}
}
// Create stone block at lava position too
var stoneBlock2 = new Block('stone');
stoneBlock2.setGridPosition(pos.x, pos.y);
game.addChild(stoneBlock2);
grid[pos.x][pos.y] = stoneBlock2;
placedBlocks.push(stoneBlock2);
// Play reaction sound
LK.getSound('break').play();
// Track water-lava reactions
waterLavaReactions++;
storage.waterLavaReactions = waterLavaReactions;
checkAllAchievements();
return; // Exit after reaction
}
}
}
// Water flowing mechanics disabled - no spreading
// if (self.flowCooldown > 0) {
// self.flowCooldown--;
// } else {
// self.tryToFlow();
// self.flowCooldown = 90; // Flow every 1.5 seconds
// }
};
self.tryToFlow = function () {
// Water flows down first, then horizontally
var flowDirections = [{
x: 0,
y: 1
},
// Down
{
x: -1,
y: 0
},
// Left
{
x: 1,
y: 0
},
// Right
{
x: -1,
y: 1
},
// Down-left
{
x: 1,
y: 1
} // Down-right
];
for (var i = 0; i < flowDirections.length; i++) {
var dir = flowDirections[i];
var targetX = self.gridX + dir.x;
var targetY = self.gridY + dir.y;
if (isValidGridPosition(targetX, targetY) && grid[targetX][targetY] === null) {
// Only flow if there are less than 4 water blocks nearby
var nearbyWaterCount = 0;
for (var checkX = targetX - 1; checkX <= targetX + 1; checkX++) {
for (var checkY = targetY - 1; checkY <= targetY + 1; checkY++) {
if (isValidGridPosition(checkX, checkY) && grid[checkX][checkY] !== null && grid[checkX][checkY].blockType === 'water') {
nearbyWaterCount++;
}
}
}
if (nearbyWaterCount < 4) {
// Create new water block
var newWater = new Water();
newWater.setGridPosition(targetX, targetY);
game.addChild(newWater);
grid[targetX][targetY] = newWater;
placedBlocks.push(newWater);
LK.effects.flashObject(newWater, 0x87ceeb, 300);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var GRID_SIZE = 80;
var GRID_WIDTH = 20;
var GRID_HEIGHT = 20;
var GRID_OFFSET_X = (2048 - GRID_WIDTH * GRID_SIZE) / 2;
var GRID_OFFSET_Y = 200;
var selectedBlockType = 'dirt';
var blockTypes = ['dirt', 'stone', 'wood', 'grass', 'grassDark', 'grassLight', 'sand', 'diamond', 'gold', 'portal', 'lava', 'water', 'villagerEgg', 'villager'];
var inventory = {
dirt: 50,
stone: 30,
wood: 25,
grass: 20,
grassDark: 15,
grassLight: 15,
sand: 25,
diamond: 5,
gold: 8,
portal: 3,
lava: 10,
water: 15,
villagerEgg: 3,
villager: 5
};
// Achievement system
var achievements = storage.achievements || {};
var currentAchievementPage = 0;
var achievementsPerPage = 8;
var achievementDefinitions = {
firstBlock: {
name: "First Builder",
description: "Place your first block",
unlocked: false
},
destroyer: {
name: "Destroyer",
description: "Break 10 blocks",
unlocked: false
},
architect: {
name: "Architect",
description: "Place 50 blocks",
unlocked: false
},
miner: {
name: "Miner",
description: "Break 50 blocks",
unlocked: false
},
collector: {
name: "Collector",
description: "Collect all block types",
unlocked: false
},
explorer: {
name: "Explorer",
description: "Move 100 times",
unlocked: false
},
masterBuilder: {
name: "Master Builder",
description: "Place 200 blocks",
unlocked: false
},
demolitionExpert: {
name: "Demolition Expert",
description: "Break 200 blocks",
unlocked: false
},
goldRush: {
name: "Gold Rush",
description: "Collect 20 gold blocks",
unlocked: false
},
diamondCollector: {
name: "Diamond Collector",
description: "Collect 15 diamond blocks",
unlocked: false
},
woodsman: {
name: "Woodsman",
description: "Collect 100 wood blocks",
unlocked: false
},
stoneAge: {
name: "Stone Age",
description: "Collect 150 stone blocks",
unlocked: false
},
adventurer: {
name: "Adventurer",
description: "Move 500 times",
unlocked: false
},
nomad: {
name: "Nomad",
description: "Move 1000 times",
unlocked: false
},
flightTime: {
name: "Taking Flight",
description: "Use flying mode 10 times",
unlocked: false
},
skyExplorer: {
name: "Sky Explorer",
description: "Spend 60 seconds in flying mode",
unlocked: false
},
areCrazy: {
name: "are you crazy?",
description: "Break each block type 10 times",
unlocked: false
},
boooing: {
name: "boooing",
description: "Jump 100 times",
unlocked: false
},
lavaLord: {
name: "Lava Lord",
description: "Place 25 lava blocks",
unlocked: false
},
waterMaster: {
name: "Water Master",
description: "Place 30 water blocks",
unlocked: false
},
speedBuilder: {
name: "Speed Builder",
description: "Place 20 blocks in 30 seconds",
unlocked: false
},
speedDestroyer: {
name: "Speed Destroyer",
description: "Break 15 blocks in 20 seconds",
unlocked: false
},
portalMaster: {
name: "Portal Master",
description: "Use portals 25 times",
unlocked: false
},
heavenReacher: {
name: "Heaven Reacher",
description: "Reach heaven biome 5 times",
unlocked: false
},
sandCastleKing: {
name: "Sand Castle King",
description: "Place 40 sand blocks",
unlocked: false
},
grassGardener: {
name: "Grass Gardener",
description: "Place 100 grass blocks total",
unlocked: false
},
undergroundExplorer: {
name: "Underground Explorer",
description: "Place blocks below Y level 15",
unlocked: false
},
skyBuilder: {
name: "Sky Builder",
description: "Place blocks above Y level 5",
unlocked: false
},
elementalReactor: {
name: "Elemental Reactor",
description: "Trigger 10 water-lava reactions",
unlocked: false
},
survivalist: {
name: "Survivalist",
description: "Survive 5 lava touches",
unlocked: false
},
teleporter: {
name: "Teleporter",
description: "Travel 1000 grid units total",
unlocked: false
},
perfectionist: {
name: "Perfectionist",
description: "Maintain max inventory for 5 minutes",
unlocked: false
},
nightOwl: {
name: "Night Owl",
description: "Play for 10 minutes straight",
unlocked: false
},
marathonPlayer: {
name: "Marathon Player",
description: "Play for 30 minutes straight",
unlocked: false
},
blockChain: {
name: "Block Chain",
description: "Place 10 blocks in a row without breaking",
unlocked: false
},
demolitionChain: {
name: "Demolition Chain",
description: "Break 10 blocks in a row without placing",
unlocked: false
},
rainbowBuilder: {
name: "Rainbow Builder",
description: "Place all 12 block types in one session",
unlocked: false
},
cornerExplorer: {
name: "Corner Explorer",
description: "Visit all 4 corners of the world",
unlocked: false
},
centerist: {
name: "Centerist",
description: "Spend 2 minutes at world center",
unlocked: false
},
pacifist: {
name: "Pacifist",
description: "Place 100 blocks without breaking any",
unlocked: false
},
destroyer2: {
name: "World Destroyer",
description: "Break 100 blocks without placing any",
unlocked: false
},
inventoryManager: {
name: "Inventory Manager",
description: "Use every block type at least once",
unlocked: false
},
highFlyer: {
name: "High Flyer",
description: "Fly to the top row 10 times",
unlocked: false
},
deepDigger: {
name: "Deep Digger",
description: "Reach the bottom row 10 times",
unlocked: false
}
};
// Achievement counters
var blocksPlaced = storage.blocksPlaced || 0;
var blocksBroken = storage.blocksBroken || 0;
var moveCount = storage.moveCount || 0;
var goldCollected = storage.goldCollected || 0;
var diamondCollected = storage.diamondCollected || 0;
var woodCollected = storage.woodCollected || 0;
var stoneCollected = storage.stoneCollected || 0;
var flyingActivations = storage.flyingActivations || 0;
var flyingStartTime = 0;
var totalFlyingTime = storage.totalFlyingTime || 0;
var blockTypeBrokenCount = storage.blockTypeBrokenCount || {};
var jumpCount = storage.jumpCount || 0;
var lavaPlaced = storage.lavaPlaced || 0;
var waterPlaced = storage.waterPlaced || 0;
var portalUses = storage.portalUses || 0;
var heavenVisits = storage.heavenVisits || 0;
var sandPlaced = storage.sandPlaced || 0;
var grassPlaced = storage.grassPlaced || 0;
var waterLavaReactions = storage.waterLavaReactions || 0;
var lavaTouches = storage.lavaTouches || 0;
var totalDistanceTraveled = storage.totalDistanceTraveled || 0;
var gameStartTime = LK.ticks;
var maxInventoryStartTime = 0;
var totalMaxInventoryTime = storage.totalMaxInventoryTime || 0;
var consecutiveBlocksPlaced = 0;
var consecutiveBlocksBroken = 0;
var usedBlockTypesThisSession = {};
var visitedCorners = storage.visitedCorners || {};
var centerTime = 0;
var consecutivePlacingStreak = storage.consecutivePlacingStreak || 0;
var consecutiveBreakingStreak = storage.consecutiveBreakingStreak || 0;
var topRowVisits = storage.topRowVisits || 0;
var bottomRowVisits = storage.bottomRowVisits || 0;
var undergroundBuilding = storage.undergroundBuilding || 0;
var skyBuilding = storage.skyBuilding || 0;
var buildingSpeedStart = 0;
var buildingSpeedCount = 0;
var breakingSpeedStart = 0;
var breakingSpeedCount = 0;
var grid = [];
var placedBlocks = [];
var villagers = [];
var toolbarButtons = [];
var controlButtons = [];
var pressTimer = null;
var isLongPress = false;
var pressStartTime = 0;
var playerAttackPosition = null; // Track where player last attacked
var playerAttackTime = 0; // When the attack happened
var character = null;
var blockSelectionMenu = null;
var menuButton = null;
var achievementNotification = null;
var achievementMenu = null;
var characterGravity = 0; // Current falling speed
var characterMaxFallSpeed = 8; // Maximum falling speed
var characterGravityAcceleration = 0.5; // How fast gravity accelerates
var isFlying = false; // Flying mode toggle
var flyingSpeed = 4; // Speed when flying
var flyingVerticalSpeed = 3; // Vertical movement speed when flying
function checkAchievement(achievementId) {
if (achievements[achievementId]) return;
var achieved = false;
switch (achievementId) {
case 'firstBlock':
achieved = blocksPlaced >= 1;
break;
case 'destroyer':
achieved = blocksBroken >= 10;
break;
case 'architect':
achieved = blocksPlaced >= 50;
break;
case 'miner':
achieved = blocksBroken >= 50;
break;
case 'collector':
// Check if player has collected all block types
var hasAllTypes = true;
for (var blockType in inventory) {
if (inventory[blockType] <= 0) {
hasAllTypes = false;
break;
}
}
achieved = hasAllTypes;
break;
case 'explorer':
achieved = moveCount >= 100;
break;
case 'masterBuilder':
achieved = blocksPlaced >= 200;
break;
case 'demolitionExpert':
achieved = blocksBroken >= 200;
break;
case 'goldRush':
achieved = goldCollected >= 20;
break;
case 'diamondCollector':
achieved = diamondCollected >= 15;
break;
case 'woodsman':
achieved = woodCollected >= 100;
break;
case 'stoneAge':
achieved = stoneCollected >= 150;
break;
case 'adventurer':
achieved = moveCount >= 500;
break;
case 'nomad':
achieved = moveCount >= 1000;
break;
case 'flightTime':
achieved = flyingActivations >= 10;
break;
case 'skyExplorer':
achieved = totalFlyingTime >= 60000; // 60 seconds in milliseconds
break;
case 'areCrazy':
// Check if each block type has been broken at least 10 times
achieved = true;
for (var i = 0; i < blockTypes.length; i++) {
var blockType = blockTypes[i];
var brokenCount = blockTypeBrokenCount[blockType] || 0;
if (brokenCount < 10) {
achieved = false;
break;
}
}
break;
case 'boooing':
achieved = jumpCount >= 100;
break;
case 'lavaLord':
achieved = lavaPlaced >= 25;
break;
case 'waterMaster':
achieved = waterPlaced >= 30;
break;
case 'speedBuilder':
achieved = buildingSpeedCount >= 20;
break;
case 'speedDestroyer':
achieved = breakingSpeedCount >= 15;
break;
case 'portalMaster':
achieved = portalUses >= 25;
break;
case 'heavenReacher':
achieved = heavenVisits >= 5;
break;
case 'sandCastleKing':
achieved = sandPlaced >= 40;
break;
case 'grassGardener':
achieved = grassPlaced >= 100;
break;
case 'undergroundExplorer':
achieved = undergroundBuilding >= 1;
break;
case 'skyBuilder':
achieved = skyBuilding >= 1;
break;
case 'elementalReactor':
achieved = waterLavaReactions >= 10;
break;
case 'survivalist':
achieved = lavaTouches >= 5;
break;
case 'teleporter':
achieved = totalDistanceTraveled >= 1000;
break;
case 'perfectionist':
achieved = totalMaxInventoryTime >= 300000; // 5 minutes in milliseconds
break;
case 'nightOwl':
var currentTime = LK.ticks;
achieved = currentTime - gameStartTime >= 36000; // 10 minutes at 60fps
break;
case 'marathonPlayer':
var currentTime = LK.ticks;
achieved = currentTime - gameStartTime >= 108000; // 30 minutes at 60fps
break;
case 'blockChain':
achieved = consecutivePlacingStreak >= 10;
break;
case 'demolitionChain':
achieved = consecutiveBreakingStreak >= 10;
break;
case 'rainbowBuilder':
achieved = Object.keys(usedBlockTypesThisSession).length >= 12;
break;
case 'cornerExplorer':
achieved = Object.keys(visitedCorners).length >= 4;
break;
case 'centerist':
achieved = centerTime >= 7200; // 2 minutes at 60fps
break;
case 'pacifist':
achieved = consecutivePlacingStreak >= 100;
break;
case 'destroyer2':
achieved = consecutiveBreakingStreak >= 100;
break;
case 'inventoryManager':
var usedAllTypes = true;
for (var i = 0; i < blockTypes.length; i++) {
if (!usedBlockTypesThisSession[blockTypes[i]]) {
usedAllTypes = false;
break;
}
}
achieved = usedAllTypes;
break;
case 'highFlyer':
achieved = topRowVisits >= 10;
break;
case 'deepDigger':
achieved = bottomRowVisits >= 10;
break;
}
if (achieved) {
unlockAchievement(achievementId);
}
}
function unlockAchievement(achievementId) {
if (achievements[achievementId]) return;
achievements[achievementId] = true;
storage.achievements = achievements;
// Update the achievement menu to show the newly unlocked achievement
if (achievementMenu) {
achievementMenu.updateAchievements();
// Show the achievement menu temporarily if not already visible
if (!achievementMenu.isVisible) {
achievementMenu.show();
// Auto-hide after a few seconds
LK.setTimeout(function () {
if (achievementMenu && achievementMenu.isVisible) {
achievementMenu.hide();
}
}, 3000);
}
}
}
function checkAllAchievements() {
for (var achievementId in achievementDefinitions) {
checkAchievement(achievementId);
}
}
// Initialize grid
for (var x = 0; x < GRID_WIDTH; x++) {
grid[x] = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[x][y] = null;
var gridCell = new GridCell();
gridCell.setGridPosition(x, y);
game.addChild(gridCell);
}
}
// Initialize character at top of world
character = new Character();
var randomX = Math.floor(Math.random() * GRID_WIDTH);
var topY = 0; // Always start at the very top
character.setGridPosition(randomX, topY);
game.addChild(character);
// Create block selection menu
blockSelectionMenu = new BlockSelectionMenu();
blockSelectionMenu.x = 1024;
blockSelectionMenu.y = 800;
LK.gui.addChild(blockSelectionMenu);
// Create achievement menu at top layer
achievementMenu = new AchievementMenu();
achievementMenu.x = 1024;
achievementMenu.y = 400;
LK.gui.addChild(achievementMenu);
// Create menu button
menuButton = new Container();
var menuBg = menuButton.attachAsset('toolbarBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var menuText = new Text2('MENU', {
size: 36,
fill: 0xFFFFFF
});
menuText.anchor.set(0.5, 0.5);
menuText.x = 0;
menuText.y = 0;
menuButton.addChild(menuText);
menuButton.x = 2048 - 80;
menuButton.y = 150;
menuButton.down = function (x, y, obj) {
menuBg.alpha = 1.0;
if (!blockSelectionMenu.isVisible) {
blockSelectionMenu.show();
}
};
menuButton.up = function (x, y, obj) {
menuBg.alpha = 0.8;
};
game.addChild(menuButton);
// Create achievements button at the top
var achievementsButton = new Container();
var achievementsBg = achievementsButton.attachAsset('toolbarBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var achievementsText = new Text2('ACHIEVEMENTS', {
size: 28,
fill: 0xFFFFFF
});
achievementsText.anchor.set(0.5, 0.5);
achievementsText.x = 0;
achievementsText.y = 0;
achievementsButton.addChild(achievementsText);
achievementsButton.x = 1024;
achievementsButton.y = 50;
achievementsButton.down = function (x, y, obj) {
achievementsBg.alpha = 1.0;
if (!achievementMenu.isVisible) {
achievementMenu.show();
}
};
achievementsButton.up = function (x, y, obj) {
achievementsBg.alpha = 0.8;
};
game.addChild(achievementsButton);
// Create flying toggle button
var flyingButton = new Container();
var flyingBg = flyingButton.attachAsset('toolbarBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var flyingText = new Text2('FLY', {
size: 32,
fill: 0xFFFFFF
});
flyingText.anchor.set(0.5, 0.5);
flyingText.x = 0;
flyingText.y = 0;
flyingButton.addChild(flyingText);
flyingButton.x = 2048 - 80;
flyingButton.y = 350;
flyingButton.down = function (x, y, obj) {
flyingBg.alpha = 1.0;
// Toggle flying mode
var wasFlying = isFlying;
isFlying = !isFlying;
// Track flying time and activations
if (isFlying && !wasFlying) {
// Started flying
flyingActivations++;
storage.flyingActivations = flyingActivations;
flyingStartTime = LK.ticks;
checkAllAchievements();
} else if (!isFlying && wasFlying) {
// Stopped flying
if (flyingStartTime > 0) {
var flyingDuration = (LK.ticks - flyingStartTime) * (1000 / 60); // Convert ticks to milliseconds
totalFlyingTime += flyingDuration;
storage.totalFlyingTime = totalFlyingTime;
flyingStartTime = 0;
checkAllAchievements();
}
}
// Update button appearance - recreate the background with new color
flyingButton.removeChild(flyingBg);
flyingBg = flyingButton.attachAsset('toolbarBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0
});
if (isFlying) {
flyingBg.tint = 0x00FF00;
} else {
flyingBg.tint = 0xFFFFFF;
}
flyingText.setText(isFlying ? 'LAND' : 'FLY');
// Visual feedback
if (isFlying) {
LK.effects.flashObject(character, 0x00FFFF, 500);
}
};
flyingButton.up = function (x, y, obj) {
flyingBg.alpha = 0.8;
};
game.addChild(flyingButton);
// Create flying status display
var flyingStatusText = new Text2('', {
size: 28,
fill: 0x00FFFF
});
flyingStatusText.anchor.set(0.5, 0);
flyingStatusText.x = 1024;
flyingStatusText.y = 100;
LK.gui.addChild(flyingStatusText);
// Create toolbar
var toolbarY = 2732 - 120;
var toolbarStartX = (2048 - blockTypes.length * 120) / 2;
for (var i = 0; i < blockTypes.length; i++) {
var button = new ToolbarButton(blockTypes[i]);
button.x = toolbarStartX + i * 120 + 60;
button.y = toolbarY;
toolbarButtons.push(button);
LK.gui.addChild(button);
}
// Create inventory display
var inventoryTexts = {};
for (var i = 0; i < blockTypes.length; i++) {
var blockType = blockTypes[i];
var inventoryText = new Text2(inventory[blockType].toString(), {
size: 36,
fill: 0xFFFFFF
});
inventoryText.anchor.set(0.5, 0);
inventoryText.x = toolbarStartX + i * 120 + 60;
inventoryText.y = toolbarY + 60;
inventoryTexts[blockType] = inventoryText;
LK.gui.addChild(inventoryText);
}
// Create control buttons
var controlButtonSize = 100;
var controlPadding = 20;
var controlCenterX = 150;
var controlCenterY = GRID_OFFSET_Y + GRID_HEIGHT * GRID_SIZE + 150;
var directions = [{
dir: 'up',
x: 0,
y: -1
}, {
dir: 'down',
x: 0,
y: 1
}, {
dir: 'left',
x: -1,
y: 0
}, {
dir: 'right',
x: 1,
y: 0
}];
for (var i = 0; i < directions.length; i++) {
var dirInfo = directions[i];
var button = new ControlButton(dirInfo.dir);
button.x = controlCenterX + dirInfo.x * (controlButtonSize + controlPadding);
button.y = controlCenterY + dirInfo.y * (controlButtonSize + controlPadding);
controlButtons.push(button);
game.addChild(button);
}
function updateToolbarSelection() {
for (var i = 0; i < toolbarButtons.length; i++) {
toolbarButtons[i].setSelected(toolbarButtons[i].blockType === selectedBlockType);
}
}
function updateInventoryDisplay() {
for (var blockType in inventoryTexts) {
inventoryTexts[blockType].setText(inventory[blockType].toString());
}
// Update block selection menu inventory counts
if (blockSelectionMenu) {
blockSelectionMenu.updateInventoryCounts();
}
}
function getGridPosition(worldX, worldY) {
var gridX = Math.floor((worldX - GRID_OFFSET_X) / GRID_SIZE);
var gridY = Math.floor((worldY - GRID_OFFSET_Y) / GRID_SIZE);
return {
x: gridX,
y: gridY
};
}
function isValidGridPosition(gridX, gridY) {
return gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT;
}
function placeBlock(gridX, gridY) {
if (!isValidGridPosition(gridX, gridY)) return false;
if (grid[gridX][gridY] !== null) return false;
if (inventory[selectedBlockType] <= 0) return false;
// Don't place block on character position
if (character && gridX === character.gridX && gridY === character.gridY) return false;
var block;
if (selectedBlockType === 'sand') {
block = new Sand();
} else if (selectedBlockType === 'portal') {
block = new Portal();
} else if (selectedBlockType === 'lava') {
block = new Lava();
} else if (selectedBlockType === 'water') {
block = new Water();
} else if (selectedBlockType === 'villagerEgg') {
block = new VillagerEgg();
} else if (selectedBlockType === 'villager') {
// Create a villager directly instead of a block
var newVillager = new Villager();
newVillager.setGridPosition(gridX, gridY);
game.addChild(newVillager);
villagers.push(newVillager);
inventory[selectedBlockType]--;
updateInventoryDisplay();
LK.getSound('place').play();
// Track achievement progress
blocksPlaced++;
storage.blocksPlaced = blocksPlaced;
consecutivePlacingStreak++;
storage.consecutivePlacingStreak = consecutivePlacingStreak;
consecutiveBreakingStreak = 0; // Reset breaking streak
storage.consecutiveBreakingStreak = 0;
// Track used block types this session
usedBlockTypesThisSession[selectedBlockType] = true;
checkAllAchievements();
return true;
} else {
block = new Block(selectedBlockType);
}
block.setGridPosition(gridX, gridY);
game.addChild(block);
grid[gridX][gridY] = block;
placedBlocks.push(block);
inventory[selectedBlockType]--;
updateInventoryDisplay();
LK.getSound('place').play();
// Track achievement progress
blocksPlaced++;
storage.blocksPlaced = blocksPlaced;
consecutivePlacingStreak++;
storage.consecutivePlacingStreak = consecutivePlacingStreak;
consecutiveBreakingStreak = 0; // Reset breaking streak
storage.consecutiveBreakingStreak = 0;
// Track used block types this session
usedBlockTypesThisSession[selectedBlockType] = true;
// Track specific block type placements
if (selectedBlockType === 'lava') {
lavaPlaced++;
storage.lavaPlaced = lavaPlaced;
}
if (selectedBlockType === 'water') {
waterPlaced++;
storage.waterPlaced = waterPlaced;
}
if (selectedBlockType === 'sand') {
sandPlaced++;
storage.sandPlaced = sandPlaced;
}
if (selectedBlockType === 'grass' || selectedBlockType === 'grassDark' || selectedBlockType === 'grassLight') {
grassPlaced++;
storage.grassPlaced = grassPlaced;
}
// Track building location achievements
if (gridY >= 15) {
undergroundBuilding++;
storage.undergroundBuilding = undergroundBuilding;
}
if (gridY <= 5) {
skyBuilding++;
storage.skyBuilding = skyBuilding;
}
// Track speed building
if (buildingSpeedStart === 0) {
buildingSpeedStart = LK.ticks;
buildingSpeedCount = 1;
} else {
var timeDiff = LK.ticks - buildingSpeedStart;
if (timeDiff <= 1800) {
// 30 seconds at 60fps
buildingSpeedCount++;
} else {
buildingSpeedStart = LK.ticks;
buildingSpeedCount = 1;
}
}
checkAllAchievements();
return true;
}
function removeBlock(gridX, gridY) {
if (!isValidGridPosition(gridX, gridY)) return false;
if (grid[gridX][gridY] === null) return false;
var block = grid[gridX][gridY];
var blockType = block.blockType;
// If breaking a portal, teleport character to heaven biome
if (blockType === 'portal') {
// Transition to heaven biome permanently
game.setBackgroundColor(0xFFD700); // Golden heaven sky
// Flash screen with heavenly light
LK.effects.flashScreen(0xFFFFFF, 2000);
// Teleport character to top of world center
var heavenX = Math.floor(GRID_WIDTH / 2);
var heavenY = 0; // Top of the world
character.setGridPosition(heavenX, heavenY);
// Add visual effects
LK.effects.flashObject(block, 0xFFD700, 1000);
LK.effects.flashObject(character, 0xFFFFFF, 1000);
// Play portal sound
LK.getSound('portal').play();
// Clear all existing blocks to create clean heaven environment
for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT; y++) {
if (grid[x][y] !== null && grid[x][y] !== block) {
var existingBlock = grid[x][y];
existingBlock.destroy();
grid[x][y] = null;
// Remove from placedBlocks array
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === existingBlock) {
placedBlocks.splice(j, 1);
break;
}
}
}
}
}
// Generate heaven environment with golden blocks and clouds
selectedBlockType = 'gold';
// Create golden platform in heaven
var heavenPlatformY = Math.floor(GRID_HEIGHT * 0.8);
for (var x = 0; x < GRID_WIDTH; x++) {
placeBlock(x, heavenPlatformY);
if (Math.random() < 0.3) {
placeBlock(x, heavenPlatformY - 1); // Add some elevation
}
}
// Add diamond clouds scattered around
selectedBlockType = 'diamond';
for (var i = 0; i < 15; i++) {
var cloudX = Math.floor(Math.random() * GRID_WIDTH);
var cloudY = Math.floor(Math.random() * heavenPlatformY * 0.7);
if (isValidGridPosition(cloudX, cloudY) && grid[cloudX][cloudY] === null) {
placeBlock(cloudX, cloudY);
}
}
// Reset selection
selectedBlockType = 'dirt';
updateToolbarSelection();
}
// Create particle effects - multiple small pieces flying out
for (var p = 0; p < 6; p++) {
var particle = new Container();
var particleGraphics = particle.attachAsset(blockType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particle.x = block.x;
particle.y = block.y;
game.addChild(particle);
// Random direction for each particle
var angle = p / 6 * Math.PI * 2 + (Math.random() - 0.5) * 0.5;
var speed = 100 + Math.random() * 80;
var targetX = particle.x + Math.cos(angle) * speed;
var targetY = particle.y + Math.sin(angle) * speed;
// Animate particle flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Add breaking animation with flash effect and rotation
LK.effects.flashObject(block, 0xFFFFFF, 200);
// Add screen shake effect for impact
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 8;
tween(game, {
x: originalX + shakeIntensity
}, {
duration: 30,
easing: tween.easeOut
});
LK.setTimeout(function () {
tween(game, {
x: originalX - shakeIntensity
}, {
duration: 30,
easing: tween.easeOut
});
}, 30);
LK.setTimeout(function () {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 40,
easing: tween.easeOut
});
}, 60);
// Remove from placedBlocks array
for (var i = placedBlocks.length - 1; i >= 0; i--) {
if (placedBlocks[i] === block) {
placedBlocks.splice(i, 1);
break;
}
}
// Animate block destruction with scaling and rotation
tween(block, {
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: Math.PI * 0.5
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
block.destroy();
}
});
grid[gridX][gridY] = null;
inventory[blockType]++;
updateInventoryDisplay();
LK.getSound('break').play();
// Track achievement progress
blocksBroken++;
storage.blocksBroken = blocksBroken;
consecutiveBreakingStreak++;
storage.consecutiveBreakingStreak = consecutiveBreakingStreak;
consecutivePlacingStreak = 0; // Reset placing streak
storage.consecutivePlacingStreak = 0;
// Track specific resource collection
if (blockType === 'gold') {
goldCollected++;
storage.goldCollected = goldCollected;
} else if (blockType === 'diamond') {
diamondCollected++;
storage.diamondCollected = diamondCollected;
} else if (blockType === 'wood') {
woodCollected++;
storage.woodCollected = woodCollected;
} else if (blockType === 'stone') {
stoneCollected++;
storage.stoneCollected = stoneCollected;
}
// Track block type breaking for 'are you crazy?' achievement
if (!blockTypeBrokenCount[blockType]) {
blockTypeBrokenCount[blockType] = 0;
}
blockTypeBrokenCount[blockType]++;
storage.blockTypeBrokenCount = blockTypeBrokenCount;
// Track speed breaking
if (breakingSpeedStart === 0) {
breakingSpeedStart = LK.ticks;
breakingSpeedCount = 1;
} else {
var timeDiff = LK.ticks - breakingSpeedStart;
if (timeDiff <= 1200) {
// 20 seconds at 60fps
breakingSpeedCount++;
} else {
breakingSpeedStart = LK.ticks;
breakingSpeedCount = 1;
}
}
checkAllAchievements();
return true;
}
// Initialize toolbar selection
updateToolbarSelection();
updateInventoryDisplay();
game.down = function (x, y, obj) {
var gridPos = getGridPosition(x, y);
pressStartTime = LK.ticks;
isLongPress = false;
// Check if clicking on a villager first
for (var i = 0; i < villagers.length; i++) {
var villager = villagers[i];
if (villager.isAlive && villager.gridX === gridPos.x && villager.gridY === gridPos.y) {
// Record player attack for other villagers to react to
playerAttackPosition = {
x: gridPos.x,
y: gridPos.y
};
playerAttackTime = LK.ticks;
villager.kill();
return; // Exit early if we killed a villager
}
}
// Start long press timer for breaking blocks
if (pressTimer) {
LK.clearTimeout(pressTimer);
}
pressTimer = LK.setTimeout(function () {
isLongPress = true;
// Break only the specific block at clicked position
if (isValidGridPosition(gridPos.x, gridPos.y) && grid[gridPos.x][gridPos.y] !== null) {
removeBlock(gridPos.x, gridPos.y);
}
}, 500); // 500ms long press
};
game.up = function (x, y, obj) {
// Clear the long press timer
if (pressTimer) {
LK.clearTimeout(pressTimer);
pressTimer = null;
}
// If it wasn't a long press, place a block
if (!isLongPress) {
var gridPos = getGridPosition(x, y);
if (isValidGridPosition(gridPos.x, gridPos.y)) {
placeBlock(gridPos.x, gridPos.y);
}
}
};
// Generate procedural world with biomes
function generateWorld() {
// Reset selected block type to dirt for generation
selectedBlockType = 'dirt';
updateToolbarSelection();
// Create height map for smooth terrain
var heightMap = [];
var baseHeightLevel = Math.floor(GRID_HEIGHT * 0.7); // Base terrain level
for (var x = 0; x < GRID_WIDTH; x++) {
// Generate smooth terrain using sine waves
var primary = Math.sin(x / GRID_WIDTH * Math.PI * 2) * 3;
var secondary = Math.sin(x / GRID_WIDTH * Math.PI * 6) * 1.5;
var noise = (Math.random() - 0.5) * 2;
var height = baseHeightLevel + primary + secondary + noise;
heightMap[x] = Math.floor(Math.max(5, Math.min(height, GRID_HEIGHT - 3)));
}
// Smooth the height map to reduce abrupt changes
for (var pass = 0; pass < 2; pass++) {
for (var x = 1; x < GRID_WIDTH - 1; x++) {
var avg = (heightMap[x - 1] + heightMap[x] + heightMap[x + 1]) / 3;
heightMap[x] = Math.floor(avg);
}
}
// Define biome regions - now with 5 sections for more biomes
var biome1 = Math.floor(GRID_WIDTH * 0.2);
var biome2 = Math.floor(GRID_WIDTH * 0.4);
var biome3 = Math.floor(GRID_WIDTH * 0.6);
var biome4 = Math.floor(GRID_WIDTH * 0.8);
var biomes = ['plains', 'forest', 'mountains', 'lava_pools', 'village'];
var currentBiomes = [biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)], biomes[Math.floor(Math.random() * biomes.length)]];
// Generate terrain layers
for (var x = 0; x < GRID_WIDTH; x++) {
var currentBiome;
if (x < biome1) {
currentBiome = currentBiomes[0];
} else if (x < biome2) {
currentBiome = currentBiomes[1];
} else if (x < biome3) {
currentBiome = currentBiomes[2];
} else if (x < biome4) {
currentBiome = currentBiomes[3];
} else {
currentBiome = currentBiomes[4];
}
var baseHeight = heightMap[x];
var surfaceBlock = 'grass';
var treeChance = 0.15;
var stoneChance = 0.1;
// Biome-specific properties
if (currentBiome === 'plains') {
surfaceBlock = 'grass';
treeChance = 0.08;
stoneChance = 0.05;
} else if (currentBiome === 'forest') {
surfaceBlock = 'grass';
treeChance = 0.25; // More trees
stoneChance = 0.08;
} else if (currentBiome === 'mountains') {
surfaceBlock = 'stone'; // Rocky surface
treeChance = 0.05; // Few trees at high altitude
stoneChance = 0.25; // Very rocky
// Adjust height for mountains
baseHeight = Math.max(baseHeight - 3, Math.floor(GRID_HEIGHT * 0.5));
} else if (currentBiome === 'lava_pools') {
surfaceBlock = 'stone'; // Rocky volcanic surface
treeChance = 0.02; // Almost no trees
stoneChance = 0.4; // Very rocky volcanic terrain
// Slightly lower terrain for lava pools
baseHeight = Math.max(baseHeight + 1, Math.floor(GRID_HEIGHT * 0.75));
} else if (currentBiome === 'village') {
surfaceBlock = 'grass'; // Nice grassy village surface
treeChance = 0.1; // Moderate trees
stoneChance = 0.03; // Clean village area
// Flatten terrain for village
baseHeight = Math.floor(GRID_HEIGHT * 0.7);
}
// Place surface layer
selectedBlockType = surfaceBlock;
placeBlock(x, baseHeight);
// Place sub-surface layers consistently
selectedBlockType = 'dirt';
var dirtLayers = currentBiome === 'mountains' ? 2 : 3;
for (var y = baseHeight + 1; y < Math.min(baseHeight + dirtLayers + 1, GRID_HEIGHT); y++) {
placeBlock(x, y);
}
// Place stone in deeper layers consistently
selectedBlockType = 'stone';
for (var y = baseHeight + dirtLayers + 1; y < GRID_HEIGHT; y++) {
var stoneProb = currentBiome === 'mountains' ? 0.9 : 0.8;
if (Math.random() < stoneProb) {
placeBlock(x, y);
}
}
// Add trees in groups for natural look
if (Math.random() < treeChance && baseHeight > 2) {
selectedBlockType = 'wood';
var treeHeight;
if (currentBiome === 'forest') {
treeHeight = Math.floor(Math.random() * 3) + 3; // Tall trees
} else {
treeHeight = Math.floor(Math.random() * 2) + 2; // Normal trees
}
for (var i = 0; i < treeHeight; i++) {
var treeY = baseHeight - i - 1;
if (treeY >= 0) {
placeBlock(x, treeY);
}
}
// Add tree leaves/branches
if (currentBiome === 'forest' && treeHeight >= 3) {
selectedBlockType = 'grassLight';
if (x > 0 && baseHeight - treeHeight >= 0) {
placeBlock(x - 1, baseHeight - treeHeight + 1);
}
if (x < GRID_WIDTH - 1 && baseHeight - treeHeight >= 0) {
placeBlock(x + 1, baseHeight - treeHeight + 1);
}
}
}
// Add surface features sparingly
if (Math.random() < stoneChance && baseHeight > 0) {
selectedBlockType = 'stone';
placeBlock(x, baseHeight - 1);
}
// Add biome-specific features occasionally
if (currentBiome === 'mountains' && Math.random() < 0.1) {
selectedBlockType = 'stone';
if (baseHeight > 1) {
placeBlock(x, baseHeight - 1);
}
} else if (currentBiome === 'lava_pools' && Math.random() < 0.15) {
// Create lava pools
selectedBlockType = 'lava';
placeBlock(x, baseHeight + 1);
if (Math.random() < 0.6) {
placeBlock(x, baseHeight + 2); // Deeper lava
}
} else if (currentBiome === 'village' && Math.random() < 0.08) {
// Create simple village structures
selectedBlockType = 'wood';
for (var houseHeight = 0; houseHeight < 3; houseHeight++) {
var houseY = baseHeight - houseHeight - 1;
if (houseY >= 0) {
placeBlock(x, houseY);
}
}
}
}
// Add minimal scattered resources in logical places
for (var i = 0; i < 15; i++) {
var randX = Math.floor(Math.random() * GRID_WIDTH);
var surfaceY = heightMap[randX];
// Place resources near surface or in underground areas
var randY = Math.random() < 0.6 ? surfaceY + Math.floor(Math.random() * 3) + 1 : Math.floor(Math.random() * (GRID_HEIGHT - surfaceY - 5)) + surfaceY + 3;
if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) {
var resourceType;
if (randY > surfaceY + 2) {
// Underground resources
resourceType = Math.random() < 0.6 ? 'stone' : 'dirt';
} else {
// Surface resources
resourceType = blockTypes[Math.floor(Math.random() * blockTypes.length)];
}
selectedBlockType = resourceType;
placeBlock(randX, randY);
}
}
// Add rare precious blocks deep underground
for (var i = 0; i < 3; i++) {
var randX = Math.floor(Math.random() * GRID_WIDTH);
var surfaceY = heightMap[randX];
// Place deep underground only
var randY = Math.floor(Math.random() * 3) + GRID_HEIGHT - 5;
if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) {
var preciousType = Math.random() < 0.4 ? 'diamond' : 'gold';
selectedBlockType = preciousType;
placeBlock(randX, randY);
}
}
// Add portals with 10% chance - place 1-2 portals randomly
if (Math.random() < 0.1) {
var numPortals = Math.floor(Math.random() * 2) + 1; // 1 or 2 portals
for (var p = 0; p < numPortals; p++) {
var attempts = 0;
var portalPlaced = false;
while (attempts < 20 && !portalPlaced) {
var randX = Math.floor(Math.random() * GRID_WIDTH);
var surfaceY = heightMap[randX];
var randY = surfaceY - Math.floor(Math.random() * 3) - 1; // Place above surface
if (isValidGridPosition(randX, randY) && grid[randX][randY] === null) {
selectedBlockType = 'portal';
if (placeBlock(randX, randY)) {
portalPlaced = true;
}
}
attempts++;
}
}
}
// Create a few small, deliberate caves
for (var caveCount = 0; caveCount < 2; caveCount++) {
var caveX = Math.floor(Math.random() * (GRID_WIDTH - 4)) + 2;
var surfaceHeight = heightMap[caveX];
var caveY = surfaceHeight + Math.floor(Math.random() * 4) + 3;
var caveSize = 1;
// Create small hollow areas
for (var cx = caveX - caveSize; cx <= caveX + caveSize; cx++) {
for (var cy = caveY - caveSize; cy <= caveY + caveSize; cy++) {
if (isValidGridPosition(cx, cy) && grid[cx][cy] !== null) {
var block = grid[cx][cy];
if (block) {
block.destroy();
grid[cx][cy] = null;
// Remove from placedBlocks array
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === block) {
placedBlocks.splice(j, 1);
break;
}
}
}
}
}
}
}
// Spawn villagers in village biomes
for (var x = 0; x < GRID_WIDTH; x++) {
var currentBiome;
if (x < biome1) {
currentBiome = currentBiomes[0];
} else if (x < biome2) {
currentBiome = currentBiomes[1];
} else if (x < biome3) {
currentBiome = currentBiomes[2];
} else if (x < biome4) {
currentBiome = currentBiomes[3];
} else {
currentBiome = currentBiomes[4];
}
if (currentBiome === 'village' && Math.random() < 0.05) {
// Find surface level for villager placement
var surfaceY = heightMap[x];
var villagerY = surfaceY - 1;
// Make sure villager has ground to stand on
if (villagerY >= 0 && villagerY < GRID_HEIGHT) {
var villager = new Villager();
villager.setGridPosition(x, villagerY);
game.addChild(villager);
villagers.push(villager);
}
} else if (currentBiome === 'village' && Math.random() < 0.03) {
// Sometimes place villager eggs instead of villagers
var surfaceY = heightMap[x];
var eggY = surfaceY - 1;
if (eggY >= 0 && eggY < GRID_HEIGHT && grid[x][eggY] === null) {
selectedBlockType = 'villagerEgg';
placeBlock(x, eggY);
}
}
}
// Create enhanced cave systems
for (var caveCount = 0; caveCount < 4; caveCount++) {
var caveX = Math.floor(Math.random() * (GRID_WIDTH - 6)) + 3;
var surfaceHeight = heightMap[caveX];
var caveY = surfaceHeight + Math.floor(Math.random() * 6) + 4;
var caveSize = Math.floor(Math.random() * 2) + 1; // 1-2 size caves
// Create larger cave chambers
for (var cx = caveX - caveSize; cx <= caveX + caveSize; cx++) {
for (var cy = caveY - caveSize; cy <= caveY + caveSize; cy++) {
if (isValidGridPosition(cx, cy) && grid[cx][cy] !== null) {
var block = grid[cx][cy];
if (block) {
block.destroy();
grid[cx][cy] = null;
// Remove from placedBlocks array
for (var j = placedBlocks.length - 1; j >= 0; j--) {
if (placedBlocks[j] === block) {
placedBlocks.splice(j, 1);
break;
}
}
}
}
}
}
// Add cave decorations - stalactites and stalagmites
if (caveSize > 1) {
selectedBlockType = 'stone';
// Stalactites from ceiling
for (var i = 0; i < 3; i++) {
var stalX = caveX + Math.floor(Math.random() * 3) - 1;
var stalY = caveY - caveSize;
if (isValidGridPosition(stalX, stalY) && grid[stalX][stalY] === null) {
placeBlock(stalX, stalY);
}
}
// Stalagmites from floor
for (var i = 0; i < 2; i++) {
var stalagX = caveX + Math.floor(Math.random() * 3) - 1;
var stalagY = caveY + caveSize;
if (isValidGridPosition(stalagX, stalagY) && grid[stalagX][stalagY] === null) {
placeBlock(stalagX, stalagY);
}
}
}
}
// Reset to dirt selection
selectedBlockType = 'dirt';
updateToolbarSelection();
}
// Generate the world after a short delay
LK.setTimeout(function () {
generateWorld();
}, 500);
function applyGravity() {
var blocksToMove = [];
// Check all blocks from bottom to top
for (var y = GRID_HEIGHT - 2; y >= 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
var block = grid[x][y];
if (block !== null) {
// Check if block has support below
var hasSupport = false;
for (var checkY = y + 1; checkY < GRID_HEIGHT; checkY++) {
if (grid[x][checkY] !== null) {
hasSupport = true;
break;
}
}
// If no support, mark for falling
if (!hasSupport) {
var fallY = y;
// Find where it should fall to
for (var targetY = y + 1; targetY < GRID_HEIGHT; targetY++) {
if (grid[x][targetY] !== null) {
break;
}
fallY = targetY;
}
if (fallY !== y) {
blocksToMove.push({
block: block,
fromX: x,
fromY: y,
toX: x,
toY: fallY
});
}
}
}
}
}
// Move blocks that need to fall
for (var i = 0; i < blocksToMove.length; i++) {
var moveData = blocksToMove[i];
var block = moveData.block;
// Clear old position
grid[moveData.fromX][moveData.fromY] = null;
// Set new position
grid[moveData.toX][moveData.toY] = block;
block.setGridPosition(moveData.toX, moveData.toY);
}
}
game.update = function () {
// Apply gravity every few ticks to make blocks fall
if (LK.ticks % 10 === 0) {
applyGravity();
}
// Update flying status display
if (isFlying) {
flyingStatusText.setText('FLYING MODE');
flyingStatusText.visible = true;
// Track flying time for achievements
if (flyingStartTime > 0) {
var currentFlyingTime = (LK.ticks - flyingStartTime) * (1000 / 60);
if (currentFlyingTime >= 1000) {
// Check every second
totalFlyingTime += 1000;
storage.totalFlyingTime = totalFlyingTime;
flyingStartTime = LK.ticks;
checkAllAchievements();
}
}
} else {
flyingStatusText.visible = false;
}
// Check if inventory is at maximum for perfectionist achievement
var isMaxInventory = true;
for (var blockType in inventory) {
if (inventory[blockType] < 50) {
isMaxInventory = false;
break;
}
}
if (isMaxInventory) {
if (maxInventoryStartTime === 0) {
maxInventoryStartTime = LK.ticks;
} else {
var maxInventoryDuration = (LK.ticks - maxInventoryStartTime) * (1000 / 60);
totalMaxInventoryTime += 1000 / 60; // Add one frame worth of time
storage.totalMaxInventoryTime = totalMaxInventoryTime;
}
} else {
maxInventoryStartTime = 0;
}
// Update villagers
for (var i = 0; i < villagers.length; i++) {
villagers[i].update();
}
// Replenish inventory slowly over time
if (LK.ticks % 600 === 0) {
// Every 10 seconds
for (var blockType in inventory) {
if (inventory[blockType] < 50) {
inventory[blockType]++;
}
}
updateInventoryDisplay();
checkAllAchievements();
}
};