/**** * 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();
}
};