User prompt
Make font colors white
User prompt
After rolling, print the name of the item and the chance rate on the screen.
User prompt
Change the item names to Water, Stone, Flame, Breeze and Amber
User prompt
Change "Blue Box" name a "Water"
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'rollBtn.down(x, y, obj);' Line Number: 358
Code edit (1 edits merged)
Please save this source code
User prompt
Loot of Odds: The RNG Collection
Initial prompt
: I want to create a random loot-based RNG game. The game contains a growing list of unique items, each with a specific rarity. Rarities are defined as 1 in X chances (e.g., 1/2, 1/4, 1/1000, etc.), and the odds vary non-linearly. Some rarities are close, while others have large gaps. The items are labeled with fitting names that reflect their rarity (e.g., "Water" is common, while "Eternity" is extremely rare). There are currently 50 items, each with a rarity value. More items will be added in the future, either between existing items or at the end. When a new item is added between two existing ones, the others are pushed down but their internal numbers remain stable (i.e., item #23 stays #23 even if a new item is inserted before it and becomes #24). The rarity values are not always neat powers of two or rounded numbers; some values are arbitrary like 1/384000 or 1/12999999 to make the system feel more organic. I want you to simulate or help code this system. The structure should allow: Random rolls that select items based on their defined rarity Easy insertion of new items without breaking existing IDs or logic Optional: output of the rolled item and its rarity
/**** * 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