User prompt
Right now you’re showing "Drift Limb: HINT TEXT". Instead, we’ll just show the hint: // OLD: patientDiv.innerText = `${illness.name}: ${illness.hint}`; // NEW: patientDiv.innerText = illness.hint; patientDiv.style.color = "black"; // also fixes the black text request . Fix Hint Text Overflow If the hint is too long and runs off screen, wrap it inside a scrollable box. For example: #patientHint { max-width: 400px; /* keep within screen */ max-height: 120px; /* limit vertical space */ overflow-y: auto; /* add scroll if too long */ padding: 8px; background: #fff; border-radius: 12px; color: black; font-size: 14px; } // wrap text inside the div patientDiv.innerHTML = `
User prompt
Remove elements object entirely. We’ll replace it with teaBases. Define Tea Bases: const teaBases = { Wind: { name: "Uplifting Tea Base", element: "Wind" }, Earth: { name: "Earthy Tea Base", element: "Earth" }, Water: { name: "Cooling Tea Base", element: "Water" }, Fire: { name: "Spicy Tea Base", element: "Fire" } }; Update Herbs list so each herb has an element property: const herbs = { demetersArrow: { name: "Demeter’s Arrow", element: "Wind" }, fuzzyPeeper: { name: "Fuzzy Peeper", element: "Wind" }, kittyMitten: { name: "Kitty Mitten", element: "Fire" }, devilsLantern: { name: "Devil’s Lantern", element: "Fire" }, // etc. for Earth + Water herbs }; Crafting rule: Check: first item = Tea Base Next 2 items = herbs of the same element as that base If match → valid tea cures illness. function brewTea(base, herb1, herb2) { if (herb1.element === base.element && herb2.element === base.element && herb1 !== herb2) { return { name: `${base.name} + ${herb1.name} + ${herb2.name}`, element: base.element }; } return null; // invalid tea } llness definitions: Each illness should be linked to an element, not a specific fixed recipe. const illnesses = { driftLimb: { name: "Drift Limb", element: "Wind", hint: "I’ve been working so hard lately, my arms feel so heavy..." }, // Fire, Earth, Water illnesses... }; Checking cures: If brewedTea.element === illness.element → cured ✅
User prompt
We’ll add a Herbalist’s Journal object (or just a JS data structure for now), and give it one illness entry (Feverish Glow) plus the herbs you’ve already got. Then we can render that in a simple scrollable popup. Here’s a working setup you can drop into your existing code:
“A heat that rises from the soles like creeping ivy, leaving the brow slick and the mind hazy.”
Causes: Overexertion, getting caught in the rain, too many fire herbs.
Symptoms (what patients might say):
Cure: Brew Water-aligned tea with River Leek or Frogwick.
User prompt
2. How the cure works within the matrix Each illness is secretly defined by imbalances in your existing system. Example with Feverish Glow: Causes: Overexertion, rain exposure, or too many fire herbs. Hidden State: Body = “overheated / too much fire element.” Cure Rule: Counter fire → prescribe water element + a cooling herb. So in your data: { "id": "feverish_glow", "description": "A heat that rises from the soles...", "symptoms": [ "I’ve been working late into the night, and now I can’t stop sweating.", "Is it hot in here?", "Have you ever had a Devil’s Lantern?..." ], "element_imbalance": "fire", "cure": { "required_element": "water", "preferred_herbs": ["moonleaf", "dewpetal", "cooling_mint"], "base": "any" // flexible, unless you want special restrictions } }
User prompt
// ✅ 2) Add "behind counter" graphic let behindCounter = scene.add.image(scene.cameras.main.centerX, 500, "behindCounter"); behindCounter.setDepth(1); // Assume your main counter graphic is already added counter.setDepth(2); cauldron.setDepth(3); // ✅ 3) Reposition cauldron // Move it lower so it looks like it sits ON the counter cauldron.setPosition(scene.cameras.main.centerX, 520); cauldron.setDepth(3); // Ensure it's above the counter // ✅ 4) Remove Shop UI if (shopUI) { shopUI.destroy(true); // remove container + children shopUI = null; } // ✅ 5) Make herbs box text white let herbsLabel = scene.add.text(50, 100, "Herbs", { fontSize: "26px", color: "#ffffff", // White text fontStyle: "bold" });
User prompt
5) Reliable “tap for description” vs “drag to brew” + visible drag ghost Replace the popup part of your input handlers with the following. (Leave the “regular inventory” branch as-is.) // === Tap vs Drag state === var downItemCandidate = null; var downStartX = 0, downStartY = 0; var DRAG_THRESHOLD = 22; // px movement to start dragging var dragGhost = null; // helper: rectangular hit test function hitRect(x, y, cx, cy, halfW, halfH) { return Math.abs(x - cx) <= halfW && Math.abs(y - cy) <= halfH; } function createDragGhostFromMenu(name, color, cost, startX, startY) { var ghost = new Container(); ghost.x = startX; ghost.y = startY; game.addChild(ghost); var bg = ghost.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, tint: color, width: 220, height: 100 }); var txt = new Text2(name, { size: 44, fill: 0xFFFFFF }); txt.anchor.set(0.5,0.5); txt.x = 0; txt.y = 0; ghost.addChild(txt); ghost.name = name; ghost.isDragging = true; ghost.isMenuPurchase = true; ghost.cost = cost; ghost.dragOffsetX = 0; ghost.dragOffsetY = 0; // subtle pop so it feels grabbed tween(ghost, { scaleX: 1.08, scaleY: 1.08 }, { duration: 120 }); return ghost; } game.down = function (x, y, obj) { // If popup visible, prefer its items if (popupMenu.isVisible) { downItemCandidate = null; for (var i = popupMenu.menuItems.length - 1; i >= 0; i--) { var it = popupMenu.menuItems[i]; // rectangular hit for reliability var hw = (it.hitW || 120) / 2; var hh = (it.hitH || 120) / 2; if (hitRect(x, y, it.x, it.y, hw, hh)) { downItemCandidate = it; break; } } downStartX = x; downStartY = y; return; // wait to see if this becomes a tap or a drag } // Regular inventory items (unchanged) for (var i = inventoryItems.length - 1; i >= 0; i--) { var item = inventoryItems[i]; var dx = x - item.x, dy = y - item.y; var distance = Math.sqrt(dx*dx + dy*dy); if (distance < 50) { heldItem = item; item.isDragging = true; item.dragOffsetX = x - item.x; item.dragOffsetY = y - item.y; break; } } }; game.move = function (x, y, obj) { // If we have a candidate from popup and movement crosses threshold, start drag with a visible ghost if (downItemCandidate && !heldItem) { var moved = Math.sqrt((x - downStartX)*(x - downStartX) + (y - downStartY)*(y - downStartY)); if (moved > DRAG_THRESHOLD) { // check funds before allowing drag if (goldAmount >= downItemCandidate.cost) { var tint = downItemCandidate.children[0] ? downItemCandidate.children[0].tint : 0x1565C0; heldItem = dragGhost = createDragGhostFromMenu(downItemCandidate.name, tint, downItemCandidate.cost, x, y); heldItem.dragOffsetX = 0; heldItem.dragOffsetY = 0; } else { patientStatusText.setText('Not enough gold! Need ' + downItemCandidate.cost + ' gold.'); downItemCandidate = null; } } } // Move the active drag item (ghost or regular) if (heldItem && heldItem.isDragging) { heldItem.x = x - (heldItem.dragOffsetX || 0); heldItem.y = y - (heldItem.dragOffsetY || 0); } }; game.up = function (x, y, obj) { // TAP on popup item -> show details (no drag started) if (downItemCandidate && !heldItem) { popupMenu.showDetails(downItemCandidate.name); downItemCandidate = null; return; } downItemCandidate = null; // DROP logic (ghost from menu or regular inventory) if (heldItem) { heldItem.isDragging = false; // Calculate distance to cauldron var dx = heldItem.x - cauldron.x; var dy = heldItem.y - cauldron.y; var distance = Math.sqrt(dx*dx + dy*dy); // Menu purchase ghost dropped on cauldron if (heldItem.isMenuPurchase) { if (distance < 200 && cauldron.ingredients.length < 3) { goldAmount -= heldItem.cost; goldText.setText('Gold: ' + goldAmount); cauldron.ingredients.push(heldItem.name); updateCauldronDisplay(); tween(cauldron, { tint: 0x44FF44 }, { duration: 200, onFinish: function() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); }}); // remove ghost, keep popup open for multiple adds game.removeChild(heldItem); } else { // Not dropped in valid area -> cancel purchase, remove ghost game.removeChild(heldItem); } heldItem = null; dragGhost = null; return; } // Regular inventory item drop (unchanged) if (distance < 200 && cauldron.ingredients.length < 3) { cauldron.ingredients.push(heldItem.name); updateCauldronDisplay(); tween(cauldron, { tint: 0x44FF44 }, { duration: 200, onFinish: function() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); }}); var itemIndex = inventoryItems.indexOf(heldItem); if (itemIndex > -1) { game.removeChild(heldItem); inventoryItems.splice(itemIndex, 1); } } else { tween(cauldron, { tint: 0xFF4444 }, { duration: 200, onFinish: function() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); }}); resetItem(heldItem); } heldItem = null; } }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
4) Make menu items big, readable, and easy to hit (replace your createInventoryItem) function createInventoryItem(name, x, y, color, isMenuItem) { var container = new Container(); container.x = x; container.y = y; game.addChild(container); // Use big rounded rectangles for menu items; bottles for inventory var assetType = isMenuItem ? 'craftButton' : 'potionBottle'; var item = container.attachAsset(assetType, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, tint: color }); if (isMenuItem) { item.width = 220; item.height = 100; } else { // inventory representation can stay as-is } var label = new Text2(name, { size: isMenuItem ? 44 : 24, // 👈 bigger menu text fill: 0xFFFFFF }); label.anchor.set(0.5, 0.5); label.x = 0; label.y = 0; container.addChild(label); // attach metadata (if this is a herb, we have more info) container.name = name; container.meta = HERB_DATA[name] || null; container.draggable = true; container.type = 'ingredient'; container.isDragging = false; container.originalX = x; container.originalY = y; // for rectangular hit test container.hitW = isMenuItem ? item.width : 120; container.hitH = isMenuItem ? item.height : 120; return container; }
User prompt
let’s make the herb-description popup reliable on tap and make all the text much larger, plus give you a visible drag ghost so the player can clearly see what they’re holding. Below are surgical code edits you can paste into your current file. I’m only touching the parts needed: /**** * Herb Data & Helpers ****/ var HERB_DATA = { "Sheep Bane": { type:"Plant", alignment:"Earth", description:"A nourishing plant with small white flowers found in lush meadows with plenty of sunshine." }, "Fuzzy Peeper": { type:"Plant", alignment:"Wind", description:"A pale puffball perched on a long stem; it turns gently to face any breeze, no matter how faint." }, "Sommeral": { type:"Plant", alignment:"Fire", description:"A spiny red blossom that thrives in sun-scorched fields; its petals crackle faintly when crushed." }, "Common Snupe": { type:"Plant", alignment:"Earth", description:"A squat, stubby herb with ridged leaves and a sharp nutty scent—grows best near compost heaps and pig pens." }, "Geneva’s Dewdrop": { type:"Plant", alignment:"Water", description:"Delicate blue bulbs that drip a perpetual cool dew; said to be first planted by a weeping maiden." }, "Brindleroot": { type:"Plant", alignment:"Fire", description:"A twisted root that glows faintly from within—often brewed to stoke warmth into cold limbs." }, "Frogwick": { type:"Plant", alignment:"Water", description:"Rubbery, semi-aquatic herb found near marshes—tastes awful but soothes the throat like magic." }, "Demeter’s Arrow": { type:"Plant", alignment:"Wind", description:"Thin silvery leaves that grow in spirals. When burned, they produce a vapor that clears the lungs and bowels." }, "River Leek": { type:"Plant", alignment:"Water", description:"A long, fragrant stalk that grows in riverside soil; its boiled leaves soothe fevers and dampen restlessness." }, "Pipeweed": { type:"Plant", alignment:"Wind", description:"Fragrant and slightly numbing, this herb is often smoked by hill witches and daydreaming farmhands." }, "Kitty Mitten": { type:"Plant", alignment:"Fire", description:"Soft red-orange fuzz covers these leaves, which are pleasantly warm to the touch. Cats love to nap on them." }, "Malicious Bule": { type:"Fungus", alignment:"Wind", description:"A pale fungus with twisting gills and a sweet, spoiled smell. It’s said to whisper when the wind passes through a grove." }, "False Dunny Cap": { type:"Fungus", alignment:"Earth", description:"Often mistaken for its edible cousin, this fungus has a thick gray stalk and a damp cellar scent. Caution advised." }, "Callum’s Perch": { type:"Fungus", alignment:"Earth", description:"Named for the druid who first brewed it into a sleep tonic, this squat, mossy mushroom grows on dead stumps in shadow." }, "Pinwort": { type:"Fungus", alignment:"Water", description:"Soft and glossy with lilac-speckled caps, these fungi flourish in damp caves and give off a faint minty aroma." }, "Devil’s Lantern": { type:"Fungus", alignment:"Fire", description:"This bioluminescent red fungus pulses with warmth. It grows in deep caverns and is rumored to attract fire spirits." }, "Lady’s Teacup": { type:"Fungus", alignment:"Water", description:"Shaped like a delicate bowl, often found filled with morning dew. Some fae insist it must only be harvested under moonlight." }, "Dead Man’s Toe": { type:"Fungus", alignment:"Earth", description:"Black and bulbous, it pushes up from graveyard soil and smells of rust. Disturbing in appearance, yet grounding when dried." }, "Agarium": { type:"Fungus", alignment:"Earth", description:"Smooth and tan, with a satisfying snap when broken. This dependable fungus is a staple in many earthy decoctions." }, "Thimbliss": { type:"Fungus", alignment:"Wind", description:"Tiny, translucent caps that grow in clusters beneath bird nests. Harmless alone, but intensify the effects of other herbs." }, "Lesser Bluecap": { type:"Fungus", alignment:"Water", description:"Short and stubby with a dusty blue top, this unassuming mushroom has mild cooling properties and is common in boggy woods." } }; // ordered list for the Herbs popup var herbItems = Object.keys(HERB_DATA); function colorForAlignment(align) { if (align === "Fire") return 0xFF6B6B; if (align === "Water") return 0x6BB6FF; if (align === "Earth") return 0x8B8F6B; if (align === "Wind") return 0xBEE3F8; return 0xADFF2F; } function wrapTextByWords(str, maxLen) { var words = str.split(' '); var lines = []; var current = ''; for (var i = 0; i < words.length; i++) { var tryLine = current.length ? (current + ' ' + words[i]) : words[i]; if (tryLine.length > maxLen) { if (current.length) lines.push(current); current = words[i]; } else { current = tryLine; } } if (current.length) lines.push(current); return lines.join('\n'); }
User prompt
Step 2: Create the Popup Window This will be a floating div that appears when an herb is tapped.
User prompt
const herbs = { "Sheep Bane": { element: "Earth", description: "A nourishing plant with small white flowers found in lush meadows with plenty of sunshine.", sprite: "sheepbane.png" }, "Fuzzy Peeper": { element: "Wind", description: "A pale puffball perched on a long stem; it turns gently to face any breeze, no matter how faint.", sprite: "fuzzypeeper.png" }, // ... continue for all herbs };
User prompt
fully replace the placeholder herbs in your game. Here’s how we can integrate them cleanly: Step 1. Structure the herb data It’s best to keep all herb info in a single object/array, so you can easily pull alignment, description, etc., when the player selects one. Example: // herbs.js const herbs = [ { name: "Sheep Bane", type: "Plant", alignment: "Earth", description: "A nourishing plant with small white flowers found in lush meadows with plenty of sunshine." }, { name: "Fuzzy Peeper", type: "Plant", alignment: "Wind", description: "A pale puffball perched on a long stem; it turns gently to face any breeze, no matter how faint." }, { name: "Sommeral", type: "Plant", alignment: "Fire", description: "A spiny red blossom that thrives in sun-scorched fields; its petals crackle faintly when crushed." }, { name: "Common Snupe", type: "Plant", alignment: "Earth", description: "A squat, stubby herb with ridged leaves and a sharp nutty scent—grows best near compost heaps and pig pens." }, { name: "Geneva’s Dewdrop", type: "Plant", alignment: "Water", description: "Delicate blue bulbs that drip a perpetual cool dew; said to be first planted by a weeping maiden." }, { name: "Brindleroot", type: "Plant", alignment: "Fire", description: "A twisted root that glows faintly from within—often brewed to stoke warmth into cold limbs." }, { name: "Frogwick", type: "Plant", alignment: "Water", description: "Rubbery, semi-aquatic herb found near marshes—tastes awful but soothes the throat like magic." }, { name: "Demeter’s Arrow", type: "Plant", alignment: "Wind", description: "Thin silvery leaves that grow in spirals. When burned, they produce a vapor that clears the lungs and bowels." }, { name: "River Leek", type: "Plant", alignment: "Water", description: "A long, fragrant stalk that grows in riverside soil; its boiled leaves soothe fevers and dampen restlessness." }, { name: "Pipeweed", type: "Plant", alignment: "Wind", description: "Fragrant and slightly numbing, this herb is often smoked by hill witches and daydreaming farmhands." }, { name: "Kitty Mitten", type: "Plant", alignment: "Fire", description: "Soft red-orange fuzz covers these leaves, which are pleasantly warm to the touch. Cats love to nap on them." }, { name: "Malicious Bule", type: "Fungus", alignment: "Wind", description: "A pale fungus with twisting gills and a sweet, spoiled smell. It’s said to whisper when the wind passes through a grove." }, { name: "False Dunny Cap", type: "Fungus", alignment: "Earth", description: "Often mistaken for its edible cousin, this fungus has a thick gray stalk and a damp cellar scent. Caution advised." }, { name: "Callum’s Perch", type: "Fungus", alignment: "Earth", description: "Named for the druid who first brewed it into a sleep tonic, this squat, mossy mushroom grows on dead stumps in shadow." }, { name: "Pinwort", type: "Fungus", alignment: "Water", description: "Soft and glossy with lilac-speckled caps, these fungi flourish in damp caves and give off a faint minty aroma." }, { name: "Devil’s Lantern", type: "Fungus", alignment: "Fire", description: "This biolum
User prompt
Movie the notification status like "a new patient has arrived!" so the bottom of the screen.
User prompt
move the craft potion button so it's visible at the bottom of the screen.
User prompt
Here’s a rewritten version of your drawIngredientMenu that makes the menus look like proper pop-up windows with centered text buttons:
User prompt
apply the same centering and font logic inside the popup menu rendering function. Here’s a template fix for that part:
User prompt
Enlarging the buttons. Increasing the font size. Centering them horizontally within the brown panel. Adding some spacing so they’re not squished together. Here’s an example in JavaScript + CSS (LK.js / Canvas style) that would give you bigger, centered, more visible buttons:
User prompt
move the bases, elements, and herbs buttons down under the wood plank counter so that they dont overlap
User prompt
increase text size for ingredients for better readbility
User prompt
ChatGPT said: Got it — to make the UI less “stacked and cluttered,” we want to restructure it into three layers/containers: HUD / Top Bar (Gold, Score, Buttons) Center (Patient + Cauldron + Dialogue) Bottom (Ingredient Buttons + Craft Button) Here’s a clean way to handle this in LK.js (your setup looks a lot like p5.js / JS canvas style code):
User prompt
🔨 Plan for the Ingredient Menus Remove the bottles from the main play area → No potion bottle graphics cluttering the bottom. Ingredients won’t appear unless you open their category menu. Add three clean buttons under the cauldron BASES ELEMENTS HERBS Clicking a button opens a pop-up menu (overlay window) This window lists the available ingredients for that category. Each ingredient appears as a draggable text button (or a small icon if you want later), not potion bottle art. Example:
User prompt
✅ Step 1: Add Menu Buttons We’ll place three buttons above the counter.
User prompt
✅ Step 3: Generate Ingredient Items inside the Window We’ll reuse createInventoryItem, but change it to use simple colored boxes instead of potion bottles.
User prompt
✅ Step 2: Create a Pop-up Menu Window This is a box that appears when a button is pressed, with draggable ingredient items inside.
User prompt
✅ Step 1: Add Menu Buttons We’ll place three buttons above the counter.
User prompt
✅ 2. Potion bottle only appears after all 3 ingredients are added Right now, the PotionBottle is always on screen (game.addChild(new PotionBottle())). We need to: Start hidden (visible = false). Show it only when cauldron.ingredients.length === 3. Code Edit (PotionBottle instance):
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Patient = Container.expand(function () { var self = Container.call(this); var patientBody = self.attachAsset('patient', { anchorX: 0.5, anchorY: 0.5 }); // symptomGlow moved to main game - reference will be set externally self.symptomGlow = null; self.symptomType = ''; self.requiredBase = ''; self.requiredElement = ''; self.requiredHerb = ''; self.goldReward = 0; self.setSymptom = function (type) { self.symptomType = type; if (type === 'redrose') { if (self.symptomGlow) self.symptomGlow.tint = 0xFF4444; self.requiredBase = 'infusion'; self.requiredElement = 'water'; self.requiredHerb = 'moonpetal'; self.goldReward = 100; } else if (type === 'bluemist') { if (self.symptomGlow) self.symptomGlow.tint = 0x4444FF; self.requiredBase = 'broth'; self.requiredElement = 'fire'; self.requiredHerb = 'firesilk'; self.goldReward = 120; } else if (type === 'emeraldveil') { if (self.symptomGlow) self.symptomGlow.tint = 0x44FF44; self.requiredBase = 'infusion'; self.requiredElement = 'earth'; self.requiredHerb = 'shadowcaps'; self.goldReward = 150; } }; self.reactToPotion = function (base, element, herb) { var correctCount = 0; if (base === self.requiredBase) correctCount++; if (element === self.requiredElement) correctCount++; if (herb === self.requiredHerb) correctCount++; if (correctCount === 3) { tween(patientBody, { tint: 0x90EE90 }, { duration: 1000 }); LK.getSound('success').play(); return self.goldReward; } else if (correctCount >= 1) { tween(patientBody, { tint: 0xFFFF99 }, { duration: 1000 }); return Math.floor(self.goldReward * 0.5); } else { tween(patientBody, { tint: 0xFF6666 }, { duration: 1000 }); LK.getSound('failure').play(); return -20; } }; return self; }); var PotionBottle = Container.expand(function () { var self = Container.call(this); var bottle = self.attachAsset('potionBottle', { anchorX: 0.5, anchorY: 0.5 }); self.setColor = function (base, element, herb) { var color = 0x87CEEB; if (element === 'fire') color = 0xFF6B6B;else if (element === 'water') color = 0x6BB6FF;else if (element === 'earth') color = 0x8B4513;else if (element === 'wind') color = 0xE6E6FA; bottle.tint = color; tween(bottle, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(bottle, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F2F }); /**** * Game Code ****/ var gameBackground = game.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); var goldAmount = 500; var currentScore = 0; var currentPatient = null; var isProcessingTreatment = false; // UI Elements var goldText = new Text2('Gold: ' + goldAmount, { size: 60, fill: 0xFFD700 }); goldText.anchor.set(0, 0); goldText.x = 120; goldText.y = 50; LK.gui.topLeft.addChild(goldText); var scoreText = new Text2('Score: ' + currentScore, { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(1, 0); LK.gui.topRight.addChild(scoreText); var patientStatusText = new Text2('Welcome! A patient awaits...', { size: 50, fill: 0xFFFFFF }); patientStatusText.anchor.set(0.5, 0); LK.gui.top.addChild(patientStatusText); // Game Objects var patient = game.addChild(new Patient()); patient.x = 1024; patient.y = 600; // Create symptom indicator on left side of screen var symptomIndicator = game.attachAsset('symptomIndicator', { anchorX: 0.5, anchorY: 0.5, x: 150, y: 600 }); // Link symptom indicator to patient patient.symptomGlow = symptomIndicator; // Selection wheels removed // Potion Bottle var potionBottle = game.addChild(new PotionBottle()); potionBottle.x = 1024; potionBottle.y = 800; potionBottle.visible = false; // Start hidden // Craft Button var craftButton = game.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2200 }); var craftButtonText = new Text2('CRAFT POTION', { size: 40, fill: 0xFFFFFF }); craftButtonText.anchor.set(0.5, 0.5); craftButtonText.x = 1024; craftButtonText.y = 2200; game.addChild(craftButtonText); // Wheel labels and selection display removed var symptoms = ['redrose', 'bluemist', 'emeraldveil']; var patientTimer = 0; var treatmentTimer = 0; function spawnNewPatient() { if (currentPatient) return; var randomSymptom = symptoms[Math.floor(Math.random() * symptoms.length)]; patient.setSymptom(randomSymptom); currentPatient = randomSymptom; patientStatusText.setText('A new patient has arrived!'); tween(patient, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, onFinish: function onFinish() { tween(patient, { scaleX: 1, scaleY: 1 }, { duration: 500 }); } }); } // updateSelectionDisplay function removed function craftPotion() { if (!currentPatient || isProcessingTreatment || cauldron.ingredients.length !== 3) return; isProcessingTreatment = true; var base = cauldron.ingredients[0]; var element = cauldron.ingredients[1]; var herb = cauldron.ingredients[2]; var goldEarned = patient.reactToPotion(base, element, herb); goldAmount += goldEarned; currentScore += Math.max(0, goldEarned); // Update UI and reset cauldron goldText.setText('Gold: ' + goldAmount); scoreText.setText('Score: ' + currentScore); cauldron.ingredients = []; updateCauldronDisplay(); if (goldEarned > 0) { if (goldEarned >= 100) { patientStatusText.setText('Perfect cure! +' + goldEarned + ' gold'); } else { patientStatusText.setText('Partial success! +' + goldEarned + ' gold'); } } else { patientStatusText.setText('Treatment failed! ' + goldEarned + ' gold'); } treatmentTimer = LK.ticks + 120; // 2 seconds } craftButton.down = function (x, y, obj) { craftPotion(); }; var heldItem = null; game.down = function (x, y, obj) { // Check each inventory item to see if it was clicked for (var i = inventoryItems.length - 1; i >= 0; i--) { var item = inventoryItems[i]; // Calculate distance from click point to item center var dx = x - item.x; var dy = y - item.y; var distance = Math.sqrt(dx * dx + dy * dy); // If click is within item bounds (assuming 50px radius for hit detection) if (distance < 50) { heldItem = item; item.isDragging = true; // Store drag offset to prevent jumping item.dragOffsetX = x - item.x; item.dragOffsetY = y - item.y; break; } } }; game.move = function (x, y, obj) { if (heldItem && heldItem.isDragging) { heldItem.x = x - heldItem.dragOffsetX; heldItem.y = y - heldItem.dragOffsetY; } }; game.up = function (x, y, obj) { if (heldItem) { heldItem.isDragging = false; // Calculate distance manually since LK.math is not available var dx = heldItem.x - cauldron.x; var dy = heldItem.y - cauldron.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 200 && cauldron.ingredients.length < 3) { cauldron.ingredients.push(heldItem.name); updateCauldronDisplay(); // Flash cauldron green briefly for success tween(cauldron, { tint: 0x44FF44 }, { duration: 200, onFinish: function onFinish() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Remove item from scene and inventory array on successful drop var itemIndex = inventoryItems.indexOf(heldItem); if (itemIndex > -1) { game.removeChild(heldItem); inventoryItems.splice(itemIndex, 1); } } else { // Flash cauldron red briefly for failed drop tween(cauldron, { tint: 0xFF4444 }, { duration: 200, onFinish: function onFinish() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Reset position if not dropped on cauldron resetItem(heldItem); } heldItem = null; } }; game.update = function () { patientTimer++; // Spawn new patient every 8 seconds if none exists if (!currentPatient && patientTimer % 480 === 0) { spawnNewPatient(); } // Clear patient after treatment if (isProcessingTreatment && LK.ticks >= treatmentTimer) { isProcessingTreatment = false; currentPatient = null; patientStatusText.setText('Next patient incoming...'); patientTimer = 0; } // Selection display update removed // Check for game over condition (bankruptcy) if (goldAmount < -100) { LK.showGameOver(); } // Check for win condition (high score) if (currentScore >= 2000) { LK.showYouWin(); } }; // Create draggable inventory items function createInventoryItem(name, x, y, color) { var container = new Container(); container.x = x; container.y = y; game.addChild(container); var item = container.attachAsset('potionBottle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, tint: color }); var label = new Text2(name, { size: 20, fill: 0xFFFFFF }); label.anchor.set(0.5, 0.5); label.x = 0; label.y = 0; container.addChild(label); container.name = name; container.draggable = true; container.type = 'ingredient'; // base, element, or herb container.isDragging = false; container.originalX = x; container.originalY = y; // Event handlers removed - now managed by global game handlers return container; } // Inventory section labels var inventoryLabel = new Text2('INGREDIENT INVENTORY', { size: 45, fill: 0xFFD700 }); inventoryLabel.anchor.set(0.5, 0.5); inventoryLabel.x = 1024; inventoryLabel.y = 1920; game.addChild(inventoryLabel); var baseInventoryLabel = new Text2('BASES', { size: 30, fill: 0xFFFFFF }); baseInventoryLabel.anchor.set(0.5, 0.5); baseInventoryLabel.x = 250; baseInventoryLabel.y = 2000; game.addChild(baseInventoryLabel); var elementInventoryLabel = new Text2('ELEMENTS', { size: 30, fill: 0xFFFFFF }); elementInventoryLabel.anchor.set(0.5, 0.5); elementInventoryLabel.x = 250; elementInventoryLabel.y = 2120; game.addChild(elementInventoryLabel); var herbInventoryLabel = new Text2('HERBS', { size: 30, fill: 0xFFFFFF }); herbInventoryLabel.anchor.set(0.5, 0.5); herbInventoryLabel.x = 250; herbInventoryLabel.y = 2240; game.addChild(herbInventoryLabel); var inventoryItems = []; var baseItems = ['infusion', 'broth', 'tonic']; var elementItems = ['fire', 'water', 'earth', 'wind']; var herbItems = ['firesilk', 'moonpetal', 'shadowcaps', 'stormleaf']; // Create wooden counter at bottom var counter = game.attachAsset('counterTop', { anchorX: 0.5, anchorY: 1, x: 1024, y: 2732 }); // Create cauldron drop zone in center foreground var cauldron = game.attachAsset('cauldronZone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400 }); cauldron.ingredients = []; // Add cauldron ingredients display var cauldronLabel = new Text2('Cauldron: Empty', { size: 35, fill: 0xFFFFFF }); cauldronLabel.anchor.set(0.5, 0.5); cauldronLabel.x = 1024; cauldronLabel.y = 1250; game.addChild(cauldronLabel); function updateCauldronDisplay() { if (cauldron.ingredients.length === 0) { cauldronLabel.setText('Cauldron: Empty'); } else { cauldronLabel.setText('Added: ' + cauldron.ingredients.join(', ')); } // Show/hide potion bottle based on ingredient count if (cauldron.ingredients.length === 3) { potionBottle.visible = true; } else { potionBottle.visible = false; } } function resetItem(item) { tween(item, { x: item.originalX, y: item.originalY }, { duration: 300 }); } // Create in rows or columns visually in bottom third baseItems.forEach(function (item, i) { inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2000, 0xDAA520)); }); elementItems.forEach(function (item, i) { inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2120, 0x20B2AA)); }); herbItems.forEach(function (item, i) { inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2240, 0xADFF2F)); }); // Initialize first patient spawnNewPatient();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Patient = Container.expand(function () {
var self = Container.call(this);
var patientBody = self.attachAsset('patient', {
anchorX: 0.5,
anchorY: 0.5
});
// symptomGlow moved to main game - reference will be set externally
self.symptomGlow = null;
self.symptomType = '';
self.requiredBase = '';
self.requiredElement = '';
self.requiredHerb = '';
self.goldReward = 0;
self.setSymptom = function (type) {
self.symptomType = type;
if (type === 'redrose') {
if (self.symptomGlow) self.symptomGlow.tint = 0xFF4444;
self.requiredBase = 'infusion';
self.requiredElement = 'water';
self.requiredHerb = 'moonpetal';
self.goldReward = 100;
} else if (type === 'bluemist') {
if (self.symptomGlow) self.symptomGlow.tint = 0x4444FF;
self.requiredBase = 'broth';
self.requiredElement = 'fire';
self.requiredHerb = 'firesilk';
self.goldReward = 120;
} else if (type === 'emeraldveil') {
if (self.symptomGlow) self.symptomGlow.tint = 0x44FF44;
self.requiredBase = 'infusion';
self.requiredElement = 'earth';
self.requiredHerb = 'shadowcaps';
self.goldReward = 150;
}
};
self.reactToPotion = function (base, element, herb) {
var correctCount = 0;
if (base === self.requiredBase) correctCount++;
if (element === self.requiredElement) correctCount++;
if (herb === self.requiredHerb) correctCount++;
if (correctCount === 3) {
tween(patientBody, {
tint: 0x90EE90
}, {
duration: 1000
});
LK.getSound('success').play();
return self.goldReward;
} else if (correctCount >= 1) {
tween(patientBody, {
tint: 0xFFFF99
}, {
duration: 1000
});
return Math.floor(self.goldReward * 0.5);
} else {
tween(patientBody, {
tint: 0xFF6666
}, {
duration: 1000
});
LK.getSound('failure').play();
return -20;
}
};
return self;
});
var PotionBottle = Container.expand(function () {
var self = Container.call(this);
var bottle = self.attachAsset('potionBottle', {
anchorX: 0.5,
anchorY: 0.5
});
self.setColor = function (base, element, herb) {
var color = 0x87CEEB;
if (element === 'fire') color = 0xFF6B6B;else if (element === 'water') color = 0x6BB6FF;else if (element === 'earth') color = 0x8B4513;else if (element === 'wind') color = 0xE6E6FA;
bottle.tint = color;
tween(bottle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(bottle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
var gameBackground = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var goldAmount = 500;
var currentScore = 0;
var currentPatient = null;
var isProcessingTreatment = false;
// UI Elements
var goldText = new Text2('Gold: ' + goldAmount, {
size: 60,
fill: 0xFFD700
});
goldText.anchor.set(0, 0);
goldText.x = 120;
goldText.y = 50;
LK.gui.topLeft.addChild(goldText);
var scoreText = new Text2('Score: ' + currentScore, {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
var patientStatusText = new Text2('Welcome! A patient awaits...', {
size: 50,
fill: 0xFFFFFF
});
patientStatusText.anchor.set(0.5, 0);
LK.gui.top.addChild(patientStatusText);
// Game Objects
var patient = game.addChild(new Patient());
patient.x = 1024;
patient.y = 600;
// Create symptom indicator on left side of screen
var symptomIndicator = game.attachAsset('symptomIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: 150,
y: 600
});
// Link symptom indicator to patient
patient.symptomGlow = symptomIndicator;
// Selection wheels removed
// Potion Bottle
var potionBottle = game.addChild(new PotionBottle());
potionBottle.x = 1024;
potionBottle.y = 800;
potionBottle.visible = false; // Start hidden
// Craft Button
var craftButton = game.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2200
});
var craftButtonText = new Text2('CRAFT POTION', {
size: 40,
fill: 0xFFFFFF
});
craftButtonText.anchor.set(0.5, 0.5);
craftButtonText.x = 1024;
craftButtonText.y = 2200;
game.addChild(craftButtonText);
// Wheel labels and selection display removed
var symptoms = ['redrose', 'bluemist', 'emeraldveil'];
var patientTimer = 0;
var treatmentTimer = 0;
function spawnNewPatient() {
if (currentPatient) return;
var randomSymptom = symptoms[Math.floor(Math.random() * symptoms.length)];
patient.setSymptom(randomSymptom);
currentPatient = randomSymptom;
patientStatusText.setText('A new patient has arrived!');
tween(patient, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
onFinish: function onFinish() {
tween(patient, {
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
}
});
}
// updateSelectionDisplay function removed
function craftPotion() {
if (!currentPatient || isProcessingTreatment || cauldron.ingredients.length !== 3) return;
isProcessingTreatment = true;
var base = cauldron.ingredients[0];
var element = cauldron.ingredients[1];
var herb = cauldron.ingredients[2];
var goldEarned = patient.reactToPotion(base, element, herb);
goldAmount += goldEarned;
currentScore += Math.max(0, goldEarned);
// Update UI and reset cauldron
goldText.setText('Gold: ' + goldAmount);
scoreText.setText('Score: ' + currentScore);
cauldron.ingredients = [];
updateCauldronDisplay();
if (goldEarned > 0) {
if (goldEarned >= 100) {
patientStatusText.setText('Perfect cure! +' + goldEarned + ' gold');
} else {
patientStatusText.setText('Partial success! +' + goldEarned + ' gold');
}
} else {
patientStatusText.setText('Treatment failed! ' + goldEarned + ' gold');
}
treatmentTimer = LK.ticks + 120; // 2 seconds
}
craftButton.down = function (x, y, obj) {
craftPotion();
};
var heldItem = null;
game.down = function (x, y, obj) {
// Check each inventory item to see if it was clicked
for (var i = inventoryItems.length - 1; i >= 0; i--) {
var item = inventoryItems[i];
// Calculate distance from click point to item center
var dx = x - item.x;
var dy = y - item.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If click is within item bounds (assuming 50px radius for hit detection)
if (distance < 50) {
heldItem = item;
item.isDragging = true;
// Store drag offset to prevent jumping
item.dragOffsetX = x - item.x;
item.dragOffsetY = y - item.y;
break;
}
}
};
game.move = function (x, y, obj) {
if (heldItem && heldItem.isDragging) {
heldItem.x = x - heldItem.dragOffsetX;
heldItem.y = y - heldItem.dragOffsetY;
}
};
game.up = function (x, y, obj) {
if (heldItem) {
heldItem.isDragging = false;
// Calculate distance manually since LK.math is not available
var dx = heldItem.x - cauldron.x;
var dy = heldItem.y - cauldron.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200 && cauldron.ingredients.length < 3) {
cauldron.ingredients.push(heldItem.name);
updateCauldronDisplay();
// Flash cauldron green briefly for success
tween(cauldron, {
tint: 0x44FF44
}, {
duration: 200,
onFinish: function onFinish() {
tween(cauldron, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Remove item from scene and inventory array on successful drop
var itemIndex = inventoryItems.indexOf(heldItem);
if (itemIndex > -1) {
game.removeChild(heldItem);
inventoryItems.splice(itemIndex, 1);
}
} else {
// Flash cauldron red briefly for failed drop
tween(cauldron, {
tint: 0xFF4444
}, {
duration: 200,
onFinish: function onFinish() {
tween(cauldron, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Reset position if not dropped on cauldron
resetItem(heldItem);
}
heldItem = null;
}
};
game.update = function () {
patientTimer++;
// Spawn new patient every 8 seconds if none exists
if (!currentPatient && patientTimer % 480 === 0) {
spawnNewPatient();
}
// Clear patient after treatment
if (isProcessingTreatment && LK.ticks >= treatmentTimer) {
isProcessingTreatment = false;
currentPatient = null;
patientStatusText.setText('Next patient incoming...');
patientTimer = 0;
}
// Selection display update removed
// Check for game over condition (bankruptcy)
if (goldAmount < -100) {
LK.showGameOver();
}
// Check for win condition (high score)
if (currentScore >= 2000) {
LK.showYouWin();
}
};
// Create draggable inventory items
function createInventoryItem(name, x, y, color) {
var container = new Container();
container.x = x;
container.y = y;
game.addChild(container);
var item = container.attachAsset('potionBottle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
tint: color
});
var label = new Text2(name, {
size: 20,
fill: 0xFFFFFF
});
label.anchor.set(0.5, 0.5);
label.x = 0;
label.y = 0;
container.addChild(label);
container.name = name;
container.draggable = true;
container.type = 'ingredient'; // base, element, or herb
container.isDragging = false;
container.originalX = x;
container.originalY = y;
// Event handlers removed - now managed by global game handlers
return container;
}
// Inventory section labels
var inventoryLabel = new Text2('INGREDIENT INVENTORY', {
size: 45,
fill: 0xFFD700
});
inventoryLabel.anchor.set(0.5, 0.5);
inventoryLabel.x = 1024;
inventoryLabel.y = 1920;
game.addChild(inventoryLabel);
var baseInventoryLabel = new Text2('BASES', {
size: 30,
fill: 0xFFFFFF
});
baseInventoryLabel.anchor.set(0.5, 0.5);
baseInventoryLabel.x = 250;
baseInventoryLabel.y = 2000;
game.addChild(baseInventoryLabel);
var elementInventoryLabel = new Text2('ELEMENTS', {
size: 30,
fill: 0xFFFFFF
});
elementInventoryLabel.anchor.set(0.5, 0.5);
elementInventoryLabel.x = 250;
elementInventoryLabel.y = 2120;
game.addChild(elementInventoryLabel);
var herbInventoryLabel = new Text2('HERBS', {
size: 30,
fill: 0xFFFFFF
});
herbInventoryLabel.anchor.set(0.5, 0.5);
herbInventoryLabel.x = 250;
herbInventoryLabel.y = 2240;
game.addChild(herbInventoryLabel);
var inventoryItems = [];
var baseItems = ['infusion', 'broth', 'tonic'];
var elementItems = ['fire', 'water', 'earth', 'wind'];
var herbItems = ['firesilk', 'moonpetal', 'shadowcaps', 'stormleaf'];
// Create wooden counter at bottom
var counter = game.attachAsset('counterTop', {
anchorX: 0.5,
anchorY: 1,
x: 1024,
y: 2732
});
// Create cauldron drop zone in center foreground
var cauldron = game.attachAsset('cauldronZone', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1400
});
cauldron.ingredients = [];
// Add cauldron ingredients display
var cauldronLabel = new Text2('Cauldron: Empty', {
size: 35,
fill: 0xFFFFFF
});
cauldronLabel.anchor.set(0.5, 0.5);
cauldronLabel.x = 1024;
cauldronLabel.y = 1250;
game.addChild(cauldronLabel);
function updateCauldronDisplay() {
if (cauldron.ingredients.length === 0) {
cauldronLabel.setText('Cauldron: Empty');
} else {
cauldronLabel.setText('Added: ' + cauldron.ingredients.join(', '));
}
// Show/hide potion bottle based on ingredient count
if (cauldron.ingredients.length === 3) {
potionBottle.visible = true;
} else {
potionBottle.visible = false;
}
}
function resetItem(item) {
tween(item, {
x: item.originalX,
y: item.originalY
}, {
duration: 300
});
}
// Create in rows or columns visually in bottom third
baseItems.forEach(function (item, i) {
inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2000, 0xDAA520));
});
elementItems.forEach(function (item, i) {
inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2120, 0x20B2AA));
});
herbItems.forEach(function (item, i) {
inventoryItems.push(createInventoryItem(item, 400 + i * 150, 2240, 0xADFF2F));
});
// Initialize first patient
spawnNewPatient();