/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { collection: [] }); /**** * Classes ****/ // Collection item icon (for collection view) var CollectionIcon = Container.expand(function () { var self = Container.call(this); self.asset = null; self.overlayText = null; self.setItem = function (itemDef, owned) { if (self.asset) { self.removeChild(self.asset); self.asset = null; } if (self.overlayText) { self.removeChild(self.overlayText); self.overlayText = null; } self.asset = self.attachAsset(itemDef.asset, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); self.asset.alpha = 1; if (itemDef.rotation) self.asset.rotation = itemDef.rotation; if (itemDef.overlay) { self.overlayText = new Text2(itemDef.overlay, { size: 60, fill: "#222" }); self.overlayText.anchor.set(0.5, 0.5); self.addChild(self.overlayText); } // If not owned, gray out if (!owned) self.asset.alpha = 0.25;else self.asset.alpha = 1; }; return self; }); // Item display class var ItemDisplay = Container.expand(function () { var self = Container.call(this); self.asset = null; self.overlayText = null; self.setItem = function (itemDef) { // Remove previous if (self.asset) { self.removeChild(self.asset); self.asset = null; } if (self.overlayText) { self.removeChild(self.overlayText); self.overlayText = null; } // Add shape self.asset = self.attachAsset(itemDef.asset, { anchorX: 0.5, anchorY: 0.5 }); self.asset.alpha = 1; if (itemDef.rotation) self.asset.rotation = itemDef.rotation; // Overlay text if needed if (itemDef.overlay) { self.overlayText = new Text2(itemDef.overlay, { size: 120, fill: "#222" }); self.overlayText.anchor.set(0.5, 0.5); self.addChild(self.overlayText); } }; return self; }); // Roll button class var RollButton = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5 }); self.bg.alpha = 1; self.label = new Text2("ROLL", { size: 70, fill: "#fff" }); self.label.anchor.set(0.5, 0.5); self.addChild(self.label); // Simple press effect self.down = function (x, y, obj) { tween(self.bg, { scaleX: 0.95, scaleY: 0.95 }, { duration: 80, easing: tween.cubicIn }); }; self.up = function (x, y, obj) { tween(self.bg, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.cubicOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Item definitions: id, name, rarity (1 in X), asset, extra (for shape/rotation/text) // We'll overlay text for "hex" // We'll overlay text for "star" // We'll rotate this to look like a triangle /* We will use simple shapes for items (boxes, ellipses) and a "roll" button. Each item will have a unique color and shape for visual distinction. */ // --- State --- var ITEM_DEFS = [{ id: 0, name: "Water", rarity: 2, asset: 'item_box' }, { id: 1, name: "Stone", rarity: 3, asset: 'item_ellipse' }, { id: 2, name: "Flame", rarity: 7, asset: 'item_triangle', rotation: Math.PI / 4 }, { id: 3, name: "Breeze", rarity: 20, asset: 'item_star', overlay: "★" }, { id: 4, name: "Amber", rarity: 50, asset: 'item_hex', overlay: "⬡" }, { id: 5, name: "Moss", rarity: 60, asset: 'item_moss' }, { id: 6, name: "Crystal", rarity: 128, asset: 'item_crystal' }, { id: 7, name: "Sand", rarity: 256, asset: 'item_sand' }, { id: 8, name: "Ash", rarity: 512, asset: 'item_ash' }, { id: 9, name: "Iron", rarity: 1000, asset: 'item_iron' }, { id: 10, name: "Quartz", rarity: 2000, asset: 'item_quartz' }, { id: 11, name: "Smoke", rarity: 4000, asset: 'item_smoke' }, { id: 12, name: "Frost", rarity: 8000, asset: 'item_frost' }, { id: 13, name: "Thunder", rarity: 16000, asset: 'item_thunder' }, { id: 14, name: "Vine", rarity: 24000, asset: 'item_vine' }, { id: 15, name: "Spark", rarity: 28000, asset: 'item_spark' }]; // --- Luck Event State --- var luckEventActive = false; // true if a luck event is active var luckEventMultiplier = 1; // 1 (normal), 10, or 25 var luckEventTimeout = null; // timeout id for ending event var luckEventLabel = null; // Text2 label for event var luckEventTimerBar = null; // Container for timer bar var luckEventEndTime = 0; // timestamp when event ends // --- Level State --- var level = 1; var rollsThisLevel = 0; var rollsTotal = 0; var levelBar = null; var levelBarLabel = null; var levelBarBg = null; var levelBarFg = null; var levelLuckMultiplier = 1; // Helper: Start a random luck event (10x or 25x) function startLuckEvent(multiplier) { // End any existing event endLuckEvent(); luckEventActive = true; luckEventMultiplier = multiplier; luckEventEndTime = Date.now() + 20000; // 20 seconds from now // Show label if (!luckEventLabel) { luckEventLabel = new Text2("", { size: 80, fill: 0xFFE066 }); luckEventLabel.anchor.set(0.5, 0); LK.gui.top.addChild(luckEventLabel); // Keep level bar UI in sync (in case of external changes) if (levelBarFg && levelBarLabel) { var frac = Math.min(1, rollsThisLevel / 100); levelBarFg.width = 1200 * frac; levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/100) Luck x" + levelLuckMultiplier * luckEventMultiplier); } } ; luckEventLabel.visible = true; luckEventLabel.setText(multiplier === 25 ? "💎 ULTRA LUCK! 25x odds for 20s!" : "🍀 LUCKY! 10x odds for 20s!"); luckEventLabel.x = 180; luckEventLabel.y = 340; // Show timer bar if (!luckEventTimerBar) { luckEventTimerBar = new Container(); luckEventTimerBar.bg = LK.getAsset('item_box', { anchorX: 0, anchorY: 0 }); luckEventTimerBar.bg.width = 600; luckEventTimerBar.bg.height = 30; luckEventTimerBar.bg.alpha = 0.2; luckEventTimerBar.addChild(luckEventTimerBar.bg); luckEventTimerBar.fg = LK.getAsset('item_box', { anchorX: 0, anchorY: 0 }); luckEventTimerBar.fg.width = 600; luckEventTimerBar.fg.height = 30; luckEventTimerBar.fg.alpha = 0.7; luckEventTimerBar.fg.tint = 0xffe066; luckEventTimerBar.addChild(luckEventTimerBar.fg); LK.gui.top.addChild(luckEventTimerBar); } luckEventTimerBar.visible = true; luckEventTimerBar.x = 180; luckEventTimerBar.y = 440; // Set timeout to end event luckEventTimeout = LK.setTimeout(function () { endLuckEvent(); }, 20000); } // Helper: End the luck event function endLuckEvent() { luckEventActive = false; luckEventMultiplier = 1; if (luckEventTimeout) { LK.clearTimeout(luckEventTimeout); luckEventTimeout = null; } if (luckEventLabel) luckEventLabel.visible = false; if (luckEventTimerBar) luckEventTimerBar.visible = false; } // Helper: Schedule the next random luck event function scheduleNextLuckEvent() { // Next event in 20-40 seconds var nextIn = 20000 + Math.random() * 20000; LK.setTimeout(function () { // 50% chance for 10x, 50% for 25x var multiplier = Math.random() < 0.5 ? 10 : 25; startLuckEvent(multiplier); // Schedule next event after this one ends LK.setTimeout(scheduleNextLuckEvent, 20000); }, nextIn); } // Start the first luck event timer scheduleNextLuckEvent(); // Helper: get item by id function getItemDef(id) { for (var i = 0; i < ITEM_DEFS.length; ++i) { if (ITEM_DEFS[i].id === id) return ITEM_DEFS[i]; } return null; } // Reset all persistent player data at every game start delete storage.collection; delete storage.luckyPebbleCrafted; delete storage.fortuneLeafCrafted; var collection = []; // array of owned item ids var lastRollResult = null; // id of last rolled item var rolling = false; // is animation running var showCollection = false; // --- Roll Cooldown State --- var rollCooldown = false; // true if roll is on cooldown var rollCooldownTimeout = null; // timeout id for cooldown // --- UI Elements --- // Level bar UI (bottom of screen) levelBar = new Container(); levelBarBg = LK.getAsset('item_box', { anchorX: 0.5, anchorY: 0.5 }); levelBarBg.width = 1200; levelBarBg.height = 60; levelBarBg.alpha = 0.18; levelBar.addChild(levelBarBg); levelBarFg = LK.getAsset('item_box', { anchorX: 0.5, anchorY: 0.5 }); levelBarFg.width = 1200; levelBarFg.height = 60; levelBarFg.alpha = 0.7; levelBarFg.tint = 0x44e0ff; levelBar.addChild(levelBarFg); levelBarLabel = new Text2("", { size: 48, fill: "#fff" }); levelBarLabel.anchor.set(0.5, 0.5); levelBar.addChild(levelBarLabel); levelBar.x = 2048 / 2; levelBar.y = 2732 - 100; game.addChild(levelBar); // Title var titleTxt = new Text2("Loot Roll", { size: 120, fill: "#fff" }); titleTxt.anchor.set(0.5, 0); LK.gui.top.addChild(titleTxt); // Rarity info var rarityTxt = new Text2("", { size: 60, fill: "#fff" }); rarityTxt.anchor.set(0.5, 0); LK.gui.top.addChild(rarityTxt); // Main item display var itemDisplay = new ItemDisplay(); itemDisplay.x = 2048 / 2; itemDisplay.y = 900; game.addChild(itemDisplay); // Item name display (shows the rolled item's name) var itemNameTxt = new Text2("", { size: 90, fill: "#fff" }); itemNameTxt.anchor.set(0.5, 0); itemNameTxt.x = 2048 / 2; itemNameTxt.y = itemDisplay.y + 220; // Place below the item display game.addChild(itemNameTxt); // Roll button var rollBtn = new RollButton(); rollBtn.x = 2048 / 2; rollBtn.y = 1400; // moved up from 1800 game.addChild(rollBtn); // "Show Collection" button var showCollBtn = new Container(); var showCollBg = showCollBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, // Equalize to rollBtn scaleY: 1 // Equalize to rollBtn }); showCollBg.alpha = 1; var showCollLabel = new Text2("COLLECTION", { size: 48, // Shrunk to fit inside button fill: "#fff" }); showCollLabel.anchor.set(0.5, 0.5); showCollBtn.addChild(showCollLabel); showCollBtn.x = 2048 / 2; showCollBtn.y = 1600; // moved up from 2050 game.addChild(showCollBtn); // --- Inventory Button and State --- // inventory is now an array of objects: { id: <itemId>, count: <number> } var inventory = []; var inventoryBtn = new Container(); var inventoryBg = inventoryBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, // Equalize to rollBtn scaleY: 1 // Equalize to rollBtn }); inventoryBg.alpha = 1; var inventoryLabel = new Text2("INVENTORY", { size: 48, // Shrunk to fit inside button fill: "#fff" }); inventoryLabel.anchor.set(0.5, 0.5); inventoryBtn.addChild(inventoryLabel); // Place inventory button under collection button inventoryBtn.x = 2048 / 2; inventoryBtn.y = showCollBtn.y + 170; // will now be 1770, higher up game.addChild(inventoryBtn); // --- Craft Button --- var craftBtn = new Container(); var craftBg = craftBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, // Equalize to rollBtn scaleY: 1 // Equalize to rollBtn }); craftBg.alpha = 1; var craftLabel = new Text2("CRAFT", { size: 70, // Match rollBtn label size fill: "#fff" }); craftLabel.anchor.set(0.5, 0.5); craftBtn.addChild(craftLabel); // Place craft button under inventory button craftBtn.x = 2048 / 2; craftBtn.y = inventoryBtn.y + 170; game.addChild(craftBtn); // --- Lucky Pebble & Fortune Leaf Craft Recipes --- // Recipe definitions var CRAFT_RECIPES = [{ name: "Lucky Pebble", asset: "craft_luckypebble", ingredients: [{ id: 15, name: "Spark", count: 1 }, // Spark { id: 7, name: "Sand", count: 1 } // Sand ], effect: function effect() { // Permanently give 2x luck multiplier if (!storage.luckyPebbleCrafted) { storage.luckyPebbleCrafted = true; levelLuckMultiplier *= 2; // Flash level bar to indicate permanent luck LK.effects.flashObject(levelBarFg, 0xffe066, 1200); levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier); } } }, { name: "Fortune Leaf", asset: "craft_fortuneleaf", ingredients: [{ id: 5, name: "Moss", count: 1 }, { id: 3, name: "Breeze", count: 1 }], effect: function effect() { // Permanently give 3x luck multiplier if (!storage.fortuneLeafCrafted) { storage.fortuneLeafCrafted = true; levelLuckMultiplier *= 3; // Flash level bar to indicate permanent luck LK.effects.flashObject(levelBarFg, 0x44ff66, 1200); levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier); } } }]; // Craft overlay UI var craftOverlay = new Container(); craftOverlay.visible = false; game.addChild(craftOverlay); // Craft recipe icon, name, and button var craftRecipeIcon = null; var craftRecipeName = null; var craftRecipeBtn = null; var craftRecipeBtnLabel = null; var craftRecipeMsg = null; // Show craft overlay for selected recipe (default to first) function showCraftOverlay(selectedRecipeIdx) { // Remove previous children while (craftOverlay.children.length) craftOverlay.removeChild(craftOverlay.children[0]); // If not specified, show first recipe var recipeIdx = typeof selectedRecipeIdx === "number" ? selectedRecipeIdx : 0; var recipe = CRAFT_RECIPES[recipeIdx]; // Icon craftRecipeIcon = LK.getAsset(recipe.asset, { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); craftRecipeIcon.x = 2048 / 2; craftRecipeIcon.y = 700; craftOverlay.addChild(craftRecipeIcon); // Name craftRecipeName = new Text2(recipe.name, { size: 80, fill: "#fff" }); craftRecipeName.anchor.set(0.5, 0); craftRecipeName.x = 2048 / 2; craftRecipeName.y = 900; craftOverlay.addChild(craftRecipeName); // Ingredients var ingText = "Requires: "; for (var i = 0; i < recipe.ingredients.length; ++i) { var ing = recipe.ingredients[i]; ingText += ing.name + (i < recipe.ingredients.length - 1 ? " + " : ""); } var craftRecipeIng = new Text2(ingText, { size: 48, fill: "#fff" }); craftRecipeIng.anchor.set(0.5, 0); craftRecipeIng.x = 2048 / 2; craftRecipeIng.y = 1000; craftOverlay.addChild(craftRecipeIng); // Permanent effect info var effectText = ""; if (recipeIdx === 0) { effectText = "Permanently gives 2x luck multiplier!"; } else if (recipeIdx === 1) { effectText = "Permanently gives 3x luck multiplier!"; } var craftRecipeEffect = new Text2(effectText, { size: 48, fill: 0xFFE066 }); craftRecipeEffect.anchor.set(0.5, 0); craftRecipeEffect.x = 2048 / 2; craftRecipeEffect.y = 1080; craftOverlay.addChild(craftRecipeEffect); // Craft button craftRecipeBtn = new Container(); var btnBg = craftRecipeBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); btnBg.alpha = 1; craftRecipeBtnLabel = new Text2("CRAFT", { size: 48, fill: "#fff" }); craftRecipeBtnLabel.anchor.set(0.5, 0.5); craftRecipeBtn.addChild(craftRecipeBtnLabel); craftRecipeBtn.x = 2048 / 2; craftRecipeBtn.y = 1200; craftOverlay.addChild(craftRecipeBtn); // Message text craftRecipeMsg = new Text2("", { size: 44, fill: "#fff" }); craftRecipeMsg.anchor.set(0.5, 0); craftRecipeMsg.x = 2048 / 2; craftRecipeMsg.y = 1300; craftOverlay.addChild(craftRecipeMsg); // Back button var craftBackBtn = new Container(); var craftBackBg = craftBackBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); craftBackBg.alpha = 1; var craftBackLabel = new Text2("BACK", { size: 48, fill: "#fff" }); craftBackLabel.anchor.set(0.5, 0.5); craftBackBtn.addChild(craftBackLabel); craftBackBtn.x = 2048 / 2; craftBackBtn.y = 2500; craftOverlay.addChild(craftBackBtn); // If there are multiple recipes, add a toggle button to switch between them if (CRAFT_RECIPES.length > 1) { var toggleBtn = new Container(); var toggleBg = toggleBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); toggleBg.alpha = 1; var toggleLabel = new Text2(recipeIdx === 0 ? "NEXT" : "PREV", { size: 36, fill: "#fff" }); toggleLabel.anchor.set(0.5, 0.5); toggleBtn.addChild(toggleLabel); toggleBtn.x = 2048 / 2 + 350; toggleBtn.y = 900; craftOverlay.addChild(toggleBtn); toggleBtn.down = function (x, y, obj) { toggleBg.scaleX = 0.95; toggleBg.scaleY = 0.95; }; toggleBtn.up = function (x, y, obj) { toggleBg.scaleX = 1; toggleBg.scaleY = 1; // Toggle between recipes showCraftOverlay(recipeIdx === 0 ? 1 : 0); }; } // Craft button handlers craftRecipeBtn.down = function (x, y, obj) { btnBg.scaleX = 0.95; btnBg.scaleY = 0.95; }; craftRecipeBtn.up = function (x, y, obj) { btnBg.scaleX = 1; btnBg.scaleY = 1; // Check if already crafted if (recipeIdx === 0 && storage.luckyPebbleCrafted || recipeIdx === 1 && storage.fortuneLeafCrafted) { craftRecipeMsg.setText("Already crafted!"); return; } // Check inventory for ingredients var canCraft = true; for (var i = 0; i < recipe.ingredients.length; ++i) { var ing = recipe.ingredients[i]; var found = false; for (var j = 0; j < inventory.length; ++j) { if (inventory[j].id === ing.id && inventory[j].count >= ing.count) { found = true; break; } } if (!found) { canCraft = false; break; } } if (!canCraft) { craftRecipeMsg.setText("Missing ingredients!"); return; } // Remove ingredients from inventory for (var i = 0; i < recipe.ingredients.length; ++i) { var ing = recipe.ingredients[i]; for (var j = 0; j < inventory.length; ++j) { if (inventory[j].id === ing.id) { inventory[j].count -= ing.count; if (inventory[j].count <= 0) { inventory.splice(j, 1); } break; } } } // Apply effect recipe.effect(); if (recipeIdx === 0) { craftRecipeMsg.setText("Crafted! 2x luck multiplier unlocked!"); } else if (recipeIdx === 1) { craftRecipeMsg.setText("Crafted! 3x luck multiplier unlocked!"); } }; // Back button handlers craftBackBtn.down = function (x, y, obj) { craftBackBg.scaleX = 0.95; craftBackBg.scaleY = 0.95; }; craftBackBtn.up = function (x, y, obj) { craftBackBg.scaleX = 1; craftBackBg.scaleY = 1; craftOverlay.visible = false; // Restore main UI elements titleTxt.visible = true; rarityTxt.visible = true; itemDisplay.visible = true; itemNameTxt.visible = true; rollBtn.visible = true; showCollBtn.visible = true; inventoryBtn.visible = true; craftBtn.visible = true; }; craftOverlay.visible = true; // Hide main UI elements titleTxt.visible = false; rarityTxt.visible = false; itemDisplay.visible = false; itemNameTxt.visible = false; rollBtn.visible = false; showCollBtn.visible = false; inventoryBtn.visible = false; craftBtn.visible = false; } // Craft button handlers craftBtn.down = function (x, y, obj) { craftBg.scaleX = 0.95; craftBg.scaleY = 0.95; }; craftBtn.up = function (x, y, obj) { craftBg.scaleX = 1; craftBg.scaleY = 1; if (rolling || showCollection || inventoryOverlay.visible) return; showCraftOverlay(); }; // On load, if Lucky Pebble was crafted, apply effect if (storage.luckyPebbleCrafted) { levelLuckMultiplier *= 2; } // On load, if Fortune Leaf was crafted, apply effect if (storage.fortuneLeafCrafted) { levelLuckMultiplier *= 3; } // Collection view overlay var collectionOverlay = new Container(); collectionOverlay.visible = false; game.addChild(collectionOverlay); // "Back" button for collection var backBtn = new Container(); var backBg = backBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); backBg.alpha = 1; var backLabel = new Text2("BACK", { size: 48, fill: "#fff" }); backLabel.anchor.set(0.5, 0.5); backBtn.addChild(backLabel); backBtn.x = 2048 / 2; backBtn.y = 2500; collectionOverlay.addChild(backBtn); // Collection grid var collectionIcons = []; function updateCollectionGrid() { // Remove old for (var i = 0; i < collectionIcons.length; ++i) collectionOverlay.removeChild(collectionIcons[i]); collectionIcons = []; // Layout: 3 per row, centered var perRow = 3; var spacing = 320; var startX = 2048 / 2 - spacing; var startY = 600; for (var i = 0; i < ITEM_DEFS.length; ++i) { var item = ITEM_DEFS[i]; var owned = collection.indexOf(item.id) !== -1; var icon = new CollectionIcon(); icon.setItem(item, owned); icon.x = startX + i % perRow * spacing; icon.y = startY + Math.floor(i / perRow) * spacing; // Name and rarity var nameTxt = new Text2(item.name, { size: 36, fill: "#fff" }); nameTxt.anchor.set(0.5, 0); nameTxt.x = 0; nameTxt.y = 90; icon.addChild(nameTxt); var rareTxt = new Text2("1 in " + item.rarity, { size: 28, fill: "#aaa" }); rareTxt.anchor.set(0.5, 0); rareTxt.x = 0; rareTxt.y = 130; icon.addChild(rareTxt); collectionOverlay.addChild(icon); collectionIcons.push(icon); } } // --- Functions --- function updateMainDisplay() { // Show last rolled item or first item var itemId = lastRollResult !== null ? lastRollResult : 0; var itemDef = getItemDef(itemId); itemDisplay.setItem(itemDef); // Show name and rarity rarityTxt.setText(itemDef.name + " • 1 in " + itemDef.rarity); // Show item name below the item display itemNameTxt.setText(itemDef.name); } function rollForItem() { // Weighted random roll, apply luck event and level multipliers var totalWeight = 0; for (var i = 0; i < ITEM_DEFS.length; ++i) { var rarity = ITEM_DEFS[i].rarity; // Apply multiplier: only to rarest item (lowest odds, highest rarity) var effectiveRarity = rarity; if (i === ITEM_DEFS.length - 1) { effectiveRarity = Math.max(1, Math.floor(rarity / (levelLuckMultiplier * luckEventMultiplier))); } totalWeight += 1 / effectiveRarity; } var r = Math.random() * totalWeight; var acc = 0; for (var i = 0; i < ITEM_DEFS.length; ++i) { var rarity = ITEM_DEFS[i].rarity; var effectiveRarity = rarity; if (i === ITEM_DEFS.length - 1) { effectiveRarity = Math.max(1, Math.floor(rarity / (levelLuckMultiplier * luckEventMultiplier))); } acc += 1 / effectiveRarity; if (r <= acc) return ITEM_DEFS[i].id; } return ITEM_DEFS[ITEM_DEFS.length - 1].id; } function animateRoll(resultId) { rolling = true; // Flash through random items, then land on result var flashes = 12; var flashTime = 60; var i = 0; function flashNext() { if (i < flashes) { var fakeId = Math.floor(Math.random() * ITEM_DEFS.length); itemDisplay.setItem(getItemDef(fakeId)); ++i; LK.setTimeout(flashNext, flashTime); } else { // Land on result itemDisplay.setItem(getItemDef(resultId)); // Animate scale itemDisplay.scale.set(1.2, 1.2); tween(itemDisplay, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.bounceOut }); rolling = false; updateMainDisplay(); } } flashNext(); } // --- Event Handlers --- // Roll button rollBtn.down = function (x, y, obj) { if (rolling || showCollection || rollCooldown) return; // Call the original RollButton.down (the class method), not this event handler if (typeof RollButton.prototype.down === "function") { RollButton.prototype.down.call(rollBtn, x, y, obj); } }; rollBtn.up = function (x, y, obj) { if (rolling || showCollection || rollCooldown) return; // Start cooldown rollCooldown = true; if (rollCooldownTimeout) { LK.clearTimeout(rollCooldownTimeout); } rollBtn.alpha = 0.5; rollCooldownTimeout = LK.setTimeout(function () { rollCooldown = false; rollBtn.alpha = 1; rollCooldownTimeout = null; }, 2000); // Do roll var resultId = rollForItem(); lastRollResult = resultId; // Add to collection if new if (collection.indexOf(resultId) === -1) { collection.push(resultId); storage.collection = collection; } // Add all rolled items to inventory, accumulate up to 64 per item var itemDef = getItemDef(resultId); if (itemDef) { // Find if item already exists in inventory var found = false; for (var i = 0; i < inventory.length; ++i) { if (inventory[i].id === resultId) { if (inventory[i].count < 64) { inventory[i].count += 1; } found = true; break; } } if (!found) { inventory.push({ id: resultId, count: 1 }); } } animateRoll(resultId); updateMainDisplay(); // --- Level logic --- rollsTotal += 1; rollsThisLevel += 1; var leveledUp = false; if (rollsThisLevel >= 50) { level += 1; rollsThisLevel = 0; leveledUp = true; // Increase level-based luck multiplier levelLuckMultiplier += 1; // Flash level bar to indicate level up LK.effects.flashObject(levelBarFg, 0xffe066, 800); } // Update level bar UI var frac = Math.min(1, rollsThisLevel / 50); levelBarFg.width = 1200 * frac; levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier); // Call the original RollButton.up (the class method), not this event handler if (typeof RollButton.prototype.up === "function") { RollButton.prototype.up.call(rollBtn, x, y, obj); } }; // Show collection showCollBtn.down = function (x, y, obj) { if (rolling) return; showCollBg.scaleX = 0.95; showCollBg.scaleY = 0.95; }; showCollBtn.up = function (x, y, obj) { if (rolling) return; showCollBg.scaleX = 1; showCollBg.scaleY = 1; showCollection = true; collectionOverlay.visible = true; updateCollectionGrid(); // Hide main UI elements when collection is open titleTxt.visible = false; rarityTxt.visible = false; itemDisplay.visible = false; itemNameTxt.visible = false; rollBtn.visible = false; showCollBtn.visible = false; inventoryBtn.visible = false; craftBtn.visible = false; }; // Back from collection backBtn.down = function (x, y, obj) { backBg.scaleX = 0.95; backBg.scaleY = 0.95; }; backBtn.up = function (x, y, obj) { backBg.scaleX = 1; backBg.scaleY = 1; showCollection = false; collectionOverlay.visible = false; // Restore main UI elements when leaving collection titleTxt.visible = true; rarityTxt.visible = true; itemDisplay.visible = true; itemNameTxt.visible = true; rollBtn.visible = true; showCollBtn.visible = true; inventoryBtn.visible = true; craftBtn.visible = true; }; // --- Inventory Overlay --- var inventoryOverlay = new Container(); inventoryOverlay.visible = false; game.addChild(inventoryOverlay); // "Back" button for inventory var invBackBtn = new Container(); var invBackBg = invBackBtn.attachAsset('roll_btn', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); invBackBg.alpha = 1; var invBackLabel = new Text2("BACK", { size: 48, fill: "#fff" }); invBackLabel.anchor.set(0.5, 0.5); invBackBtn.addChild(invBackLabel); invBackBtn.x = 2048 / 2; invBackBtn.y = 2500; inventoryOverlay.addChild(invBackBtn); // Inventory grid var inventoryIcons = []; function updateInventoryGrid() { // Remove old for (var i = 0; i < inventoryIcons.length; ++i) inventoryOverlay.removeChild(inventoryIcons[i]); inventoryIcons = []; // Layout: 3 per row, centered var perRow = 3; var spacing = 320; var startX = 2048 / 2 - spacing; var startY = 600; for (var i = 0; i < inventory.length; ++i) { var invEntry = inventory[i]; var itemId = invEntry.id; var itemCount = invEntry.count; var item = getItemDef(itemId); if (!item) continue; var icon = new CollectionIcon(); icon.setItem(item, true); icon.x = startX + i % perRow * spacing; icon.y = startY + Math.floor(i / perRow) * spacing; // Name and rarity var nameTxt = new Text2(item.name, { size: 36, fill: "#fff" }); nameTxt.anchor.set(0.5, 0); nameTxt.x = 0; nameTxt.y = 90; icon.addChild(nameTxt); var rareTxt = new Text2("1 in " + item.rarity, { size: 28, fill: "#aaa" }); rareTxt.anchor.set(0.5, 0); rareTxt.x = 0; rareTxt.y = 130; icon.addChild(rareTxt); // Show count if more than 1 if (itemCount > 1) { var countTxt = new Text2("x" + itemCount, { size: 40, fill: 0xFFE066 }); countTxt.anchor.set(1, 1); countTxt.x = 90; countTxt.y = 90; icon.addChild(countTxt); } inventoryOverlay.addChild(icon); inventoryIcons.push(icon); } } // Inventory button handlers inventoryBtn.down = function (x, y, obj) { inventoryBg.scaleX = 0.95; inventoryBg.scaleY = 0.95; }; inventoryBtn.up = function (x, y, obj) { inventoryBg.scaleX = 1; inventoryBg.scaleY = 1; if (rolling) return; showCollection = false; collectionOverlay.visible = false; inventoryOverlay.visible = true; updateInventoryGrid(); // Hide main UI elements when inventory is open titleTxt.visible = false; rarityTxt.visible = false; itemDisplay.visible = false; itemNameTxt.visible = false; rollBtn.visible = false; showCollBtn.visible = false; inventoryBtn.visible = false; craftBtn.visible = false; }; // Inventory back button handlers invBackBtn.down = function (x, y, obj) { invBackBg.scaleX = 0.95; invBackBg.scaleY = 0.95; }; invBackBtn.up = function (x, y, obj) { invBackBg.scaleX = 1; invBackBg.scaleY = 1; inventoryOverlay.visible = false; // Restore main UI elements when leaving inventory titleTxt.visible = true; rarityTxt.visible = true; itemDisplay.visible = true; itemNameTxt.visible = true; rollBtn.visible = true; showCollBtn.visible = true; inventoryBtn.visible = true; craftBtn.visible = true; }; // --- Initial State --- updateMainDisplay(); collectionOverlay.visible = false; // --- Level bar initial state --- rollsThisLevel = 0; level = 1; levelLuckMultiplier = 1; levelBarFg.width = 0; levelBarLabel.setText("Level 1 (0/50) Luck x1"); // --- GUI Layout --- titleTxt.x = 2048 / 2; titleTxt.y = 40; rarityTxt.x = 2048 / 2; rarityTxt.y = 200; // --- Touch Handling (prevent drag) --- game.move = function (x, y, obj) {}; game.down = function (x, y, obj) {}; game.up = function (x, y, obj) {}; // --- Game Update --- game.update = function () { // Update luck event timer bar if active if (luckEventActive && luckEventTimerBar && luckEventTimerBar.visible) { var now = Date.now(); var total = 20000; var left = Math.max(0, luckEventEndTime - now); var frac = left / total; luckEventTimerBar.fg.width = 600 * frac; if (left <= 0) { endLuckEvent(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
collection: []
});
/****
* Classes
****/
// Collection item icon (for collection view)
var CollectionIcon = Container.expand(function () {
var self = Container.call(this);
self.asset = null;
self.overlayText = null;
self.setItem = function (itemDef, owned) {
if (self.asset) {
self.removeChild(self.asset);
self.asset = null;
}
if (self.overlayText) {
self.removeChild(self.overlayText);
self.overlayText = null;
}
self.asset = self.attachAsset(itemDef.asset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
self.asset.alpha = 1;
if (itemDef.rotation) self.asset.rotation = itemDef.rotation;
if (itemDef.overlay) {
self.overlayText = new Text2(itemDef.overlay, {
size: 60,
fill: "#222"
});
self.overlayText.anchor.set(0.5, 0.5);
self.addChild(self.overlayText);
}
// If not owned, gray out
if (!owned) self.asset.alpha = 0.25;else self.asset.alpha = 1;
};
return self;
});
// Item display class
var ItemDisplay = Container.expand(function () {
var self = Container.call(this);
self.asset = null;
self.overlayText = null;
self.setItem = function (itemDef) {
// Remove previous
if (self.asset) {
self.removeChild(self.asset);
self.asset = null;
}
if (self.overlayText) {
self.removeChild(self.overlayText);
self.overlayText = null;
}
// Add shape
self.asset = self.attachAsset(itemDef.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.asset.alpha = 1;
if (itemDef.rotation) self.asset.rotation = itemDef.rotation;
// Overlay text if needed
if (itemDef.overlay) {
self.overlayText = new Text2(itemDef.overlay, {
size: 120,
fill: "#222"
});
self.overlayText.anchor.set(0.5, 0.5);
self.addChild(self.overlayText);
}
};
return self;
});
// Roll button class
var RollButton = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5
});
self.bg.alpha = 1;
self.label = new Text2("ROLL", {
size: 70,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// Simple press effect
self.down = function (x, y, obj) {
tween(self.bg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 80,
easing: tween.cubicIn
});
};
self.up = function (x, y, obj) {
tween(self.bg, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Item definitions: id, name, rarity (1 in X), asset, extra (for shape/rotation/text)
// We'll overlay text for "hex"
// We'll overlay text for "star"
// We'll rotate this to look like a triangle
/*
We will use simple shapes for items (boxes, ellipses) and a "roll" button.
Each item will have a unique color and shape for visual distinction.
*/
// --- State ---
var ITEM_DEFS = [{
id: 0,
name: "Water",
rarity: 2,
asset: 'item_box'
}, {
id: 1,
name: "Stone",
rarity: 3,
asset: 'item_ellipse'
}, {
id: 2,
name: "Flame",
rarity: 7,
asset: 'item_triangle',
rotation: Math.PI / 4
}, {
id: 3,
name: "Breeze",
rarity: 20,
asset: 'item_star',
overlay: "★"
}, {
id: 4,
name: "Amber",
rarity: 50,
asset: 'item_hex',
overlay: "⬡"
}, {
id: 5,
name: "Moss",
rarity: 60,
asset: 'item_moss'
}, {
id: 6,
name: "Crystal",
rarity: 128,
asset: 'item_crystal'
}, {
id: 7,
name: "Sand",
rarity: 256,
asset: 'item_sand'
}, {
id: 8,
name: "Ash",
rarity: 512,
asset: 'item_ash'
}, {
id: 9,
name: "Iron",
rarity: 1000,
asset: 'item_iron'
}, {
id: 10,
name: "Quartz",
rarity: 2000,
asset: 'item_quartz'
}, {
id: 11,
name: "Smoke",
rarity: 4000,
asset: 'item_smoke'
}, {
id: 12,
name: "Frost",
rarity: 8000,
asset: 'item_frost'
}, {
id: 13,
name: "Thunder",
rarity: 16000,
asset: 'item_thunder'
}, {
id: 14,
name: "Vine",
rarity: 24000,
asset: 'item_vine'
}, {
id: 15,
name: "Spark",
rarity: 28000,
asset: 'item_spark'
}];
// --- Luck Event State ---
var luckEventActive = false; // true if a luck event is active
var luckEventMultiplier = 1; // 1 (normal), 10, or 25
var luckEventTimeout = null; // timeout id for ending event
var luckEventLabel = null; // Text2 label for event
var luckEventTimerBar = null; // Container for timer bar
var luckEventEndTime = 0; // timestamp when event ends
// --- Level State ---
var level = 1;
var rollsThisLevel = 0;
var rollsTotal = 0;
var levelBar = null;
var levelBarLabel = null;
var levelBarBg = null;
var levelBarFg = null;
var levelLuckMultiplier = 1;
// Helper: Start a random luck event (10x or 25x)
function startLuckEvent(multiplier) {
// End any existing event
endLuckEvent();
luckEventActive = true;
luckEventMultiplier = multiplier;
luckEventEndTime = Date.now() + 20000; // 20 seconds from now
// Show label
if (!luckEventLabel) {
luckEventLabel = new Text2("", {
size: 80,
fill: 0xFFE066
});
luckEventLabel.anchor.set(0.5, 0);
LK.gui.top.addChild(luckEventLabel);
// Keep level bar UI in sync (in case of external changes)
if (levelBarFg && levelBarLabel) {
var frac = Math.min(1, rollsThisLevel / 100);
levelBarFg.width = 1200 * frac;
levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/100) Luck x" + levelLuckMultiplier * luckEventMultiplier);
}
}
;
luckEventLabel.visible = true;
luckEventLabel.setText(multiplier === 25 ? "💎 ULTRA LUCK! 25x odds for 20s!" : "🍀 LUCKY! 10x odds for 20s!");
luckEventLabel.x = 180;
luckEventLabel.y = 340;
// Show timer bar
if (!luckEventTimerBar) {
luckEventTimerBar = new Container();
luckEventTimerBar.bg = LK.getAsset('item_box', {
anchorX: 0,
anchorY: 0
});
luckEventTimerBar.bg.width = 600;
luckEventTimerBar.bg.height = 30;
luckEventTimerBar.bg.alpha = 0.2;
luckEventTimerBar.addChild(luckEventTimerBar.bg);
luckEventTimerBar.fg = LK.getAsset('item_box', {
anchorX: 0,
anchorY: 0
});
luckEventTimerBar.fg.width = 600;
luckEventTimerBar.fg.height = 30;
luckEventTimerBar.fg.alpha = 0.7;
luckEventTimerBar.fg.tint = 0xffe066;
luckEventTimerBar.addChild(luckEventTimerBar.fg);
LK.gui.top.addChild(luckEventTimerBar);
}
luckEventTimerBar.visible = true;
luckEventTimerBar.x = 180;
luckEventTimerBar.y = 440;
// Set timeout to end event
luckEventTimeout = LK.setTimeout(function () {
endLuckEvent();
}, 20000);
}
// Helper: End the luck event
function endLuckEvent() {
luckEventActive = false;
luckEventMultiplier = 1;
if (luckEventTimeout) {
LK.clearTimeout(luckEventTimeout);
luckEventTimeout = null;
}
if (luckEventLabel) luckEventLabel.visible = false;
if (luckEventTimerBar) luckEventTimerBar.visible = false;
}
// Helper: Schedule the next random luck event
function scheduleNextLuckEvent() {
// Next event in 20-40 seconds
var nextIn = 20000 + Math.random() * 20000;
LK.setTimeout(function () {
// 50% chance for 10x, 50% for 25x
var multiplier = Math.random() < 0.5 ? 10 : 25;
startLuckEvent(multiplier);
// Schedule next event after this one ends
LK.setTimeout(scheduleNextLuckEvent, 20000);
}, nextIn);
}
// Start the first luck event timer
scheduleNextLuckEvent();
// Helper: get item by id
function getItemDef(id) {
for (var i = 0; i < ITEM_DEFS.length; ++i) {
if (ITEM_DEFS[i].id === id) return ITEM_DEFS[i];
}
return null;
}
// Reset all persistent player data at every game start
delete storage.collection;
delete storage.luckyPebbleCrafted;
delete storage.fortuneLeafCrafted;
var collection = []; // array of owned item ids
var lastRollResult = null; // id of last rolled item
var rolling = false; // is animation running
var showCollection = false;
// --- Roll Cooldown State ---
var rollCooldown = false; // true if roll is on cooldown
var rollCooldownTimeout = null; // timeout id for cooldown
// --- UI Elements ---
// Level bar UI (bottom of screen)
levelBar = new Container();
levelBarBg = LK.getAsset('item_box', {
anchorX: 0.5,
anchorY: 0.5
});
levelBarBg.width = 1200;
levelBarBg.height = 60;
levelBarBg.alpha = 0.18;
levelBar.addChild(levelBarBg);
levelBarFg = LK.getAsset('item_box', {
anchorX: 0.5,
anchorY: 0.5
});
levelBarFg.width = 1200;
levelBarFg.height = 60;
levelBarFg.alpha = 0.7;
levelBarFg.tint = 0x44e0ff;
levelBar.addChild(levelBarFg);
levelBarLabel = new Text2("", {
size: 48,
fill: "#fff"
});
levelBarLabel.anchor.set(0.5, 0.5);
levelBar.addChild(levelBarLabel);
levelBar.x = 2048 / 2;
levelBar.y = 2732 - 100;
game.addChild(levelBar);
// Title
var titleTxt = new Text2("Loot Roll", {
size: 120,
fill: "#fff"
});
titleTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(titleTxt);
// Rarity info
var rarityTxt = new Text2("", {
size: 60,
fill: "#fff"
});
rarityTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(rarityTxt);
// Main item display
var itemDisplay = new ItemDisplay();
itemDisplay.x = 2048 / 2;
itemDisplay.y = 900;
game.addChild(itemDisplay);
// Item name display (shows the rolled item's name)
var itemNameTxt = new Text2("", {
size: 90,
fill: "#fff"
});
itemNameTxt.anchor.set(0.5, 0);
itemNameTxt.x = 2048 / 2;
itemNameTxt.y = itemDisplay.y + 220; // Place below the item display
game.addChild(itemNameTxt);
// Roll button
var rollBtn = new RollButton();
rollBtn.x = 2048 / 2;
rollBtn.y = 1400; // moved up from 1800
game.addChild(rollBtn);
// "Show Collection" button
var showCollBtn = new Container();
var showCollBg = showCollBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
// Equalize to rollBtn
scaleY: 1 // Equalize to rollBtn
});
showCollBg.alpha = 1;
var showCollLabel = new Text2("COLLECTION", {
size: 48,
// Shrunk to fit inside button
fill: "#fff"
});
showCollLabel.anchor.set(0.5, 0.5);
showCollBtn.addChild(showCollLabel);
showCollBtn.x = 2048 / 2;
showCollBtn.y = 1600; // moved up from 2050
game.addChild(showCollBtn);
// --- Inventory Button and State ---
// inventory is now an array of objects: { id: <itemId>, count: <number> }
var inventory = [];
var inventoryBtn = new Container();
var inventoryBg = inventoryBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
// Equalize to rollBtn
scaleY: 1 // Equalize to rollBtn
});
inventoryBg.alpha = 1;
var inventoryLabel = new Text2("INVENTORY", {
size: 48,
// Shrunk to fit inside button
fill: "#fff"
});
inventoryLabel.anchor.set(0.5, 0.5);
inventoryBtn.addChild(inventoryLabel);
// Place inventory button under collection button
inventoryBtn.x = 2048 / 2;
inventoryBtn.y = showCollBtn.y + 170; // will now be 1770, higher up
game.addChild(inventoryBtn);
// --- Craft Button ---
var craftBtn = new Container();
var craftBg = craftBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
// Equalize to rollBtn
scaleY: 1 // Equalize to rollBtn
});
craftBg.alpha = 1;
var craftLabel = new Text2("CRAFT", {
size: 70,
// Match rollBtn label size
fill: "#fff"
});
craftLabel.anchor.set(0.5, 0.5);
craftBtn.addChild(craftLabel);
// Place craft button under inventory button
craftBtn.x = 2048 / 2;
craftBtn.y = inventoryBtn.y + 170;
game.addChild(craftBtn);
// --- Lucky Pebble & Fortune Leaf Craft Recipes ---
// Recipe definitions
var CRAFT_RECIPES = [{
name: "Lucky Pebble",
asset: "craft_luckypebble",
ingredients: [{
id: 15,
name: "Spark",
count: 1
},
// Spark
{
id: 7,
name: "Sand",
count: 1
} // Sand
],
effect: function effect() {
// Permanently give 2x luck multiplier
if (!storage.luckyPebbleCrafted) {
storage.luckyPebbleCrafted = true;
levelLuckMultiplier *= 2;
// Flash level bar to indicate permanent luck
LK.effects.flashObject(levelBarFg, 0xffe066, 1200);
levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier);
}
}
}, {
name: "Fortune Leaf",
asset: "craft_fortuneleaf",
ingredients: [{
id: 5,
name: "Moss",
count: 1
}, {
id: 3,
name: "Breeze",
count: 1
}],
effect: function effect() {
// Permanently give 3x luck multiplier
if (!storage.fortuneLeafCrafted) {
storage.fortuneLeafCrafted = true;
levelLuckMultiplier *= 3;
// Flash level bar to indicate permanent luck
LK.effects.flashObject(levelBarFg, 0x44ff66, 1200);
levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier);
}
}
}];
// Craft overlay UI
var craftOverlay = new Container();
craftOverlay.visible = false;
game.addChild(craftOverlay);
// Craft recipe icon, name, and button
var craftRecipeIcon = null;
var craftRecipeName = null;
var craftRecipeBtn = null;
var craftRecipeBtnLabel = null;
var craftRecipeMsg = null;
// Show craft overlay for selected recipe (default to first)
function showCraftOverlay(selectedRecipeIdx) {
// Remove previous children
while (craftOverlay.children.length) craftOverlay.removeChild(craftOverlay.children[0]);
// If not specified, show first recipe
var recipeIdx = typeof selectedRecipeIdx === "number" ? selectedRecipeIdx : 0;
var recipe = CRAFT_RECIPES[recipeIdx];
// Icon
craftRecipeIcon = LK.getAsset(recipe.asset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
craftRecipeIcon.x = 2048 / 2;
craftRecipeIcon.y = 700;
craftOverlay.addChild(craftRecipeIcon);
// Name
craftRecipeName = new Text2(recipe.name, {
size: 80,
fill: "#fff"
});
craftRecipeName.anchor.set(0.5, 0);
craftRecipeName.x = 2048 / 2;
craftRecipeName.y = 900;
craftOverlay.addChild(craftRecipeName);
// Ingredients
var ingText = "Requires: ";
for (var i = 0; i < recipe.ingredients.length; ++i) {
var ing = recipe.ingredients[i];
ingText += ing.name + (i < recipe.ingredients.length - 1 ? " + " : "");
}
var craftRecipeIng = new Text2(ingText, {
size: 48,
fill: "#fff"
});
craftRecipeIng.anchor.set(0.5, 0);
craftRecipeIng.x = 2048 / 2;
craftRecipeIng.y = 1000;
craftOverlay.addChild(craftRecipeIng);
// Permanent effect info
var effectText = "";
if (recipeIdx === 0) {
effectText = "Permanently gives 2x luck multiplier!";
} else if (recipeIdx === 1) {
effectText = "Permanently gives 3x luck multiplier!";
}
var craftRecipeEffect = new Text2(effectText, {
size: 48,
fill: 0xFFE066
});
craftRecipeEffect.anchor.set(0.5, 0);
craftRecipeEffect.x = 2048 / 2;
craftRecipeEffect.y = 1080;
craftOverlay.addChild(craftRecipeEffect);
// Craft button
craftRecipeBtn = new Container();
var btnBg = craftRecipeBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
btnBg.alpha = 1;
craftRecipeBtnLabel = new Text2("CRAFT", {
size: 48,
fill: "#fff"
});
craftRecipeBtnLabel.anchor.set(0.5, 0.5);
craftRecipeBtn.addChild(craftRecipeBtnLabel);
craftRecipeBtn.x = 2048 / 2;
craftRecipeBtn.y = 1200;
craftOverlay.addChild(craftRecipeBtn);
// Message text
craftRecipeMsg = new Text2("", {
size: 44,
fill: "#fff"
});
craftRecipeMsg.anchor.set(0.5, 0);
craftRecipeMsg.x = 2048 / 2;
craftRecipeMsg.y = 1300;
craftOverlay.addChild(craftRecipeMsg);
// Back button
var craftBackBtn = new Container();
var craftBackBg = craftBackBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
craftBackBg.alpha = 1;
var craftBackLabel = new Text2("BACK", {
size: 48,
fill: "#fff"
});
craftBackLabel.anchor.set(0.5, 0.5);
craftBackBtn.addChild(craftBackLabel);
craftBackBtn.x = 2048 / 2;
craftBackBtn.y = 2500;
craftOverlay.addChild(craftBackBtn);
// If there are multiple recipes, add a toggle button to switch between them
if (CRAFT_RECIPES.length > 1) {
var toggleBtn = new Container();
var toggleBg = toggleBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
toggleBg.alpha = 1;
var toggleLabel = new Text2(recipeIdx === 0 ? "NEXT" : "PREV", {
size: 36,
fill: "#fff"
});
toggleLabel.anchor.set(0.5, 0.5);
toggleBtn.addChild(toggleLabel);
toggleBtn.x = 2048 / 2 + 350;
toggleBtn.y = 900;
craftOverlay.addChild(toggleBtn);
toggleBtn.down = function (x, y, obj) {
toggleBg.scaleX = 0.95;
toggleBg.scaleY = 0.95;
};
toggleBtn.up = function (x, y, obj) {
toggleBg.scaleX = 1;
toggleBg.scaleY = 1;
// Toggle between recipes
showCraftOverlay(recipeIdx === 0 ? 1 : 0);
};
}
// Craft button handlers
craftRecipeBtn.down = function (x, y, obj) {
btnBg.scaleX = 0.95;
btnBg.scaleY = 0.95;
};
craftRecipeBtn.up = function (x, y, obj) {
btnBg.scaleX = 1;
btnBg.scaleY = 1;
// Check if already crafted
if (recipeIdx === 0 && storage.luckyPebbleCrafted || recipeIdx === 1 && storage.fortuneLeafCrafted) {
craftRecipeMsg.setText("Already crafted!");
return;
}
// Check inventory for ingredients
var canCraft = true;
for (var i = 0; i < recipe.ingredients.length; ++i) {
var ing = recipe.ingredients[i];
var found = false;
for (var j = 0; j < inventory.length; ++j) {
if (inventory[j].id === ing.id && inventory[j].count >= ing.count) {
found = true;
break;
}
}
if (!found) {
canCraft = false;
break;
}
}
if (!canCraft) {
craftRecipeMsg.setText("Missing ingredients!");
return;
}
// Remove ingredients from inventory
for (var i = 0; i < recipe.ingredients.length; ++i) {
var ing = recipe.ingredients[i];
for (var j = 0; j < inventory.length; ++j) {
if (inventory[j].id === ing.id) {
inventory[j].count -= ing.count;
if (inventory[j].count <= 0) {
inventory.splice(j, 1);
}
break;
}
}
}
// Apply effect
recipe.effect();
if (recipeIdx === 0) {
craftRecipeMsg.setText("Crafted! 2x luck multiplier unlocked!");
} else if (recipeIdx === 1) {
craftRecipeMsg.setText("Crafted! 3x luck multiplier unlocked!");
}
};
// Back button handlers
craftBackBtn.down = function (x, y, obj) {
craftBackBg.scaleX = 0.95;
craftBackBg.scaleY = 0.95;
};
craftBackBtn.up = function (x, y, obj) {
craftBackBg.scaleX = 1;
craftBackBg.scaleY = 1;
craftOverlay.visible = false;
// Restore main UI elements
titleTxt.visible = true;
rarityTxt.visible = true;
itemDisplay.visible = true;
itemNameTxt.visible = true;
rollBtn.visible = true;
showCollBtn.visible = true;
inventoryBtn.visible = true;
craftBtn.visible = true;
};
craftOverlay.visible = true;
// Hide main UI elements
titleTxt.visible = false;
rarityTxt.visible = false;
itemDisplay.visible = false;
itemNameTxt.visible = false;
rollBtn.visible = false;
showCollBtn.visible = false;
inventoryBtn.visible = false;
craftBtn.visible = false;
}
// Craft button handlers
craftBtn.down = function (x, y, obj) {
craftBg.scaleX = 0.95;
craftBg.scaleY = 0.95;
};
craftBtn.up = function (x, y, obj) {
craftBg.scaleX = 1;
craftBg.scaleY = 1;
if (rolling || showCollection || inventoryOverlay.visible) return;
showCraftOverlay();
};
// On load, if Lucky Pebble was crafted, apply effect
if (storage.luckyPebbleCrafted) {
levelLuckMultiplier *= 2;
}
// On load, if Fortune Leaf was crafted, apply effect
if (storage.fortuneLeafCrafted) {
levelLuckMultiplier *= 3;
}
// Collection view overlay
var collectionOverlay = new Container();
collectionOverlay.visible = false;
game.addChild(collectionOverlay);
// "Back" button for collection
var backBtn = new Container();
var backBg = backBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
backBg.alpha = 1;
var backLabel = new Text2("BACK", {
size: 48,
fill: "#fff"
});
backLabel.anchor.set(0.5, 0.5);
backBtn.addChild(backLabel);
backBtn.x = 2048 / 2;
backBtn.y = 2500;
collectionOverlay.addChild(backBtn);
// Collection grid
var collectionIcons = [];
function updateCollectionGrid() {
// Remove old
for (var i = 0; i < collectionIcons.length; ++i) collectionOverlay.removeChild(collectionIcons[i]);
collectionIcons = [];
// Layout: 3 per row, centered
var perRow = 3;
var spacing = 320;
var startX = 2048 / 2 - spacing;
var startY = 600;
for (var i = 0; i < ITEM_DEFS.length; ++i) {
var item = ITEM_DEFS[i];
var owned = collection.indexOf(item.id) !== -1;
var icon = new CollectionIcon();
icon.setItem(item, owned);
icon.x = startX + i % perRow * spacing;
icon.y = startY + Math.floor(i / perRow) * spacing;
// Name and rarity
var nameTxt = new Text2(item.name, {
size: 36,
fill: "#fff"
});
nameTxt.anchor.set(0.5, 0);
nameTxt.x = 0;
nameTxt.y = 90;
icon.addChild(nameTxt);
var rareTxt = new Text2("1 in " + item.rarity, {
size: 28,
fill: "#aaa"
});
rareTxt.anchor.set(0.5, 0);
rareTxt.x = 0;
rareTxt.y = 130;
icon.addChild(rareTxt);
collectionOverlay.addChild(icon);
collectionIcons.push(icon);
}
}
// --- Functions ---
function updateMainDisplay() {
// Show last rolled item or first item
var itemId = lastRollResult !== null ? lastRollResult : 0;
var itemDef = getItemDef(itemId);
itemDisplay.setItem(itemDef);
// Show name and rarity
rarityTxt.setText(itemDef.name + " • 1 in " + itemDef.rarity);
// Show item name below the item display
itemNameTxt.setText(itemDef.name);
}
function rollForItem() {
// Weighted random roll, apply luck event and level multipliers
var totalWeight = 0;
for (var i = 0; i < ITEM_DEFS.length; ++i) {
var rarity = ITEM_DEFS[i].rarity;
// Apply multiplier: only to rarest item (lowest odds, highest rarity)
var effectiveRarity = rarity;
if (i === ITEM_DEFS.length - 1) {
effectiveRarity = Math.max(1, Math.floor(rarity / (levelLuckMultiplier * luckEventMultiplier)));
}
totalWeight += 1 / effectiveRarity;
}
var r = Math.random() * totalWeight;
var acc = 0;
for (var i = 0; i < ITEM_DEFS.length; ++i) {
var rarity = ITEM_DEFS[i].rarity;
var effectiveRarity = rarity;
if (i === ITEM_DEFS.length - 1) {
effectiveRarity = Math.max(1, Math.floor(rarity / (levelLuckMultiplier * luckEventMultiplier)));
}
acc += 1 / effectiveRarity;
if (r <= acc) return ITEM_DEFS[i].id;
}
return ITEM_DEFS[ITEM_DEFS.length - 1].id;
}
function animateRoll(resultId) {
rolling = true;
// Flash through random items, then land on result
var flashes = 12;
var flashTime = 60;
var i = 0;
function flashNext() {
if (i < flashes) {
var fakeId = Math.floor(Math.random() * ITEM_DEFS.length);
itemDisplay.setItem(getItemDef(fakeId));
++i;
LK.setTimeout(flashNext, flashTime);
} else {
// Land on result
itemDisplay.setItem(getItemDef(resultId));
// Animate scale
itemDisplay.scale.set(1.2, 1.2);
tween(itemDisplay, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
rolling = false;
updateMainDisplay();
}
}
flashNext();
}
// --- Event Handlers ---
// Roll button
rollBtn.down = function (x, y, obj) {
if (rolling || showCollection || rollCooldown) return;
// Call the original RollButton.down (the class method), not this event handler
if (typeof RollButton.prototype.down === "function") {
RollButton.prototype.down.call(rollBtn, x, y, obj);
}
};
rollBtn.up = function (x, y, obj) {
if (rolling || showCollection || rollCooldown) return;
// Start cooldown
rollCooldown = true;
if (rollCooldownTimeout) {
LK.clearTimeout(rollCooldownTimeout);
}
rollBtn.alpha = 0.5;
rollCooldownTimeout = LK.setTimeout(function () {
rollCooldown = false;
rollBtn.alpha = 1;
rollCooldownTimeout = null;
}, 2000);
// Do roll
var resultId = rollForItem();
lastRollResult = resultId;
// Add to collection if new
if (collection.indexOf(resultId) === -1) {
collection.push(resultId);
storage.collection = collection;
}
// Add all rolled items to inventory, accumulate up to 64 per item
var itemDef = getItemDef(resultId);
if (itemDef) {
// Find if item already exists in inventory
var found = false;
for (var i = 0; i < inventory.length; ++i) {
if (inventory[i].id === resultId) {
if (inventory[i].count < 64) {
inventory[i].count += 1;
}
found = true;
break;
}
}
if (!found) {
inventory.push({
id: resultId,
count: 1
});
}
}
animateRoll(resultId);
updateMainDisplay();
// --- Level logic ---
rollsTotal += 1;
rollsThisLevel += 1;
var leveledUp = false;
if (rollsThisLevel >= 50) {
level += 1;
rollsThisLevel = 0;
leveledUp = true;
// Increase level-based luck multiplier
levelLuckMultiplier += 1;
// Flash level bar to indicate level up
LK.effects.flashObject(levelBarFg, 0xffe066, 800);
}
// Update level bar UI
var frac = Math.min(1, rollsThisLevel / 50);
levelBarFg.width = 1200 * frac;
levelBarLabel.setText("Level " + level + " (" + rollsThisLevel + "/50) Luck x" + levelLuckMultiplier * luckEventMultiplier);
// Call the original RollButton.up (the class method), not this event handler
if (typeof RollButton.prototype.up === "function") {
RollButton.prototype.up.call(rollBtn, x, y, obj);
}
};
// Show collection
showCollBtn.down = function (x, y, obj) {
if (rolling) return;
showCollBg.scaleX = 0.95;
showCollBg.scaleY = 0.95;
};
showCollBtn.up = function (x, y, obj) {
if (rolling) return;
showCollBg.scaleX = 1;
showCollBg.scaleY = 1;
showCollection = true;
collectionOverlay.visible = true;
updateCollectionGrid();
// Hide main UI elements when collection is open
titleTxt.visible = false;
rarityTxt.visible = false;
itemDisplay.visible = false;
itemNameTxt.visible = false;
rollBtn.visible = false;
showCollBtn.visible = false;
inventoryBtn.visible = false;
craftBtn.visible = false;
};
// Back from collection
backBtn.down = function (x, y, obj) {
backBg.scaleX = 0.95;
backBg.scaleY = 0.95;
};
backBtn.up = function (x, y, obj) {
backBg.scaleX = 1;
backBg.scaleY = 1;
showCollection = false;
collectionOverlay.visible = false;
// Restore main UI elements when leaving collection
titleTxt.visible = true;
rarityTxt.visible = true;
itemDisplay.visible = true;
itemNameTxt.visible = true;
rollBtn.visible = true;
showCollBtn.visible = true;
inventoryBtn.visible = true;
craftBtn.visible = true;
};
// --- Inventory Overlay ---
var inventoryOverlay = new Container();
inventoryOverlay.visible = false;
game.addChild(inventoryOverlay);
// "Back" button for inventory
var invBackBtn = new Container();
var invBackBg = invBackBtn.attachAsset('roll_btn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
invBackBg.alpha = 1;
var invBackLabel = new Text2("BACK", {
size: 48,
fill: "#fff"
});
invBackLabel.anchor.set(0.5, 0.5);
invBackBtn.addChild(invBackLabel);
invBackBtn.x = 2048 / 2;
invBackBtn.y = 2500;
inventoryOverlay.addChild(invBackBtn);
// Inventory grid
var inventoryIcons = [];
function updateInventoryGrid() {
// Remove old
for (var i = 0; i < inventoryIcons.length; ++i) inventoryOverlay.removeChild(inventoryIcons[i]);
inventoryIcons = [];
// Layout: 3 per row, centered
var perRow = 3;
var spacing = 320;
var startX = 2048 / 2 - spacing;
var startY = 600;
for (var i = 0; i < inventory.length; ++i) {
var invEntry = inventory[i];
var itemId = invEntry.id;
var itemCount = invEntry.count;
var item = getItemDef(itemId);
if (!item) continue;
var icon = new CollectionIcon();
icon.setItem(item, true);
icon.x = startX + i % perRow * spacing;
icon.y = startY + Math.floor(i / perRow) * spacing;
// Name and rarity
var nameTxt = new Text2(item.name, {
size: 36,
fill: "#fff"
});
nameTxt.anchor.set(0.5, 0);
nameTxt.x = 0;
nameTxt.y = 90;
icon.addChild(nameTxt);
var rareTxt = new Text2("1 in " + item.rarity, {
size: 28,
fill: "#aaa"
});
rareTxt.anchor.set(0.5, 0);
rareTxt.x = 0;
rareTxt.y = 130;
icon.addChild(rareTxt);
// Show count if more than 1
if (itemCount > 1) {
var countTxt = new Text2("x" + itemCount, {
size: 40,
fill: 0xFFE066
});
countTxt.anchor.set(1, 1);
countTxt.x = 90;
countTxt.y = 90;
icon.addChild(countTxt);
}
inventoryOverlay.addChild(icon);
inventoryIcons.push(icon);
}
}
// Inventory button handlers
inventoryBtn.down = function (x, y, obj) {
inventoryBg.scaleX = 0.95;
inventoryBg.scaleY = 0.95;
};
inventoryBtn.up = function (x, y, obj) {
inventoryBg.scaleX = 1;
inventoryBg.scaleY = 1;
if (rolling) return;
showCollection = false;
collectionOverlay.visible = false;
inventoryOverlay.visible = true;
updateInventoryGrid();
// Hide main UI elements when inventory is open
titleTxt.visible = false;
rarityTxt.visible = false;
itemDisplay.visible = false;
itemNameTxt.visible = false;
rollBtn.visible = false;
showCollBtn.visible = false;
inventoryBtn.visible = false;
craftBtn.visible = false;
};
// Inventory back button handlers
invBackBtn.down = function (x, y, obj) {
invBackBg.scaleX = 0.95;
invBackBg.scaleY = 0.95;
};
invBackBtn.up = function (x, y, obj) {
invBackBg.scaleX = 1;
invBackBg.scaleY = 1;
inventoryOverlay.visible = false;
// Restore main UI elements when leaving inventory
titleTxt.visible = true;
rarityTxt.visible = true;
itemDisplay.visible = true;
itemNameTxt.visible = true;
rollBtn.visible = true;
showCollBtn.visible = true;
inventoryBtn.visible = true;
craftBtn.visible = true;
};
// --- Initial State ---
updateMainDisplay();
collectionOverlay.visible = false;
// --- Level bar initial state ---
rollsThisLevel = 0;
level = 1;
levelLuckMultiplier = 1;
levelBarFg.width = 0;
levelBarLabel.setText("Level 1 (0/50) Luck x1");
// --- GUI Layout ---
titleTxt.x = 2048 / 2;
titleTxt.y = 40;
rarityTxt.x = 2048 / 2;
rarityTxt.y = 200;
// --- Touch Handling (prevent drag) ---
game.move = function (x, y, obj) {};
game.down = function (x, y, obj) {};
game.up = function (x, y, obj) {};
// --- Game Update ---
game.update = function () {
// Update luck event timer bar if active
if (luckEventActive && luckEventTimerBar && luckEventTimerBar.visible) {
var now = Date.now();
var total = 20000;
var left = Math.max(0, luckEventEndTime - now);
var frac = left / total;
luckEventTimerBar.fg.width = 600 * frac;
if (left <= 0) {
endLuckEvent();
}
}
};
Water. In-Game asset. 2d. High contrast. No shadows
Stone. In-Game asset. 2d. High contrast. No shadows
Flame. In-Game asset. 2d. High contrast. No shadows
Breeze. In-Game asset. 2d. High contrast. No shadows
Ember. In-Game asset. 2d. High contrast. No shadows
light green moss. In-Game asset. 2d. High contrast. No shadows
Crystal. In-Game asset. 2d. High contrast. No shadows
sand. In-Game asset. 2d. High contrast. No shadows
Gray ash. In-Game asset. 2d. High contrast. No shadows
Iron bar. In-Game asset. 2d. High contrast. No shadows
Rectangular button. In-Game asset. 2d. High contrast. No shadows
Quartz. In-Game asset. 2d. High contrast. No shadows
smoke. In-Game asset. 2d. High contrast. No shadows
Frozen human. In-Game asset. 2d. High contrast. No shadows
Thunder. In-Game asset. 2d. High contrast. No shadows
Vine. In-Game asset. 2d. High contrast. No shadows
spark. In-Game asset. 2d. High contrast. No shadows
lucky pebble. In-Game asset. 2d. High contrast. No shadows
Fortune Leaf. In-Game asset. 2d. High contrast. No shadows