/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var HerbPopup = Container.expand(function () { var self = Container.call(this); // Semi-transparent background overlay var overlay = self.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0.8, tint: 0x000000 }); // Main popup window - larger and centered var popupWindow = self.attachAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, width: 1600, height: 1000, tint: 0x8B4513 }); // Close button (X) - larger var closeButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1700, y: 950, width: 120, height: 120, tint: 0xFF0000 }); var closeButtonText = new Text2('X', { size: 80, fill: 0xFFFFFF }); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = 1700; closeButtonText.y = 950; self.addChild(closeButtonText); // Herb name title - much larger var nameText = new Text2('', { size: 72, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.x = 1024; nameText.y = 1050; self.addChild(nameText); // Type text (Plant/Fungus) var typeText = new Text2('', { size: 48, fill: 0xFFD700 }); typeText.anchor.set(0.5, 0.5); typeText.x = 1024; typeText.y = 1150; self.addChild(typeText); // Element text - larger var elementText = new Text2('', { size: 52, fill: 0xFFFFFF }); elementText.anchor.set(0.5, 0.5); elementText.x = 1024; elementText.y = 1250; self.addChild(elementText); // Description text - much larger and wrapped var descriptionText = new Text2('', { size: 40, fill: 0xFFFFFF }); descriptionText.anchor.set(0.5, 0.5); descriptionText.x = 1024; descriptionText.y = 1500; self.addChild(descriptionText); self.isVisible = false; self.showPopup = function (herbName, x, y) { var herbData = HERB_DATA[herbName]; if (!herbData) return; nameText.setText(herbName); typeText.setText(herbData.type); elementText.setText('Element: ' + herbData.alignment); // Wrap long descriptions var wrappedDesc = wrapTextByWords(herbData.description, 50); descriptionText.setText(wrappedDesc); // Color the element text based on alignment var color = colorForAlignment(herbData.alignment); elementText.tint = color; self.visible = true; self.isVisible = true; }; self.hidePopup = function () { self.visible = false; self.isVisible = false; }; // Close button handler closeButton.down = function (x, y, obj) { self.hidePopup(); }; // Overlay click to close overlay.down = function (x, y, obj) { self.hidePopup(); }; // Start hidden self.visible = false; return self; }); // Shop button event handler removed per requirements // === Herbalist's Journal with Table of Contents === var JournalPopup = Container.expand(function () { var self = Container.call(this); // Semi-transparent background overlay var overlay = self.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0.8, tint: 0x000000 }); // Main journal window - 75% of screen var journalWindow = self.attachAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, width: 1536, // 75% of 2048 height: 2050, // 75% of 2732 tint: 0xfdf6e3 }); // Close button (X) var closeButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1700, y: 500, width: 90, // 1.5x larger height: 90, // 1.5x larger tint: 0xFF0000 }); var closeButtonText = new Text2('✕', { size: 60, // instead of 40, 1.5x larger fill: 0xFFFFFF }); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = 1700; closeButtonText.y = 500; self.addChild(closeButtonText); // Table of Contents View var contentsTitle = new Text2('Herbalist\'s Journal', { size: 64, fill: 0x000000 }); contentsTitle.anchor.set(0.5, 0.5); contentsTitle.x = 1024; contentsTitle.y = 650; self.addChild(contentsTitle); var contentsSubtitle = new Text2('Contents', { size: 48, fill: 0x444444 }); contentsSubtitle.anchor.set(0.5, 0.5); contentsSubtitle.x = 1024; contentsSubtitle.y = 750; self.addChild(contentsSubtitle); // Content section buttons - improved styling var herbsContentButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 950, width: 650, // Increased width height: 180, // Increased height tint: 0x222222 // Dark background for contrast }); var herbsContentText = new Text2('Herbs & Fungi', { size: 72, // Increased from 40 fill: 0xFFFFFF // White text for contrast }); herbsContentText.anchor.set(0.5, 0.5); herbsContentText.x = 1024; herbsContentText.y = 950; self.addChild(herbsContentText); var basesContentButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, // was 1100, now spaced further down width: 650, // Increased width height: 180, // Increased height tint: 0x222222 // Dark background for contrast }); var basesContentText = new Text2('Tea Bases', { size: 72, // Increased from 40 fill: 0xFFFFFF // White text for contrast }); basesContentText.anchor.set(0.5, 0.5); basesContentText.x = 1024; basesContentText.y = 1200; // was 1100, now spaced further down self.addChild(basesContentText); var illnessesContentButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1450, // was 1250, now spaced further down width: 650, // Increased width height: 180, // Increased height tint: 0x222222 // Dark background for contrast }); var illnessesContentText = new Text2('Illnesses', { size: 72, // Increased from 40 fill: 0xFFFFFF // White text for contrast }); illnessesContentText.anchor.set(0.5, 0.5); illnessesContentText.x = 1024; illnessesContentText.y = 1450; // was 1250, now spaced further down self.addChild(illnessesContentText); // Section View Elements (hidden initially) var sectionTitle = new Text2('', { size: 80, // larger fill: 0x000000, // darker fontWeight: 'bold' }); sectionTitle.anchor.set(0.5, 0.5); sectionTitle.x = 1024; sectionTitle.y = 750; self.addChild(sectionTitle); var sectionType = new Text2('', { size: 84, // larger fill: 0x006400 // dark green }); sectionType.anchor.set(0.5, 0.5); sectionType.x = 1024; sectionType.y = 950; self.addChild(sectionType); var sectionElement = new Text2('', { size: 84, fill: 0x000000 // darker, readable }); sectionElement.anchor.set(0.5, 0.5); sectionElement.x = 1024; sectionElement.y = 1100; self.addChild(sectionElement); var sectionDescription = new Text2('', { size: 75, // substantially larger fill: 0x000000, // darker align: 'center' }); sectionDescription.anchor.set(0.5, 0); sectionDescription.x = 1024; sectionDescription.y = 1300; self.addChild(sectionDescription); // Navigation buttons for sections var backToContentsButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 750, y: 2200, width: 300, height: 100, tint: 0x222222 }); var backToContentsText = new Text2('⬅ Back', { size: 42, fill: 0xFFFFFF }); backToContentsText.anchor.set(0.5, 0.5); backToContentsText.x = 750; backToContentsText.y = 2200; self.addChild(backToContentsText); var nextEntryButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1300, y: 2200, width: 300, height: 100, tint: 0x222222 }); var nextEntryText = new Text2('Next ➡', { size: 42, fill: 0xFFFFFF }); nextEntryText.anchor.set(0.5, 0.5); nextEntryText.x = 1300; nextEntryText.y = 2200; self.addChild(nextEntryText); // State management self.currentView = 'contents'; // 'contents' or 'section' self.currentSection = ''; self.currentIndex = 0; self.viewHistory = []; // Track navigation history for back button self.journalData = { herbs: Object.keys(HERB_DATA), teaBases: Object.keys(teaBases), illnesses: Object.keys(ILLNESS_DATA) }; self.isVisible = false; // Show/hide content elements self.showContents = function () { self.currentView = 'contents'; // Show contents elements contentsTitle.visible = true; contentsSubtitle.visible = true; herbsContentButton.visible = true; herbsContentText.visible = true; basesContentButton.visible = true; basesContentText.visible = true; illnessesContentButton.visible = true; illnessesContentText.visible = true; // Hide section elements sectionTitle.visible = false; sectionType.visible = false; sectionElement.visible = false; sectionDescription.visible = false; backToContentsButton.visible = false; backToContentsText.visible = false; nextEntryButton.visible = false; nextEntryText.visible = false; }; self.showSection = function (section) { // Save current state to history before changing if (self.currentView === 'contents') { self.viewHistory.push({ view: 'contents' }); } else if (self.currentView === 'section') { self.viewHistory.push({ view: 'section', section: self.currentSection, index: self.currentIndex }); } self.currentView = 'section'; self.currentSection = section; self.currentIndex = 0; // Hide contents elements contentsTitle.visible = false; contentsSubtitle.visible = false; herbsContentButton.visible = false; herbsContentText.visible = false; basesContentButton.visible = false; basesContentText.visible = false; illnessesContentButton.visible = false; illnessesContentText.visible = false; // Show section elements sectionTitle.visible = true; sectionType.visible = true; sectionElement.visible = true; sectionDescription.visible = true; backToContentsButton.visible = true; backToContentsText.visible = true; nextEntryButton.visible = true; nextEntryText.visible = true; self.updateSectionContent(); }; self.updateSectionContent = function () { var entries = self.journalData[self.currentSection]; var entry = entries[self.currentIndex]; if (self.currentSection === 'herbs') { var herbData = HERB_DATA[entry]; sectionTitle.setText(entry.toUpperCase()); sectionType.setText(herbData.type); sectionElement.setText("Element: " + herbData.alignment); sectionDescription.setText(wrapTextByWords(herbData.description, 40)); } else if (self.currentSection === 'teaBases') { var baseData = teaBases[entry]; sectionTitle.setText(entry.toUpperCase()); sectionType.setText("Tea Base"); sectionElement.setText("Element: " + baseData.element.charAt(0).toUpperCase() + baseData.element.slice(1)); sectionDescription.setText(wrapTextByWords("A light base for brewing teas, often combined with matching herbs.", 40)); } else if (self.currentSection === 'illnesses') { var illnessData = ILLNESS_DATA[entry]; sectionTitle.setText(illnessData.name.toUpperCase()); sectionType.setText("Illness"); var elementText = "Element: "; for (var i = 0; i < illnessData.element_imbalance.length; i++) { if (i > 0) elementText += " + "; elementText += illnessData.element_imbalance[i].charAt(0).toUpperCase() + illnessData.element_imbalance[i].slice(1); } sectionElement.setText(elementText); var cureText = "Cure: Use "; for (var i = 0; i < illnessData.element_imbalance.length; i++) { if (i > 0) cureText += " + "; cureText += illnessData.element_imbalance[i]; } cureText += " base with herbs."; var desc = "Description: " + illnessData.description + "\n\n" + cureText; sectionDescription.setText(wrapTextByWords(desc, 40)); } }; self.nextEntry = function () { var entries = self.journalData[self.currentSection]; // Save current state before moving forward self.viewHistory.push({ view: 'section', section: self.currentSection, index: self.currentIndex }); if (self.currentIndex < entries.length - 1) { self.currentIndex++; } else { self.currentIndex = 0; // Loop back to first } self.updateSectionContent(); }; self.showJournal = function () { self.visible = true; self.isVisible = true; self.viewHistory = []; // Clear history when opening journal fresh self.showContents(); }; self.hideJournal = function () { self.visible = false; self.isVisible = false; }; // Button handlers herbsContentButton.down = function (x, y, obj) { self.showSection('herbs'); }; basesContentButton.down = function (x, y, obj) { self.showSection('teaBases'); }; illnessesContentButton.down = function (x, y, obj) { self.showSection('illnesses'); }; backToContentsButton.down = function (x, y, obj) { // Go back to previous view in history if (self.viewHistory.length > 0) { var previousState = self.viewHistory.pop(); if (previousState.view === 'contents') { self.showContents(); } else if (previousState.view === 'section') { self.currentView = 'section'; self.currentSection = previousState.section; self.currentIndex = previousState.index; // Hide contents elements contentsTitle.visible = false; contentsSubtitle.visible = false; herbsContentButton.visible = false; herbsContentText.visible = false; basesContentButton.visible = false; basesContentText.visible = false; illnessesContentButton.visible = false; illnessesContentText.visible = false; // Show section elements sectionTitle.visible = true; sectionType.visible = true; sectionElement.visible = true; sectionDescription.visible = true; backToContentsButton.visible = true; backToContentsText.visible = true; nextEntryButton.visible = true; nextEntryText.visible = true; self.updateSectionContent(); } } else { // No history, do nothing (do not fallback to showContents) } }; nextEntryButton.down = function (x, y, obj) { self.nextEntry(); }; // Close button handler closeButton.down = function (x, y, obj) { self.hideJournal(); }; // Overlay click to close overlay.down = function (x, y, obj) { self.hideJournal(); }; // Start hidden self.visible = false; return self; }); // Create journal popup var Patient = Container.expand(function () { var self = Container.call(this); // Randomize patient graphic var patientGraphics = ['patient', 'patient1', 'patient2']; var randomGraphic = patientGraphics[Math.floor(Math.random() * patientGraphics.length)]; var patientBody = self.attachAsset(randomGraphic, { 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; var illness = ILLNESS_DATA[type]; if (illness) { // if (self.symptomGlow) self.symptomGlow.tint = illness.symptom_color; self.element_imbalance = illness.element_imbalance; self.goldReward = illness.gold_reward; self.illnessName = illness.name; self.description = illness.description; } }; // reactToPotion method removed - cure logic now handled in craftPotion function return self; }); var PopupMenu = Container.expand(function () { var self = Container.call(this); // Semi-transparent background overlay var overlay = self.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0.7, tint: 0x000000 }); // Main popup window - 75% of screen size var popupWindow = self.attachAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, width: 1536, // 75% of 2048 height: 2050, // 75% of 2732 tint: 0x8B4513 }); // Close button (X) var closeButton = self.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1600, y: 600, width: 80, height: 80, tint: 0xFF0000 }); var closeButtonText = new Text2('X', { size: 40, fill: 0xFFFFFF }); closeButtonText.anchor.set(0.5, 0.5); closeButtonText.x = 1600; closeButtonText.y = 600; self.addChild(closeButtonText); // Title text - larger for better visibility var titleText = new Text2('', { size: 72, // Increased from 50 to 72 fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 700; self.addChild(titleText); self.menuItems = []; self.isVisible = false; self.showMenu = function (title, items, itemColors) { titleText.setText(title); self.visible = true; self.isVisible = true; // Clear existing menu items for (var i = 0; i < self.menuItems.length; i++) { game.removeChild(self.menuItems[i]); } self.menuItems = []; // Special layout for Tea Bases (centered vertical stack) if (title.indexOf('TEA BASES') !== -1) { var buttonWidth = 800; // 60% of screen width (1536 * 0.6) var buttonHeight = 120; var buttonSpacing = 40; var startY = 1000; for (var i = 0; i < items.length; i++) { var y = startY + i * (buttonHeight + buttonSpacing); // Create button-style container var menuItem = new Container(); menuItem.x = 1024; // Center horizontally menuItem.y = y; game.addChild(menuItem); // Create button background var buttonBg = menuItem.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: buttonWidth, height: buttonHeight, tint: 0x1a237e // Deep blue for tea bases }); // Create centered button text var buttonText = new Text2(items[i], { size: 72, // Larger text for tea bases fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); buttonText.x = 0; buttonText.y = 0; menuItem.addChild(buttonText); menuItem.name = items[i]; menuItem.draggable = true; menuItem.type = 'ingredient'; menuItem.isDragging = false; menuItem.originalX = 1024; menuItem.originalY = y; menuItem.isMenuPurchase = true; menuItem.cost = 50; self.menuItems.push(menuItem); } } else { // Regular grid layout for other items (herbs, etc.) var itemsPerRow = 3; var buttonWidth = 400; var buttonHeight = 120; var buttonSpacing = 60; var totalRowWidth = itemsPerRow * buttonWidth + (itemsPerRow - 1) * buttonSpacing; var startX = 1024 - totalRowWidth / 2 + buttonWidth / 2; var startY = 1050; var rowSpacing = 150; for (var i = 0; i < items.length; i++) { var row = Math.floor(i / itemsPerRow); var col = i % itemsPerRow; var x = startX + col * (buttonWidth + buttonSpacing); var y = startY + row * rowSpacing; // Create button-style container var menuItem = new Container(); menuItem.x = x; menuItem.y = y; game.addChild(menuItem); // Create button background var buttonBg = menuItem.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: buttonWidth, height: buttonHeight, tint: itemColors[i] || 0x8B4513 }); // Create centered button text - larger for better visibility var buttonText = new Text2(items[i], { size: 48, // Increased from 36 to 48 fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); buttonText.x = 0; buttonText.y = 0; menuItem.addChild(buttonText); menuItem.name = items[i]; menuItem.draggable = true; menuItem.type = 'ingredient'; menuItem.isDragging = false; menuItem.originalX = x; menuItem.originalY = y; menuItem.isMenuPurchase = true; menuItem.cost = 50; // Cost to purchase from menu self.menuItems.push(menuItem); } } }; self.hideMenu = function () { self.visible = false; self.isVisible = false; // Remove menu items for (var i = 0; i < self.menuItems.length; i++) { game.removeChild(self.menuItems[i]); } self.menuItems = []; }; self.showDetails = function (itemName) { if (HERB_DATA[itemName]) { herbPopup.showPopup(itemName, 0, 0); } }; // Close button handler closeButton.down = function (x, y, obj) { self.hideMenu(); }; // Overlay click to close overlay.down = function (x, y, obj) { self.hideMenu(); }; // Start hidden self.visible = false; 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 ****/ // Add global button styling var buttonStyle = { fontSize: 20, padding: "12px 24px", borderRadius: 10, backgroundColor: "#444", color: "white", cursor: "pointer" }; var gameBackground = game.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); // === Tap vs Drag state === var downItemCandidate = null; var downStartX = 0, downStartY = 0; var DRAG_THRESHOLD = 15; // smoother tap vs drag // helper: rectangular hit test function hitRect(x, y, cx, cy, halfW, halfH) { return Math.abs(x - cx) <= halfW && Math.abs(y - cy) <= halfH; } // Helper function for exact, order-agnostic set matching function containsAll(required, brewed) { for (var i = 0; i < required.length; i++) { if (brewed.indexOf(required[i]) === -1) { return false; } } return true; } 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: 0xFFD700, // bright gold highlight when grabbed alpha: 0.9, 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 = 0; ghost.dragOffsetX = 0; ghost.dragOffsetY = 0; // stronger pop so it feels more obvious tween(ghost, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100 }); return ghost; } var reputation = 100; var currentScore = 0; var currentPatient = null; var isProcessingTreatment = false; // Menu buttons are already implemented at the top of the screen: // - Instructions button (blue) at x=400, y=100 // - New Game button (red) at x=1024, y=100 // - Shop button (green) at x=1648, y=100 // All buttons have proper styling, text labels, and event handlers // HUD / BOTTOM LEFT LAYER - Reputation Meter var meterBackground = new Container(); meterBackground.x = 120; meterBackground.y = -120; LK.gui.bottomLeft.addChild(meterBackground); // Meter background bar var meterBg = meterBackground.attachAsset('craftButton', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 600, height: 80, tint: 0x444444 }); // Meter fill bar var meterFill = meterBackground.attachAsset('craftButton', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 600, height: 80, tint: 0x00FF00 }); // Reputation label var reputationLabel = new Text2('Reputation', { size: 64, fill: 0xFFFFFF, font: "'Times New Roman', serif" }); reputationLabel.anchor.set(0, 1); reputationLabel.x = 120; reputationLabel.y = -130; LK.gui.bottomLeft.addChild(reputationLabel); // Reputation value text var reputationText = new Text2(reputation.toString(), { size: 56, fill: 0xFFFFFF, font: "'Times New Roman', serif" }); reputationText.anchor.set(0.5, 0.5); reputationText.x = 420; reputationText.y = -90; LK.gui.bottomLeft.addChild(reputationText); // CENTER LAYER - Patient + Cauldron + Dialogue var patient = game.addChild(new Patient()); patient.x = 1024; patient.y = 600; // Moved down by 100px /**** * Thermometer System ****/ // Container for thermometer var thermometer = new Container(); thermometer.x = 200; // position on screen thermometer.y = 700; // position directly behind symptom indicator game.addChild(thermometer); // Thermometer frame graphic (with transparent center) var thermometerFrame = thermometer.attachAsset('thermometerGraphic', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // Red mercury fill (rectangle) var mercuryFill = thermometer.attachAsset('craftButton', { anchorX: 0.5, anchorY: 1, // grows upward x: 0, y: thermometerFrame.height / 2, // bottom of thermometer frame width: 40, // thickness of the red line height: 0, // start empty tint: 0xFF0000 }); // Function to set thermometer level based on patient temperature function setThermometer(temp) { var maxHeight = thermometerFrame.height - 40; // subtract a little so it doesn't spill over top of the glass var targetHeight = 0; if (temp === "Cold") { targetHeight = maxHeight * 0.25; // low fill } else if (temp === "Normal") { targetHeight = maxHeight * 0.55; // mid fill } else if (temp === "Hot") { targetHeight = maxHeight * 0.95; // almost full } // Animate mercury height; bottom stays fixed because anchorY = 1 tween(mercuryFill, { height: targetHeight }, { duration: 600, easing: tween.easeOut }); } // Create symptom indicator positioned near patient (moved after counter for proper layering) var symptomIndicator = game.attachAsset('symptomIndicator', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 700 }); // Link symptom indicator to patient patient.symptomGlow = symptomIndicator; // Patient status text with background - positioned below patient portrait var hintBox = game.attachAsset('patientCommentBox', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1050 }); // Hint text var patientStatusText = new Text2('Welcome! A patient awaits...', { size: 70, // increased from 42 fill: 0xFFFFFF, align: 'center', font: "'Times New Roman', serif" }); patientStatusText.anchor.set(0.5, 0.5); patientStatusText.x = 1024; patientStatusText.y = 1050; game.addChild(patientStatusText); // BOTTOM LAYER - Ingredient Buttons + Craft Button (craft button moved after counter layer) // Wheel labels and selection display removed // Illness definitions with element imbalances and cure rules var ILLNESS_DATA = { "hollowshiver": { id: "hollowshiver", name: "Hollowshiver", description: "A creeping cold that settles in the bones, no matter how many blankets are piled on. Common after wandering too long near mushroom circles or in moonlit glades. Warming the body will help.", temperature: "Cold", causes: ["Getting too cold", "Too many water herbs", "Swimming in cold waters"], cure: "Brew fire-aligned broth with Kitty Mitten, Sommeral, or Brindleroot", symptoms: ["I took a swim, but the water was so cold…", "I feel a cold damp in my bones…"], element_imbalance: ["fire"], symptom_color: 0x4444FF, gold_reward: 120 }, "driftlimb": { id: "driftlimb", name: "Driftlimb", description: "A peculiar tiredness that makes even teacups feel too heavy. Believed to come from skipping too many meals or carrying burdens not your own. A wind-based approach will help uplift the body.", temperature: "Normal", causes: ["Working too hard", "Being ill for a long time"], cure: "Brew wind-aligned tea with Fuzzy Peeper, Demeter's Arrow or Pipeweed", symptoms: ["I've been working so hard lately, my arms feel so heavy…", "I feel like I have no muscles in my legs…"], element_imbalance: ["wind"], symptom_color: 0xFFFF44, gold_reward: 130 }, "greyheart": { id: "greyheart", name: "Greyheart", description: "A sadness with no clear shape. Often spoken of in hushed tones as the malaise of quiet souls or lonely seasons. To heal Greyheart, one must nourish the body, warm the limbs, and lift the spirit at the same time.", temperature: "Normal", causes: ["Tragedy of the heart", "Ennui"], cure: "Earthy Tea Base with Kitty Mitten and Demeter's Arrow", symptoms: ["I feel so low. As if I'm haunted somehow by the troubles of my past…", "This weather has been hard for me… I feel so… I don't know.", "I feel as if I've been flattened or hollowed out. Something is missing and I don't know what…"], element_imbalance: ["earth"], symptom_color: 0x888888, gold_reward: 140 }, "the_wilt": { id: "the_wilt", name: "The Wilt", description: "When one's spirit feels dim and steps feel cloud-soft. A common complaint after long travels or encounters with annoying relatives. The wilt requires a wind based tea.", temperature: "Normal", causes: ["Not sleeping enough", "Over excitement"], cure: "Uplifting Tea Base with Demeter's Arrow and Fuzzy Peeper", symptoms: ["I'm so tired no matter how much I sleep."], element_imbalance: ["wind"], symptom_color: 0xAAAAFF, gold_reward: 110 }, "wretching_waters": { id: "wretching_waters", name: "Wretching Waters", description: "A twisting in the gut, often after eating something wild or questionably cooked. One needs a strong, nourishing broth.", temperature: "Hot", causes: ["Rotten food", "Ingesting poisonous fungi"], cure: "Earthy Tea Base with Sheep's Bane and Common Snupe", symptoms: ["I've been in the bathroom all morning!", "My stomach…. uurrgghhh…"], element_imbalance: ["earth"], symptom_color: 0x44FF44, gold_reward: 150 }, "unmovable_phlegm": { id: "unmovable_phlegm", name: "Unmovable Phlegm", description: "More irritating than dangerous, this thick muck often makes sleep difficult. A combination of heat and wind is necessary to clear it.", temperature: "Normal", causes: ["Pollen exposure", "Allergies to animals"], cure: "Spicy Tea Base with Demeter's Arrow and Fuzzy Peeper", symptoms: ["I can't stop sneezing since I went to that petting zoo!", "I feel like I can't breathe, especially at night.", "This dust in the air is making me feel awful!"], element_imbalance: ["fire", "wind"], symptom_color: 0xFFCC44, gold_reward: 125 }, "dagger_hex": { id: "dagger_hex", name: "Dagger Hex", description: "A stiffness that clings to joints and limbs like thistleburs. Believed to follow poor posture or sleeping under cursed trees. Heat and nourish the body to encourage healing.", temperature: "Normal", causes: ["Overexertion"], cure: "Spicy Tea Base with Sheep's Bane and Common Snupe", symptoms: ["I've got this pain in my back!", "Ack! My neck is killing me. I've been working too hard…"], element_imbalance: ["fire"], symptom_color: 0xFF8844, gold_reward: 135 }, "witchrattle": { id: "witchrattle", name: "Witchrattle", description: "A dry, scratchy cough that echoes oddly through the throat. Caught mostly in deep winter and under cold moons. The treatment must warm the body but cool the throat.", temperature: "Cold", causes: ["Getting too cold", "Contact with sick people"], cure: "Spicy Tea Base with Frogwick and Geneva's Dewdrop or River Leek", symptoms: ["Hack hack! Excuse my cough!", "These frosty evenings have been so hard on my lungs…"], element_imbalance: ["fire"], symptom_color: 0x99CCFF, gold_reward: 120 }, "twiddlecurse": { id: "twiddlecurse", name: "Twiddlecurse", description: "A jittery, jumpy itch in the fingers and feet. Common among those plagued by tragedies or nightmares. A cooling tea is required.", temperature: "Normal", causes: ["Stress", "Too many wind-aligned herbs"], cure: "Cooling Tea Base with Geneva's Dewdrop and Frogwick", symptoms: ["Demeter's Arrow is so good but I think I had too many…", "I'm so restless lately, I can't stop fidgeting!"], element_imbalance: ["water"], symptom_color: 0x66CCFF, gold_reward: 115 }, "purging_rot": { id: "purging_rot", name: "Purging Rot", description: "A slow-burning ailment marked by greenish skin and sweating. Often follows the consumption of misidentified mushrooms or cursed teas. Expel the rot but make sure to also nourish the body.", temperature: "Hot", causes: ["Poisonous mushrooms", "Cursed teas"], cure: "Uplifting Tea Base with Sheep's Bane and Common Snupe", symptoms: ["I must have eaten something that went bad…", "Purple mushrooms are safe to eat… right?"], element_imbalance: ["wind"], symptom_color: 0x77DD77, gold_reward: 160 } }; var symptoms = Object.keys(ILLNESS_DATA); var patientTimer = 0; var treatmentTimer = 0; function updateHintBox() { // Resize width and height based on text size hintBox.width = patientStatusText.width + 80; // padding left/right hintBox.height = patientStatusText.height + 40; // padding top/bottom // Keep box centered with the text hintBox.x = patientStatusText.x; hintBox.y = patientStatusText.y; } // Define spawnNewPatient function function spawnNewPatient() { if (currentPatient) return; var randomSymptom = symptoms[Math.floor(Math.random() * symptoms.length)]; patient.setSymptom(randomSymptom); currentPatient = randomSymptom; var illness = ILLNESS_DATA[randomSymptom]; if (illness) { var symptom = illness.symptoms[Math.floor(Math.random() * illness.symptoms.length)]; patientStatusText.setText(symptom); updateHintBox(); // Set thermometer based on patient temperature - call it properly setThermometer(illness.temperature); } //{8T} {8U} {8V} {8W} else { patientStatusText.setText('A new patient has arrived!'); updateHintBox(); // Set default temperature for unknown illnesses setThermometer("Normal"); } 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 brewTea(baseName, herb1Name, herb2Name) { if (!teaBases[baseName]) return null; var base = teaBases[baseName]; var herb1 = HERB_DATA[herb1Name]; var herb2 = HERB_DATA[herb2Name]; if (!herb1 || !herb2 || herb1Name === herb2Name) return null; // Gather all elements in the mix var elements = [base.element, herb1.element, herb2.element]; // Pure tea (all the same element) if (herb1.element === herb2.element && herb1.element === base.element) { return { name: base.name + " + " + herb1Name + " + " + herb2Name, elements: [base.element] }; } // Hybrid tea (mixed elements) var uniqueElements = []; for (var i = 0; i < elements.length; i++) { if (uniqueElements.indexOf(elements[i]) === -1) { uniqueElements.push(elements[i]); } } return { name: base.name + " + " + herb1Name + " + " + herb2Name, elements: uniqueElements }; } function craftPotion() { if (!currentPatient || isProcessingTreatment || cauldron.ingredients.length !== 3) { return; } isProcessingTreatment = true; var baseName = cauldron.ingredients[0]; var herb1Name = cauldron.ingredients[1]; var herb2Name = cauldron.ingredients[2]; var brewedTea = brewTea(baseName, herb1Name, herb2Name); var repChange = 0; if (brewedTea) { var illness = ILLNESS_DATA[currentPatient]; if (illness) { var required = illness.element_imbalance; // always an array var brewed = brewedTea.elements; // Use contains-all matching - brew must contain at least all required elements var cureOK = containsAll(required, brewed); if (cureOK) { repChange = illness.gold_reward; tween(patient, { tint: 0x90EE90 }, { duration: 1000 }); LK.getSound('success').play(); // ✅ patient is cured → clear them currentPatient = null; patientStatusText.setText("Patient cured! +" + repChange + " reputation"); updateHintBox(); treatmentTimer = LK.ticks + 120; // delay before next patient } else { // ❌ Wrong tea → penalty, patient leaves repChange = -20; tween(patient, { tint: 0xFF6666 }, { duration: 1000 }); LK.getSound('failure').play(); patientStatusText.setText("That didn't work... -" + Math.abs(repChange) + " reputation"); updateHintBox(); // ✅ Remove patient and start timer for next one currentPatient = null; treatmentTimer = LK.ticks + 120; // delay before next patient } } } else { // Invalid tea combination (not a real brew) repChange = -10; // smaller penalty tween(patient, { tint: 0xFF6666 }, { duration: 1000 }); LK.getSound('failure').play(); LK.getSound('failure').play(); patientStatusText.setText("Invalid brew! -" + Math.abs(repChange) + " reputation"); updateHintBox(); isProcessingTreatment = false; // allow retry } reputation += repChange; currentScore += Math.max(0, repChange); // Update reputation meter var maxReputation = 200; // Maximum reputation for meter scaling var meterWidth = Math.max(0, Math.min(600, reputation / maxReputation * 600)); // Animate meter fill width change tween(meterFill, { width: meterWidth }, { duration: 500, easing: tween.easeOut }); // Update meter color based on reputation level var meterColor = 0x00FF00; // Green if (reputation < 50) { meterColor = 0xFF0000; // Red } else if (reputation < 100) { meterColor = 0xFFFF00; // Yellow } tween(meterFill, { tint: meterColor }, { duration: 300 }); // Update reputation text value reputationText.setText(reputation.toString()); cauldron.ingredients = []; updateCauldronDisplay(); } var heldItem = null; var dragGhost = null; game.down = function (x, y, obj) { // Check main game buttons first with proper hit boxes // 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]; // Use full button dimensions for tea bases var hw, hh; if (it.children[0] && it.children[0].tint === 0x1a237e) { // Tea base button - use full dimensions hw = 400; // full buttonWidth from tea base creation hh = 60; // full buttonHeight/2 from tea base creation } else { // Regular herb button hw = 200; // full buttonWidth/2 from herb creation hh = 60; // full buttonHeight/2 from herb creation } 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) { // Handle hover effects for tea base buttons if (popupMenu.isVisible && !heldItem && !downItemCandidate) { for (var i = popupMenu.menuItems.length - 1; i >= 0; i--) { var it = popupMenu.menuItems[i]; var hw = 200; // full width/2 var hh = 60; // full height/2 var isHovering = hitRect(x, y, it.x, it.y, hw, hh); // Apply hover effect for tea base buttons if (it.children[0] && it.children[0].tint === 0x1a237e) { // Tea base button if (isHovering && it.children[0].tint !== 0x3949ab) { it.children[0].tint = 0x3949ab; // Lighter blue on hover } else if (!isHovering && it.children[0].tint === 0x3949ab) { it.children[0].tint = 0x1a237e; // Return to original deep blue } } else { // Herb button hover effect var originalColor = 0x8B4513; // Default herb button color if (isHovering) { it.children[0].tint = 0x666666; // lighter shade on hover } else { it.children[0].tint = originalColor; // return to original color } } } } // 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) { // Create infinite drag ghost (no cost check needed) var tint = downItemCandidate.children[0] ? downItemCandidate.children[0].tint : 0x1565C0; heldItem = dragGhost = createDragGhostFromMenu(downItemCandidate.name, tint, 0, x, y); heldItem.dragOffsetX = 0; heldItem.dragOffsetY = 0; } } // Move the active drag item (ghost or regular) if (heldItem && heldItem.isDragging) { heldItem.x = x - (heldItem.dragOffsetX || 0); heldItem.y = y - (heldItem.dragOffsetY || 0); } }; function createDragGhost(item) { if (dragGhost) { game.removeChild(dragGhost); } dragGhost = new Container(); dragGhost.x = item.x; dragGhost.y = item.y; game.addChild(dragGhost); // Ghost background with transparency var ghostBg = dragGhost.attachAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: 150, height: 150, alpha: 0.7, tint: 0x444444 }); // Ghost text var ghostText = new Text2(item.name, { size: 28, fill: 0xFFFFFF }); ghostText.anchor.set(0.5, 0.5); ghostText.x = 0; ghostText.y = 0; dragGhost.addChild(ghostText); } function removeDragGhost() { if (dragGhost) { game.removeChild(dragGhost); dragGhost = null; } } game.up = function (x, y, obj) { // TAP on popup item -> add directly to cauldron if space available if (downItemCandidate && !heldItem) { var moved = Math.sqrt((x - downStartX) * (x - downStartX) + (y - downStartY) * (y - downStartY)); if (moved <= DRAG_THRESHOLD * 2) { // This is a tap, not a drag if (cauldron.ingredients.length < 3) { cauldron.ingredients.push(downItemCandidate.name); updateCauldronDisplay(); tween(cauldron, { tint: 0x44FF44 }, { duration: 200, onFinish: function onFinish() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); } }); } else { // Cauldron is full tween(cauldron, { tint: 0xFF4444 }, { duration: 200, onFinish: function onFinish() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); } }); } } 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) { cauldron.ingredients.push(heldItem.name); updateCauldronDisplay(); tween(cauldron, { tint: 0x44FF44 }, { duration: 200, onFinish: function onFinish() { 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 onFinish() { 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 onFinish() { tween(cauldron, { tint: 0xFFFFFF }, { duration: 200 }); } }); 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...'); updateHintBox(); patientTimer = 0; } // Selection display update removed // Check for game over condition (reputation too low) if (reputation <= 0) { LK.showGameOver(); } // Check for win condition (high score) if (currentScore >= 2000) { LK.showYouWin(); } // Handle empty text timer if (isShowingEmptyText && LK.ticks >= emptyTimer) { isShowingEmptyText = false; patientStatusText.setText(originalPatientText); updateHintBox(); } }; // Create draggable inventory items 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 : 25, // bigger menu text - minimum 25px 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; } // Inventory labels removed - ingredients now accessed through category buttons var inventoryItems = []; // Tea Bases - each has an element alignment var teaBases = { "Uplifting Tea Base": { name: "Uplifting Tea Base", element: "wind" }, "Earthy Tea Base": { name: "Earthy Tea Base", element: "earth" }, "Cooling Tea Base": { name: "Cooling Tea Base", element: "water" }, "Spicy Tea Base": { name: "Spicy Tea Base", element: "fire" } }; var baseItems = Object.keys(teaBases); /**** * Herb Data & Helpers ****/ var HERB_DATA = { "Sheep Bane": { type: "Plant", alignment: "Earth", element: "earth", description: "A nourishing plant with small white flowers found in lush meadows with plenty of sunshine." }, "Fuzzy Peeper": { type: "Plant", alignment: "Wind", element: "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", element: "fire", description: "A spiny red blossom that thrives in sun-scorched fields; its petals crackle faintly when crushed." }, "Common Snupe": { type: "Plant", alignment: "Earth", element: "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", element: "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", element: "fire", description: "A twisted root that glows faintly from within—often brewed to stoke warmth into cold limbs." }, "Frogwick": { type: "Plant", alignment: "Water", element: "water", description: "Rubbery, semi-aquatic herb found near marshes—tastes awful but soothes the throat like magic." }, "Demeter's Arrow": { type: "Plant", alignment: "Wind", element: "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", element: "water", description: "A long, fragrant stalk that grows in riverside soil; its boiled leaves soothe fevers and dampen restlessness." }, "Pipeweed": { type: "Plant", alignment: "Wind", element: "wind", description: "Fragrant and slightly numbing, this herb is often smoked by hill witches and daydreaming farmhands." }, "Kitty Mitten": { type: "Plant", alignment: "Fire", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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", element: "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'); } // Keep backward compatibility with old herbs object var herbs = {}; for (var herbName in HERB_DATA) { if (HERB_DATA.hasOwnProperty(herbName)) { herbs[herbName] = { element: HERB_DATA[herbName].element, type: HERB_DATA[herbName].type, description: HERB_DATA[herbName].description }; } } // --- COUNTER LAYER - TILED --- // Create multiple behind counter graphics to tile across screen width var counterTiles = []; var tileWidth = 200 * 8; // original width * scale var tilesNeeded = Math.ceil(2048 / tileWidth) + 2; // extra tiles to extend past edges var startX = -tileWidth; // start off-screen left for (var i = 0; i < tilesNeeded; i++) { var tile = game.attachAsset('behind_Counter', { anchorX: 0.5, anchorY: 0.5, x: startX + i * tileWidth, y: 2100, scaleX: 8, scaleY: 8 }); counterTiles.push(tile); } // --- SERVE BUTTON (placed after counter so it's visible) --- var serveButton = game.attachAsset('serveButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2450 }); serveButton.down = function (x, y, obj) { craftPotion(); }; // Serve button handler now handled in main game down event // Cauldron in center layer (depth 3, positioned lower on counter) var cauldron = game.attachAsset('cauldronZone', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1650, // omit width/height so it uses the asset's natural size scaleX: 2.5, scaleY: 2.5 }); cauldron.ingredients = []; // Potion Bottle positioned near cauldron - created after cauldron for proper layering var potionBottle = game.addChild(new PotionBottle()); potionBottle.x = 1024; potionBottle.y = 1100; potionBottle.visible = false; // Start hidden // Cauldron ingredients display var cauldronLabel = new Text2('', { size: 56, fill: 0x000000, fontWeight: 'bold', font: "'Times New Roman', serif" }); cauldronLabel.anchor.set(0.5, 0.5); cauldronLabel.x = 1024; cauldronLabel.y = 950; game.addChild(cauldronLabel); function updateCauldronDisplay() { if (cauldron.ingredients.length === 0) { cauldronLabel.setText(''); } 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 }); } // Inventory items are now only created through menu purchases // No default items in main play area // Ingredient category buttons in bottom layer - positioned below counter // Larger buttons with better spacing, centered horizontally var buttonWidth = 420; var buttonHeight = 150; var buttonSpacing = 80; var totalWidth = buttonWidth * 3 + buttonSpacing * 2; // 3 buttons with 2 spacings var startX = (2048 - totalWidth) / 2 + buttonWidth / 2; var teaBasesButton = game.attachAsset('teabaseButton', { anchorX: 0.5, anchorY: 0.5, x: startX, y: 2250 }); // teaBasesButtonText removed - using graphic asset only teaBasesButton.down = function (x, y, obj) { var teaBaseColors = []; for (var i = 0; i < baseItems.length; i++) { teaBaseColors.push(0x20B2AA); } popupMenu.showMenu('TEA BASES - 50 Gold Each', baseItems, teaBaseColors); }; var herbsButton = game.attachAsset('herbsButton', { anchorX: 0.5, anchorY: 0.5, x: startX + (buttonWidth + buttonSpacing), y: 2250 }); // herbsButtonText removed - using graphic asset only herbsButton.down = function (x, y, obj) { var herbColors = []; for (var i = 0; i < herbItems.length; i++) { herbColors.push(0xADFF2F); } popupMenu.showMenu('HERBS - 50 Gold Each', herbItems, herbColors); }; // Menu buttons in HUD layer - RESET and JOURNAL moved to right side, stacked vertically var resetButton = game.attachAsset('resetButton', { anchorX: 0.5, anchorY: 0.5, x: 1750, y: 150 }); resetButton.down = function (x, y, obj) { // Reset all game state reputation = 100; currentScore = 0; currentPatient = null; isProcessingTreatment = false; patientTimer = 0; treatmentTimer = 0; // Clear cauldron cauldron.ingredients = []; updateCauldronDisplay(); // Clear inventory items for (var i = inventoryItems.length - 1; i >= 0; i--) { game.removeChild(inventoryItems[i]); } inventoryItems = []; // Reset patient display patientStatusText.setText('Welcome! A patient awaits...'); updateHintBox(); // Reset patient tint patient.tint = 0xFFFFFF; // Reset reputation meter reputationText.setText(reputation.toString()); tween(meterFill, { width: 300 // 100/200 * 600 }, { duration: 300 }); tween(meterFill, { tint: 0xFFFF00 }, { duration: 300 }); // Set thermometer to normal setThermometer("Normal"); // Spawn new patient spawnNewPatient(); }; // Shop button removed per requirements // Empty button for tea mixture reset - positioned at bottom of cauldron var startOverButton = game.attachAsset('emptyButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1900 }); // startOverButtonText removed - using graphic asset only var originalPatientText = ''; var emptyTimer = 0; var isShowingEmptyText = false; startOverButton.down = function (x, y, obj) { cauldron.ingredients = []; updateCauldronDisplay(); // Store original text and show empty message if (!isShowingEmptyText) { originalPatientText = patientStatusText.text; patientStatusText.setText('Tea mixture cleared. Start fresh!'); updateHintBox(); emptyTimer = LK.ticks + 120; // 2 seconds at 60 FPS isShowingEmptyText = true; } }; // Empty button event handler now handled in main game down event // Create a top-level overlay container var overlayLayer = new Container(); game.addChild(overlayLayer); // Create popup menu inside overlay var popupMenu = overlayLayer.addChild(new PopupMenu()); // Create herb details popup inside overlay var herbPopup = overlayLayer.addChild(new HerbPopup()); // Category button event handlers now handled in main game down event // Reset button handler is now attached directly to the button above // Create journal popup inside overlay var journalPopup = overlayLayer.addChild(new JournalPopup()); // Journal button aligned with TEA BASES and HERBS buttons var journalButton = game.attachAsset('journalButton', { anchorX: 0.5, anchorY: 0.5, x: startX + (buttonWidth + buttonSpacing) * 2, y: 2250 }); // journalButtonText removed - using graphic asset only journalButton.down = function (x, y, obj) { journalPopup.showJournal(); }; // Journal button event handler now handled in main game down event // Ensure overlay layer is always on top after creating all other elements game.removeChild(overlayLayer); game.addChild(overlayLayer); // ensure overlay & popups are above HUD buttons // Function to refresh HUD visibility based on popup states function refreshHUD() { var anyPopupOpen = popupMenu.isVisible || herbPopup.isVisible || journalPopup.isVisible; journalButton.visible = !anyPopupOpen; // journalButtonText removed - using graphic asset only } // Wrap popup menu methods to hide/show journal button var _showMenu = popupMenu.showMenu; popupMenu.showMenu = function (title, items, colors) { _showMenu.call(popupMenu, title, items, colors); refreshHUD(); }; var _hideMenu = popupMenu.hideMenu; popupMenu.hideMenu = function () { _hideMenu.call(popupMenu); refreshHUD(); }; // Wrap herb popup methods to hide/show journal button var _showPopup = herbPopup.showPopup; herbPopup.showPopup = function (herbName, x, y) { _showPopup.call(herbPopup, herbName, x, y); refreshHUD(); }; var _hidePopup = herbPopup.hidePopup; herbPopup.hidePopup = function () { _hidePopup.call(herbPopup); refreshHUD(); }; // Wrap journal popup methods to hide/show journal button var _showJournal = journalPopup.showJournal; journalPopup.showJournal = function () { _showJournal.call(journalPopup); refreshHUD(); }; var _hideJournal = journalPopup.hideJournal; journalPopup.hideJournal = function () { _hideJournal.call(journalPopup); refreshHUD(); }; // Game state management var gameStarted = false; // How-to card overlay var howtoOverlay = new Container(); game.addChild(howtoOverlay); // Semi-transparent background var howtoBackground = howtoOverlay.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0.8, tint: 0x000000 }); // How-to card var howtoCard = howtoOverlay.attachAsset('howto_card', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200, width: 1536 // 75% of screen width (2048 * 0.75) to preserve aspect ratio }); // PLAY button var playButton = howtoOverlay.attachAsset('craftButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2000, width: 400, height: 120, tint: 0xFFFF00 }); var playButtonText = new Text2('PLAY', { size: 64, fill: 0xFFFFFF, font: "'Times New Roman', serif" }); playButtonText.anchor.set(0.5, 0.5); playButtonText.x = 1024; playButtonText.y = 2000; howtoOverlay.addChild(playButtonText); playButton.down = function (x, y, obj) { gameStarted = true; howtoOverlay.visible = false; LK.playMusic('BackgroundMusic'); spawnNewPatient(); }; // PLAY button handler now handled in main game down event // Initially show how-to card howtoOverlay.visible = true;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var HerbPopup = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = self.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.8,
tint: 0x000000
});
// Main popup window - larger and centered
var popupWindow = self.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
width: 1600,
height: 1000,
tint: 0x8B4513
});
// Close button (X) - larger
var closeButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1700,
y: 950,
width: 120,
height: 120,
tint: 0xFF0000
});
var closeButtonText = new Text2('X', {
size: 80,
fill: 0xFFFFFF
});
closeButtonText.anchor.set(0.5, 0.5);
closeButtonText.x = 1700;
closeButtonText.y = 950;
self.addChild(closeButtonText);
// Herb name title - much larger
var nameText = new Text2('', {
size: 72,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.x = 1024;
nameText.y = 1050;
self.addChild(nameText);
// Type text (Plant/Fungus)
var typeText = new Text2('', {
size: 48,
fill: 0xFFD700
});
typeText.anchor.set(0.5, 0.5);
typeText.x = 1024;
typeText.y = 1150;
self.addChild(typeText);
// Element text - larger
var elementText = new Text2('', {
size: 52,
fill: 0xFFFFFF
});
elementText.anchor.set(0.5, 0.5);
elementText.x = 1024;
elementText.y = 1250;
self.addChild(elementText);
// Description text - much larger and wrapped
var descriptionText = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
descriptionText.anchor.set(0.5, 0.5);
descriptionText.x = 1024;
descriptionText.y = 1500;
self.addChild(descriptionText);
self.isVisible = false;
self.showPopup = function (herbName, x, y) {
var herbData = HERB_DATA[herbName];
if (!herbData) return;
nameText.setText(herbName);
typeText.setText(herbData.type);
elementText.setText('Element: ' + herbData.alignment);
// Wrap long descriptions
var wrappedDesc = wrapTextByWords(herbData.description, 50);
descriptionText.setText(wrappedDesc);
// Color the element text based on alignment
var color = colorForAlignment(herbData.alignment);
elementText.tint = color;
self.visible = true;
self.isVisible = true;
};
self.hidePopup = function () {
self.visible = false;
self.isVisible = false;
};
// Close button handler
closeButton.down = function (x, y, obj) {
self.hidePopup();
};
// Overlay click to close
overlay.down = function (x, y, obj) {
self.hidePopup();
};
// Start hidden
self.visible = false;
return self;
});
// Shop button event handler removed per requirements
// === Herbalist's Journal with Table of Contents ===
var JournalPopup = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = self.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.8,
tint: 0x000000
});
// Main journal window - 75% of screen
var journalWindow = self.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
width: 1536,
// 75% of 2048
height: 2050,
// 75% of 2732
tint: 0xfdf6e3
});
// Close button (X)
var closeButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1700,
y: 500,
width: 90,
// 1.5x larger
height: 90,
// 1.5x larger
tint: 0xFF0000
});
var closeButtonText = new Text2('✕', {
size: 60,
// instead of 40, 1.5x larger
fill: 0xFFFFFF
});
closeButtonText.anchor.set(0.5, 0.5);
closeButtonText.x = 1700;
closeButtonText.y = 500;
self.addChild(closeButtonText);
// Table of Contents View
var contentsTitle = new Text2('Herbalist\'s Journal', {
size: 64,
fill: 0x000000
});
contentsTitle.anchor.set(0.5, 0.5);
contentsTitle.x = 1024;
contentsTitle.y = 650;
self.addChild(contentsTitle);
var contentsSubtitle = new Text2('Contents', {
size: 48,
fill: 0x444444
});
contentsSubtitle.anchor.set(0.5, 0.5);
contentsSubtitle.x = 1024;
contentsSubtitle.y = 750;
self.addChild(contentsSubtitle);
// Content section buttons - improved styling
var herbsContentButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 950,
width: 650,
// Increased width
height: 180,
// Increased height
tint: 0x222222 // Dark background for contrast
});
var herbsContentText = new Text2('Herbs & Fungi', {
size: 72,
// Increased from 40
fill: 0xFFFFFF // White text for contrast
});
herbsContentText.anchor.set(0.5, 0.5);
herbsContentText.x = 1024;
herbsContentText.y = 950;
self.addChild(herbsContentText);
var basesContentButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200,
// was 1100, now spaced further down
width: 650,
// Increased width
height: 180,
// Increased height
tint: 0x222222 // Dark background for contrast
});
var basesContentText = new Text2('Tea Bases', {
size: 72,
// Increased from 40
fill: 0xFFFFFF // White text for contrast
});
basesContentText.anchor.set(0.5, 0.5);
basesContentText.x = 1024;
basesContentText.y = 1200; // was 1100, now spaced further down
self.addChild(basesContentText);
var illnessesContentButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1450,
// was 1250, now spaced further down
width: 650,
// Increased width
height: 180,
// Increased height
tint: 0x222222 // Dark background for contrast
});
var illnessesContentText = new Text2('Illnesses', {
size: 72,
// Increased from 40
fill: 0xFFFFFF // White text for contrast
});
illnessesContentText.anchor.set(0.5, 0.5);
illnessesContentText.x = 1024;
illnessesContentText.y = 1450; // was 1250, now spaced further down
self.addChild(illnessesContentText);
// Section View Elements (hidden initially)
var sectionTitle = new Text2('', {
size: 80,
// larger
fill: 0x000000,
// darker
fontWeight: 'bold'
});
sectionTitle.anchor.set(0.5, 0.5);
sectionTitle.x = 1024;
sectionTitle.y = 750;
self.addChild(sectionTitle);
var sectionType = new Text2('', {
size: 84,
// larger
fill: 0x006400 // dark green
});
sectionType.anchor.set(0.5, 0.5);
sectionType.x = 1024;
sectionType.y = 950;
self.addChild(sectionType);
var sectionElement = new Text2('', {
size: 84,
fill: 0x000000 // darker, readable
});
sectionElement.anchor.set(0.5, 0.5);
sectionElement.x = 1024;
sectionElement.y = 1100;
self.addChild(sectionElement);
var sectionDescription = new Text2('', {
size: 75,
// substantially larger
fill: 0x000000,
// darker
align: 'center'
});
sectionDescription.anchor.set(0.5, 0);
sectionDescription.x = 1024;
sectionDescription.y = 1300;
self.addChild(sectionDescription);
// Navigation buttons for sections
var backToContentsButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 750,
y: 2200,
width: 300,
height: 100,
tint: 0x222222
});
var backToContentsText = new Text2('⬅ Back', {
size: 42,
fill: 0xFFFFFF
});
backToContentsText.anchor.set(0.5, 0.5);
backToContentsText.x = 750;
backToContentsText.y = 2200;
self.addChild(backToContentsText);
var nextEntryButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1300,
y: 2200,
width: 300,
height: 100,
tint: 0x222222
});
var nextEntryText = new Text2('Next ➡', {
size: 42,
fill: 0xFFFFFF
});
nextEntryText.anchor.set(0.5, 0.5);
nextEntryText.x = 1300;
nextEntryText.y = 2200;
self.addChild(nextEntryText);
// State management
self.currentView = 'contents'; // 'contents' or 'section'
self.currentSection = '';
self.currentIndex = 0;
self.viewHistory = []; // Track navigation history for back button
self.journalData = {
herbs: Object.keys(HERB_DATA),
teaBases: Object.keys(teaBases),
illnesses: Object.keys(ILLNESS_DATA)
};
self.isVisible = false;
// Show/hide content elements
self.showContents = function () {
self.currentView = 'contents';
// Show contents elements
contentsTitle.visible = true;
contentsSubtitle.visible = true;
herbsContentButton.visible = true;
herbsContentText.visible = true;
basesContentButton.visible = true;
basesContentText.visible = true;
illnessesContentButton.visible = true;
illnessesContentText.visible = true;
// Hide section elements
sectionTitle.visible = false;
sectionType.visible = false;
sectionElement.visible = false;
sectionDescription.visible = false;
backToContentsButton.visible = false;
backToContentsText.visible = false;
nextEntryButton.visible = false;
nextEntryText.visible = false;
};
self.showSection = function (section) {
// Save current state to history before changing
if (self.currentView === 'contents') {
self.viewHistory.push({
view: 'contents'
});
} else if (self.currentView === 'section') {
self.viewHistory.push({
view: 'section',
section: self.currentSection,
index: self.currentIndex
});
}
self.currentView = 'section';
self.currentSection = section;
self.currentIndex = 0;
// Hide contents elements
contentsTitle.visible = false;
contentsSubtitle.visible = false;
herbsContentButton.visible = false;
herbsContentText.visible = false;
basesContentButton.visible = false;
basesContentText.visible = false;
illnessesContentButton.visible = false;
illnessesContentText.visible = false;
// Show section elements
sectionTitle.visible = true;
sectionType.visible = true;
sectionElement.visible = true;
sectionDescription.visible = true;
backToContentsButton.visible = true;
backToContentsText.visible = true;
nextEntryButton.visible = true;
nextEntryText.visible = true;
self.updateSectionContent();
};
self.updateSectionContent = function () {
var entries = self.journalData[self.currentSection];
var entry = entries[self.currentIndex];
if (self.currentSection === 'herbs') {
var herbData = HERB_DATA[entry];
sectionTitle.setText(entry.toUpperCase());
sectionType.setText(herbData.type);
sectionElement.setText("Element: " + herbData.alignment);
sectionDescription.setText(wrapTextByWords(herbData.description, 40));
} else if (self.currentSection === 'teaBases') {
var baseData = teaBases[entry];
sectionTitle.setText(entry.toUpperCase());
sectionType.setText("Tea Base");
sectionElement.setText("Element: " + baseData.element.charAt(0).toUpperCase() + baseData.element.slice(1));
sectionDescription.setText(wrapTextByWords("A light base for brewing teas, often combined with matching herbs.", 40));
} else if (self.currentSection === 'illnesses') {
var illnessData = ILLNESS_DATA[entry];
sectionTitle.setText(illnessData.name.toUpperCase());
sectionType.setText("Illness");
var elementText = "Element: ";
for (var i = 0; i < illnessData.element_imbalance.length; i++) {
if (i > 0) elementText += " + ";
elementText += illnessData.element_imbalance[i].charAt(0).toUpperCase() + illnessData.element_imbalance[i].slice(1);
}
sectionElement.setText(elementText);
var cureText = "Cure: Use ";
for (var i = 0; i < illnessData.element_imbalance.length; i++) {
if (i > 0) cureText += " + ";
cureText += illnessData.element_imbalance[i];
}
cureText += " base with herbs.";
var desc = "Description: " + illnessData.description + "\n\n" + cureText;
sectionDescription.setText(wrapTextByWords(desc, 40));
}
};
self.nextEntry = function () {
var entries = self.journalData[self.currentSection];
// Save current state before moving forward
self.viewHistory.push({
view: 'section',
section: self.currentSection,
index: self.currentIndex
});
if (self.currentIndex < entries.length - 1) {
self.currentIndex++;
} else {
self.currentIndex = 0; // Loop back to first
}
self.updateSectionContent();
};
self.showJournal = function () {
self.visible = true;
self.isVisible = true;
self.viewHistory = []; // Clear history when opening journal fresh
self.showContents();
};
self.hideJournal = function () {
self.visible = false;
self.isVisible = false;
};
// Button handlers
herbsContentButton.down = function (x, y, obj) {
self.showSection('herbs');
};
basesContentButton.down = function (x, y, obj) {
self.showSection('teaBases');
};
illnessesContentButton.down = function (x, y, obj) {
self.showSection('illnesses');
};
backToContentsButton.down = function (x, y, obj) {
// Go back to previous view in history
if (self.viewHistory.length > 0) {
var previousState = self.viewHistory.pop();
if (previousState.view === 'contents') {
self.showContents();
} else if (previousState.view === 'section') {
self.currentView = 'section';
self.currentSection = previousState.section;
self.currentIndex = previousState.index;
// Hide contents elements
contentsTitle.visible = false;
contentsSubtitle.visible = false;
herbsContentButton.visible = false;
herbsContentText.visible = false;
basesContentButton.visible = false;
basesContentText.visible = false;
illnessesContentButton.visible = false;
illnessesContentText.visible = false;
// Show section elements
sectionTitle.visible = true;
sectionType.visible = true;
sectionElement.visible = true;
sectionDescription.visible = true;
backToContentsButton.visible = true;
backToContentsText.visible = true;
nextEntryButton.visible = true;
nextEntryText.visible = true;
self.updateSectionContent();
}
} else {
// No history, do nothing (do not fallback to showContents)
}
};
nextEntryButton.down = function (x, y, obj) {
self.nextEntry();
};
// Close button handler
closeButton.down = function (x, y, obj) {
self.hideJournal();
};
// Overlay click to close
overlay.down = function (x, y, obj) {
self.hideJournal();
};
// Start hidden
self.visible = false;
return self;
});
// Create journal popup
var Patient = Container.expand(function () {
var self = Container.call(this);
// Randomize patient graphic
var patientGraphics = ['patient', 'patient1', 'patient2'];
var randomGraphic = patientGraphics[Math.floor(Math.random() * patientGraphics.length)];
var patientBody = self.attachAsset(randomGraphic, {
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;
var illness = ILLNESS_DATA[type];
if (illness) {
// if (self.symptomGlow) self.symptomGlow.tint = illness.symptom_color;
self.element_imbalance = illness.element_imbalance;
self.goldReward = illness.gold_reward;
self.illnessName = illness.name;
self.description = illness.description;
}
};
// reactToPotion method removed - cure logic now handled in craftPotion function
return self;
});
var PopupMenu = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = self.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.7,
tint: 0x000000
});
// Main popup window - 75% of screen size
var popupWindow = self.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
width: 1536,
// 75% of 2048
height: 2050,
// 75% of 2732
tint: 0x8B4513
});
// Close button (X)
var closeButton = self.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1600,
y: 600,
width: 80,
height: 80,
tint: 0xFF0000
});
var closeButtonText = new Text2('X', {
size: 40,
fill: 0xFFFFFF
});
closeButtonText.anchor.set(0.5, 0.5);
closeButtonText.x = 1600;
closeButtonText.y = 600;
self.addChild(closeButtonText);
// Title text - larger for better visibility
var titleText = new Text2('', {
size: 72,
// Increased from 50 to 72
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 700;
self.addChild(titleText);
self.menuItems = [];
self.isVisible = false;
self.showMenu = function (title, items, itemColors) {
titleText.setText(title);
self.visible = true;
self.isVisible = true;
// Clear existing menu items
for (var i = 0; i < self.menuItems.length; i++) {
game.removeChild(self.menuItems[i]);
}
self.menuItems = [];
// Special layout for Tea Bases (centered vertical stack)
if (title.indexOf('TEA BASES') !== -1) {
var buttonWidth = 800; // 60% of screen width (1536 * 0.6)
var buttonHeight = 120;
var buttonSpacing = 40;
var startY = 1000;
for (var i = 0; i < items.length; i++) {
var y = startY + i * (buttonHeight + buttonSpacing);
// Create button-style container
var menuItem = new Container();
menuItem.x = 1024; // Center horizontally
menuItem.y = y;
game.addChild(menuItem);
// Create button background
var buttonBg = menuItem.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
width: buttonWidth,
height: buttonHeight,
tint: 0x1a237e // Deep blue for tea bases
});
// Create centered button text
var buttonText = new Text2(items[i], {
size: 72,
// Larger text for tea bases
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
buttonText.x = 0;
buttonText.y = 0;
menuItem.addChild(buttonText);
menuItem.name = items[i];
menuItem.draggable = true;
menuItem.type = 'ingredient';
menuItem.isDragging = false;
menuItem.originalX = 1024;
menuItem.originalY = y;
menuItem.isMenuPurchase = true;
menuItem.cost = 50;
self.menuItems.push(menuItem);
}
} else {
// Regular grid layout for other items (herbs, etc.)
var itemsPerRow = 3;
var buttonWidth = 400;
var buttonHeight = 120;
var buttonSpacing = 60;
var totalRowWidth = itemsPerRow * buttonWidth + (itemsPerRow - 1) * buttonSpacing;
var startX = 1024 - totalRowWidth / 2 + buttonWidth / 2;
var startY = 1050;
var rowSpacing = 150;
for (var i = 0; i < items.length; i++) {
var row = Math.floor(i / itemsPerRow);
var col = i % itemsPerRow;
var x = startX + col * (buttonWidth + buttonSpacing);
var y = startY + row * rowSpacing;
// Create button-style container
var menuItem = new Container();
menuItem.x = x;
menuItem.y = y;
game.addChild(menuItem);
// Create button background
var buttonBg = menuItem.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
width: buttonWidth,
height: buttonHeight,
tint: itemColors[i] || 0x8B4513
});
// Create centered button text - larger for better visibility
var buttonText = new Text2(items[i], {
size: 48,
// Increased from 36 to 48
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
buttonText.x = 0;
buttonText.y = 0;
menuItem.addChild(buttonText);
menuItem.name = items[i];
menuItem.draggable = true;
menuItem.type = 'ingredient';
menuItem.isDragging = false;
menuItem.originalX = x;
menuItem.originalY = y;
menuItem.isMenuPurchase = true;
menuItem.cost = 50; // Cost to purchase from menu
self.menuItems.push(menuItem);
}
}
};
self.hideMenu = function () {
self.visible = false;
self.isVisible = false;
// Remove menu items
for (var i = 0; i < self.menuItems.length; i++) {
game.removeChild(self.menuItems[i]);
}
self.menuItems = [];
};
self.showDetails = function (itemName) {
if (HERB_DATA[itemName]) {
herbPopup.showPopup(itemName, 0, 0);
}
};
// Close button handler
closeButton.down = function (x, y, obj) {
self.hideMenu();
};
// Overlay click to close
overlay.down = function (x, y, obj) {
self.hideMenu();
};
// Start hidden
self.visible = false;
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
****/
// Add global button styling
var buttonStyle = {
fontSize: 20,
padding: "12px 24px",
borderRadius: 10,
backgroundColor: "#444",
color: "white",
cursor: "pointer"
};
var gameBackground = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// === Tap vs Drag state ===
var downItemCandidate = null;
var downStartX = 0,
downStartY = 0;
var DRAG_THRESHOLD = 15; // smoother tap vs drag
// helper: rectangular hit test
function hitRect(x, y, cx, cy, halfW, halfH) {
return Math.abs(x - cx) <= halfW && Math.abs(y - cy) <= halfH;
}
// Helper function for exact, order-agnostic set matching
function containsAll(required, brewed) {
for (var i = 0; i < required.length; i++) {
if (brewed.indexOf(required[i]) === -1) {
return false;
}
}
return true;
}
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: 0xFFD700,
// bright gold highlight when grabbed
alpha: 0.9,
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 = 0;
ghost.dragOffsetX = 0;
ghost.dragOffsetY = 0;
// stronger pop so it feels more obvious
tween(ghost, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100
});
return ghost;
}
var reputation = 100;
var currentScore = 0;
var currentPatient = null;
var isProcessingTreatment = false;
// Menu buttons are already implemented at the top of the screen:
// - Instructions button (blue) at x=400, y=100
// - New Game button (red) at x=1024, y=100
// - Shop button (green) at x=1648, y=100
// All buttons have proper styling, text labels, and event handlers
// HUD / BOTTOM LEFT LAYER - Reputation Meter
var meterBackground = new Container();
meterBackground.x = 120;
meterBackground.y = -120;
LK.gui.bottomLeft.addChild(meterBackground);
// Meter background bar
var meterBg = meterBackground.attachAsset('craftButton', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 600,
height: 80,
tint: 0x444444
});
// Meter fill bar
var meterFill = meterBackground.attachAsset('craftButton', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 600,
height: 80,
tint: 0x00FF00
});
// Reputation label
var reputationLabel = new Text2('Reputation', {
size: 64,
fill: 0xFFFFFF,
font: "'Times New Roman', serif"
});
reputationLabel.anchor.set(0, 1);
reputationLabel.x = 120;
reputationLabel.y = -130;
LK.gui.bottomLeft.addChild(reputationLabel);
// Reputation value text
var reputationText = new Text2(reputation.toString(), {
size: 56,
fill: 0xFFFFFF,
font: "'Times New Roman', serif"
});
reputationText.anchor.set(0.5, 0.5);
reputationText.x = 420;
reputationText.y = -90;
LK.gui.bottomLeft.addChild(reputationText);
// CENTER LAYER - Patient + Cauldron + Dialogue
var patient = game.addChild(new Patient());
patient.x = 1024;
patient.y = 600; // Moved down by 100px
/****
* Thermometer System
****/
// Container for thermometer
var thermometer = new Container();
thermometer.x = 200; // position on screen
thermometer.y = 700; // position directly behind symptom indicator
game.addChild(thermometer);
// Thermometer frame graphic (with transparent center)
var thermometerFrame = thermometer.attachAsset('thermometerGraphic', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
// Red mercury fill (rectangle)
var mercuryFill = thermometer.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 1,
// grows upward
x: 0,
y: thermometerFrame.height / 2,
// bottom of thermometer frame
width: 40,
// thickness of the red line
height: 0,
// start empty
tint: 0xFF0000
});
// Function to set thermometer level based on patient temperature
function setThermometer(temp) {
var maxHeight = thermometerFrame.height - 40;
// subtract a little so it doesn't spill over top of the glass
var targetHeight = 0;
if (temp === "Cold") {
targetHeight = maxHeight * 0.25; // low fill
} else if (temp === "Normal") {
targetHeight = maxHeight * 0.55; // mid fill
} else if (temp === "Hot") {
targetHeight = maxHeight * 0.95; // almost full
}
// Animate mercury height; bottom stays fixed because anchorY = 1
tween(mercuryFill, {
height: targetHeight
}, {
duration: 600,
easing: tween.easeOut
});
}
// Create symptom indicator positioned near patient (moved after counter for proper layering)
var symptomIndicator = game.attachAsset('symptomIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 700
});
// Link symptom indicator to patient
patient.symptomGlow = symptomIndicator;
// Patient status text with background - positioned below patient portrait
var hintBox = game.attachAsset('patientCommentBox', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1050
});
// Hint text
var patientStatusText = new Text2('Welcome! A patient awaits...', {
size: 70,
// increased from 42
fill: 0xFFFFFF,
align: 'center',
font: "'Times New Roman', serif"
});
patientStatusText.anchor.set(0.5, 0.5);
patientStatusText.x = 1024;
patientStatusText.y = 1050;
game.addChild(patientStatusText);
// BOTTOM LAYER - Ingredient Buttons + Craft Button (craft button moved after counter layer)
// Wheel labels and selection display removed
// Illness definitions with element imbalances and cure rules
var ILLNESS_DATA = {
"hollowshiver": {
id: "hollowshiver",
name: "Hollowshiver",
description: "A creeping cold that settles in the bones, no matter how many blankets are piled on. Common after wandering too long near mushroom circles or in moonlit glades. Warming the body will help.",
temperature: "Cold",
causes: ["Getting too cold", "Too many water herbs", "Swimming in cold waters"],
cure: "Brew fire-aligned broth with Kitty Mitten, Sommeral, or Brindleroot",
symptoms: ["I took a swim, but the water was so cold…", "I feel a cold damp in my bones…"],
element_imbalance: ["fire"],
symptom_color: 0x4444FF,
gold_reward: 120
},
"driftlimb": {
id: "driftlimb",
name: "Driftlimb",
description: "A peculiar tiredness that makes even teacups feel too heavy. Believed to come from skipping too many meals or carrying burdens not your own. A wind-based approach will help uplift the body.",
temperature: "Normal",
causes: ["Working too hard", "Being ill for a long time"],
cure: "Brew wind-aligned tea with Fuzzy Peeper, Demeter's Arrow or Pipeweed",
symptoms: ["I've been working so hard lately, my arms feel so heavy…", "I feel like I have no muscles in my legs…"],
element_imbalance: ["wind"],
symptom_color: 0xFFFF44,
gold_reward: 130
},
"greyheart": {
id: "greyheart",
name: "Greyheart",
description: "A sadness with no clear shape. Often spoken of in hushed tones as the malaise of quiet souls or lonely seasons. To heal Greyheart, one must nourish the body, warm the limbs, and lift the spirit at the same time.",
temperature: "Normal",
causes: ["Tragedy of the heart", "Ennui"],
cure: "Earthy Tea Base with Kitty Mitten and Demeter's Arrow",
symptoms: ["I feel so low. As if I'm haunted somehow by the troubles of my past…", "This weather has been hard for me… I feel so… I don't know.", "I feel as if I've been flattened or hollowed out. Something is missing and I don't know what…"],
element_imbalance: ["earth"],
symptom_color: 0x888888,
gold_reward: 140
},
"the_wilt": {
id: "the_wilt",
name: "The Wilt",
description: "When one's spirit feels dim and steps feel cloud-soft. A common complaint after long travels or encounters with annoying relatives. The wilt requires a wind based tea.",
temperature: "Normal",
causes: ["Not sleeping enough", "Over excitement"],
cure: "Uplifting Tea Base with Demeter's Arrow and Fuzzy Peeper",
symptoms: ["I'm so tired no matter how much I sleep."],
element_imbalance: ["wind"],
symptom_color: 0xAAAAFF,
gold_reward: 110
},
"wretching_waters": {
id: "wretching_waters",
name: "Wretching Waters",
description: "A twisting in the gut, often after eating something wild or questionably cooked. One needs a strong, nourishing broth.",
temperature: "Hot",
causes: ["Rotten food", "Ingesting poisonous fungi"],
cure: "Earthy Tea Base with Sheep's Bane and Common Snupe",
symptoms: ["I've been in the bathroom all morning!", "My stomach…. uurrgghhh…"],
element_imbalance: ["earth"],
symptom_color: 0x44FF44,
gold_reward: 150
},
"unmovable_phlegm": {
id: "unmovable_phlegm",
name: "Unmovable Phlegm",
description: "More irritating than dangerous, this thick muck often makes sleep difficult. A combination of heat and wind is necessary to clear it.",
temperature: "Normal",
causes: ["Pollen exposure", "Allergies to animals"],
cure: "Spicy Tea Base with Demeter's Arrow and Fuzzy Peeper",
symptoms: ["I can't stop sneezing since I went to that petting zoo!", "I feel like I can't breathe, especially at night.", "This dust in the air is making me feel awful!"],
element_imbalance: ["fire", "wind"],
symptom_color: 0xFFCC44,
gold_reward: 125
},
"dagger_hex": {
id: "dagger_hex",
name: "Dagger Hex",
description: "A stiffness that clings to joints and limbs like thistleburs. Believed to follow poor posture or sleeping under cursed trees. Heat and nourish the body to encourage healing.",
temperature: "Normal",
causes: ["Overexertion"],
cure: "Spicy Tea Base with Sheep's Bane and Common Snupe",
symptoms: ["I've got this pain in my back!", "Ack! My neck is killing me. I've been working too hard…"],
element_imbalance: ["fire"],
symptom_color: 0xFF8844,
gold_reward: 135
},
"witchrattle": {
id: "witchrattle",
name: "Witchrattle",
description: "A dry, scratchy cough that echoes oddly through the throat. Caught mostly in deep winter and under cold moons. The treatment must warm the body but cool the throat.",
temperature: "Cold",
causes: ["Getting too cold", "Contact with sick people"],
cure: "Spicy Tea Base with Frogwick and Geneva's Dewdrop or River Leek",
symptoms: ["Hack hack! Excuse my cough!", "These frosty evenings have been so hard on my lungs…"],
element_imbalance: ["fire"],
symptom_color: 0x99CCFF,
gold_reward: 120
},
"twiddlecurse": {
id: "twiddlecurse",
name: "Twiddlecurse",
description: "A jittery, jumpy itch in the fingers and feet. Common among those plagued by tragedies or nightmares. A cooling tea is required.",
temperature: "Normal",
causes: ["Stress", "Too many wind-aligned herbs"],
cure: "Cooling Tea Base with Geneva's Dewdrop and Frogwick",
symptoms: ["Demeter's Arrow is so good but I think I had too many…", "I'm so restless lately, I can't stop fidgeting!"],
element_imbalance: ["water"],
symptom_color: 0x66CCFF,
gold_reward: 115
},
"purging_rot": {
id: "purging_rot",
name: "Purging Rot",
description: "A slow-burning ailment marked by greenish skin and sweating. Often follows the consumption of misidentified mushrooms or cursed teas. Expel the rot but make sure to also nourish the body.",
temperature: "Hot",
causes: ["Poisonous mushrooms", "Cursed teas"],
cure: "Uplifting Tea Base with Sheep's Bane and Common Snupe",
symptoms: ["I must have eaten something that went bad…", "Purple mushrooms are safe to eat… right?"],
element_imbalance: ["wind"],
symptom_color: 0x77DD77,
gold_reward: 160
}
};
var symptoms = Object.keys(ILLNESS_DATA);
var patientTimer = 0;
var treatmentTimer = 0;
function updateHintBox() {
// Resize width and height based on text size
hintBox.width = patientStatusText.width + 80; // padding left/right
hintBox.height = patientStatusText.height + 40; // padding top/bottom
// Keep box centered with the text
hintBox.x = patientStatusText.x;
hintBox.y = patientStatusText.y;
}
// Define spawnNewPatient function
function spawnNewPatient() {
if (currentPatient) return;
var randomSymptom = symptoms[Math.floor(Math.random() * symptoms.length)];
patient.setSymptom(randomSymptom);
currentPatient = randomSymptom;
var illness = ILLNESS_DATA[randomSymptom];
if (illness) {
var symptom = illness.symptoms[Math.floor(Math.random() * illness.symptoms.length)];
patientStatusText.setText(symptom);
updateHintBox();
// Set thermometer based on patient temperature - call it properly
setThermometer(illness.temperature);
} //{8T} {8U} {8V} {8W}
else {
patientStatusText.setText('A new patient has arrived!');
updateHintBox();
// Set default temperature for unknown illnesses
setThermometer("Normal");
}
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 brewTea(baseName, herb1Name, herb2Name) {
if (!teaBases[baseName]) return null;
var base = teaBases[baseName];
var herb1 = HERB_DATA[herb1Name];
var herb2 = HERB_DATA[herb2Name];
if (!herb1 || !herb2 || herb1Name === herb2Name) return null;
// Gather all elements in the mix
var elements = [base.element, herb1.element, herb2.element];
// Pure tea (all the same element)
if (herb1.element === herb2.element && herb1.element === base.element) {
return {
name: base.name + " + " + herb1Name + " + " + herb2Name,
elements: [base.element]
};
}
// Hybrid tea (mixed elements)
var uniqueElements = [];
for (var i = 0; i < elements.length; i++) {
if (uniqueElements.indexOf(elements[i]) === -1) {
uniqueElements.push(elements[i]);
}
}
return {
name: base.name + " + " + herb1Name + " + " + herb2Name,
elements: uniqueElements
};
}
function craftPotion() {
if (!currentPatient || isProcessingTreatment || cauldron.ingredients.length !== 3) {
return;
}
isProcessingTreatment = true;
var baseName = cauldron.ingredients[0];
var herb1Name = cauldron.ingredients[1];
var herb2Name = cauldron.ingredients[2];
var brewedTea = brewTea(baseName, herb1Name, herb2Name);
var repChange = 0;
if (brewedTea) {
var illness = ILLNESS_DATA[currentPatient];
if (illness) {
var required = illness.element_imbalance; // always an array
var brewed = brewedTea.elements;
// Use contains-all matching - brew must contain at least all required elements
var cureOK = containsAll(required, brewed);
if (cureOK) {
repChange = illness.gold_reward;
tween(patient, {
tint: 0x90EE90
}, {
duration: 1000
});
LK.getSound('success').play();
// ✅ patient is cured → clear them
currentPatient = null;
patientStatusText.setText("Patient cured! +" + repChange + " reputation");
updateHintBox();
treatmentTimer = LK.ticks + 120; // delay before next patient
} else {
// ❌ Wrong tea → penalty, patient leaves
repChange = -20;
tween(patient, {
tint: 0xFF6666
}, {
duration: 1000
});
LK.getSound('failure').play();
patientStatusText.setText("That didn't work... -" + Math.abs(repChange) + " reputation");
updateHintBox();
// ✅ Remove patient and start timer for next one
currentPatient = null;
treatmentTimer = LK.ticks + 120; // delay before next patient
}
}
} else {
// Invalid tea combination (not a real brew)
repChange = -10; // smaller penalty
tween(patient, {
tint: 0xFF6666
}, {
duration: 1000
});
LK.getSound('failure').play();
LK.getSound('failure').play();
patientStatusText.setText("Invalid brew! -" + Math.abs(repChange) + " reputation");
updateHintBox();
isProcessingTreatment = false; // allow retry
}
reputation += repChange;
currentScore += Math.max(0, repChange);
// Update reputation meter
var maxReputation = 200; // Maximum reputation for meter scaling
var meterWidth = Math.max(0, Math.min(600, reputation / maxReputation * 600));
// Animate meter fill width change
tween(meterFill, {
width: meterWidth
}, {
duration: 500,
easing: tween.easeOut
});
// Update meter color based on reputation level
var meterColor = 0x00FF00; // Green
if (reputation < 50) {
meterColor = 0xFF0000; // Red
} else if (reputation < 100) {
meterColor = 0xFFFF00; // Yellow
}
tween(meterFill, {
tint: meterColor
}, {
duration: 300
});
// Update reputation text value
reputationText.setText(reputation.toString());
cauldron.ingredients = [];
updateCauldronDisplay();
}
var heldItem = null;
var dragGhost = null;
game.down = function (x, y, obj) {
// Check main game buttons first with proper hit boxes
// 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];
// Use full button dimensions for tea bases
var hw, hh;
if (it.children[0] && it.children[0].tint === 0x1a237e) {
// Tea base button - use full dimensions
hw = 400; // full buttonWidth from tea base creation
hh = 60; // full buttonHeight/2 from tea base creation
} else {
// Regular herb button
hw = 200; // full buttonWidth/2 from herb creation
hh = 60; // full buttonHeight/2 from herb creation
}
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) {
// Handle hover effects for tea base buttons
if (popupMenu.isVisible && !heldItem && !downItemCandidate) {
for (var i = popupMenu.menuItems.length - 1; i >= 0; i--) {
var it = popupMenu.menuItems[i];
var hw = 200; // full width/2
var hh = 60; // full height/2
var isHovering = hitRect(x, y, it.x, it.y, hw, hh);
// Apply hover effect for tea base buttons
if (it.children[0] && it.children[0].tint === 0x1a237e) {
// Tea base button
if (isHovering && it.children[0].tint !== 0x3949ab) {
it.children[0].tint = 0x3949ab; // Lighter blue on hover
} else if (!isHovering && it.children[0].tint === 0x3949ab) {
it.children[0].tint = 0x1a237e; // Return to original deep blue
}
} else {
// Herb button hover effect
var originalColor = 0x8B4513; // Default herb button color
if (isHovering) {
it.children[0].tint = 0x666666; // lighter shade on hover
} else {
it.children[0].tint = originalColor; // return to original color
}
}
}
}
// 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) {
// Create infinite drag ghost (no cost check needed)
var tint = downItemCandidate.children[0] ? downItemCandidate.children[0].tint : 0x1565C0;
heldItem = dragGhost = createDragGhostFromMenu(downItemCandidate.name, tint, 0, x, y);
heldItem.dragOffsetX = 0;
heldItem.dragOffsetY = 0;
}
}
// Move the active drag item (ghost or regular)
if (heldItem && heldItem.isDragging) {
heldItem.x = x - (heldItem.dragOffsetX || 0);
heldItem.y = y - (heldItem.dragOffsetY || 0);
}
};
function createDragGhost(item) {
if (dragGhost) {
game.removeChild(dragGhost);
}
dragGhost = new Container();
dragGhost.x = item.x;
dragGhost.y = item.y;
game.addChild(dragGhost);
// Ghost background with transparency
var ghostBg = dragGhost.attachAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
width: 150,
height: 150,
alpha: 0.7,
tint: 0x444444
});
// Ghost text
var ghostText = new Text2(item.name, {
size: 28,
fill: 0xFFFFFF
});
ghostText.anchor.set(0.5, 0.5);
ghostText.x = 0;
ghostText.y = 0;
dragGhost.addChild(ghostText);
}
function removeDragGhost() {
if (dragGhost) {
game.removeChild(dragGhost);
dragGhost = null;
}
}
game.up = function (x, y, obj) {
// TAP on popup item -> add directly to cauldron if space available
if (downItemCandidate && !heldItem) {
var moved = Math.sqrt((x - downStartX) * (x - downStartX) + (y - downStartY) * (y - downStartY));
if (moved <= DRAG_THRESHOLD * 2) {
// This is a tap, not a drag
if (cauldron.ingredients.length < 3) {
cauldron.ingredients.push(downItemCandidate.name);
updateCauldronDisplay();
tween(cauldron, {
tint: 0x44FF44
}, {
duration: 200,
onFinish: function onFinish() {
tween(cauldron, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else {
// Cauldron is full
tween(cauldron, {
tint: 0xFF4444
}, {
duration: 200,
onFinish: function onFinish() {
tween(cauldron, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
}
}
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) {
cauldron.ingredients.push(heldItem.name);
updateCauldronDisplay();
tween(cauldron, {
tint: 0x44FF44
}, {
duration: 200,
onFinish: function onFinish() {
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 onFinish() {
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 onFinish() {
tween(cauldron, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
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...');
updateHintBox();
patientTimer = 0;
}
// Selection display update removed
// Check for game over condition (reputation too low)
if (reputation <= 0) {
LK.showGameOver();
}
// Check for win condition (high score)
if (currentScore >= 2000) {
LK.showYouWin();
}
// Handle empty text timer
if (isShowingEmptyText && LK.ticks >= emptyTimer) {
isShowingEmptyText = false;
patientStatusText.setText(originalPatientText);
updateHintBox();
}
};
// Create draggable inventory items
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 : 25,
// bigger menu text - minimum 25px
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;
}
// Inventory labels removed - ingredients now accessed through category buttons
var inventoryItems = [];
// Tea Bases - each has an element alignment
var teaBases = {
"Uplifting Tea Base": {
name: "Uplifting Tea Base",
element: "wind"
},
"Earthy Tea Base": {
name: "Earthy Tea Base",
element: "earth"
},
"Cooling Tea Base": {
name: "Cooling Tea Base",
element: "water"
},
"Spicy Tea Base": {
name: "Spicy Tea Base",
element: "fire"
}
};
var baseItems = Object.keys(teaBases);
/****
* Herb Data & Helpers
****/
var HERB_DATA = {
"Sheep Bane": {
type: "Plant",
alignment: "Earth",
element: "earth",
description: "A nourishing plant with small white flowers found in lush meadows with plenty of sunshine."
},
"Fuzzy Peeper": {
type: "Plant",
alignment: "Wind",
element: "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",
element: "fire",
description: "A spiny red blossom that thrives in sun-scorched fields; its petals crackle faintly when crushed."
},
"Common Snupe": {
type: "Plant",
alignment: "Earth",
element: "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",
element: "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",
element: "fire",
description: "A twisted root that glows faintly from within—often brewed to stoke warmth into cold limbs."
},
"Frogwick": {
type: "Plant",
alignment: "Water",
element: "water",
description: "Rubbery, semi-aquatic herb found near marshes—tastes awful but soothes the throat like magic."
},
"Demeter's Arrow": {
type: "Plant",
alignment: "Wind",
element: "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",
element: "water",
description: "A long, fragrant stalk that grows in riverside soil; its boiled leaves soothe fevers and dampen restlessness."
},
"Pipeweed": {
type: "Plant",
alignment: "Wind",
element: "wind",
description: "Fragrant and slightly numbing, this herb is often smoked by hill witches and daydreaming farmhands."
},
"Kitty Mitten": {
type: "Plant",
alignment: "Fire",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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",
element: "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');
}
// Keep backward compatibility with old herbs object
var herbs = {};
for (var herbName in HERB_DATA) {
if (HERB_DATA.hasOwnProperty(herbName)) {
herbs[herbName] = {
element: HERB_DATA[herbName].element,
type: HERB_DATA[herbName].type,
description: HERB_DATA[herbName].description
};
}
}
// --- COUNTER LAYER - TILED ---
// Create multiple behind counter graphics to tile across screen width
var counterTiles = [];
var tileWidth = 200 * 8; // original width * scale
var tilesNeeded = Math.ceil(2048 / tileWidth) + 2; // extra tiles to extend past edges
var startX = -tileWidth; // start off-screen left
for (var i = 0; i < tilesNeeded; i++) {
var tile = game.attachAsset('behind_Counter', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + i * tileWidth,
y: 2100,
scaleX: 8,
scaleY: 8
});
counterTiles.push(tile);
}
// --- SERVE BUTTON (placed after counter so it's visible) ---
var serveButton = game.attachAsset('serveButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2450
});
serveButton.down = function (x, y, obj) {
craftPotion();
};
// Serve button handler now handled in main game down event
// Cauldron in center layer (depth 3, positioned lower on counter)
var cauldron = game.attachAsset('cauldronZone', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1650,
// omit width/height so it uses the asset's natural size
scaleX: 2.5,
scaleY: 2.5
});
cauldron.ingredients = [];
// Potion Bottle positioned near cauldron - created after cauldron for proper layering
var potionBottle = game.addChild(new PotionBottle());
potionBottle.x = 1024;
potionBottle.y = 1100;
potionBottle.visible = false; // Start hidden
// Cauldron ingredients display
var cauldronLabel = new Text2('', {
size: 56,
fill: 0x000000,
fontWeight: 'bold',
font: "'Times New Roman', serif"
});
cauldronLabel.anchor.set(0.5, 0.5);
cauldronLabel.x = 1024;
cauldronLabel.y = 950;
game.addChild(cauldronLabel);
function updateCauldronDisplay() {
if (cauldron.ingredients.length === 0) {
cauldronLabel.setText('');
} 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
});
}
// Inventory items are now only created through menu purchases
// No default items in main play area
// Ingredient category buttons in bottom layer - positioned below counter
// Larger buttons with better spacing, centered horizontally
var buttonWidth = 420;
var buttonHeight = 150;
var buttonSpacing = 80;
var totalWidth = buttonWidth * 3 + buttonSpacing * 2; // 3 buttons with 2 spacings
var startX = (2048 - totalWidth) / 2 + buttonWidth / 2;
var teaBasesButton = game.attachAsset('teabaseButton', {
anchorX: 0.5,
anchorY: 0.5,
x: startX,
y: 2250
});
// teaBasesButtonText removed - using graphic asset only
teaBasesButton.down = function (x, y, obj) {
var teaBaseColors = [];
for (var i = 0; i < baseItems.length; i++) {
teaBaseColors.push(0x20B2AA);
}
popupMenu.showMenu('TEA BASES - 50 Gold Each', baseItems, teaBaseColors);
};
var herbsButton = game.attachAsset('herbsButton', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + (buttonWidth + buttonSpacing),
y: 2250
});
// herbsButtonText removed - using graphic asset only
herbsButton.down = function (x, y, obj) {
var herbColors = [];
for (var i = 0; i < herbItems.length; i++) {
herbColors.push(0xADFF2F);
}
popupMenu.showMenu('HERBS - 50 Gold Each', herbItems, herbColors);
};
// Menu buttons in HUD layer - RESET and JOURNAL moved to right side, stacked vertically
var resetButton = game.attachAsset('resetButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1750,
y: 150
});
resetButton.down = function (x, y, obj) {
// Reset all game state
reputation = 100;
currentScore = 0;
currentPatient = null;
isProcessingTreatment = false;
patientTimer = 0;
treatmentTimer = 0;
// Clear cauldron
cauldron.ingredients = [];
updateCauldronDisplay();
// Clear inventory items
for (var i = inventoryItems.length - 1; i >= 0; i--) {
game.removeChild(inventoryItems[i]);
}
inventoryItems = [];
// Reset patient display
patientStatusText.setText('Welcome! A patient awaits...');
updateHintBox();
// Reset patient tint
patient.tint = 0xFFFFFF;
// Reset reputation meter
reputationText.setText(reputation.toString());
tween(meterFill, {
width: 300 // 100/200 * 600
}, {
duration: 300
});
tween(meterFill, {
tint: 0xFFFF00
}, {
duration: 300
});
// Set thermometer to normal
setThermometer("Normal");
// Spawn new patient
spawnNewPatient();
};
// Shop button removed per requirements
// Empty button for tea mixture reset - positioned at bottom of cauldron
var startOverButton = game.attachAsset('emptyButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1900
});
// startOverButtonText removed - using graphic asset only
var originalPatientText = '';
var emptyTimer = 0;
var isShowingEmptyText = false;
startOverButton.down = function (x, y, obj) {
cauldron.ingredients = [];
updateCauldronDisplay();
// Store original text and show empty message
if (!isShowingEmptyText) {
originalPatientText = patientStatusText.text;
patientStatusText.setText('Tea mixture cleared. Start fresh!');
updateHintBox();
emptyTimer = LK.ticks + 120; // 2 seconds at 60 FPS
isShowingEmptyText = true;
}
};
// Empty button event handler now handled in main game down event
// Create a top-level overlay container
var overlayLayer = new Container();
game.addChild(overlayLayer);
// Create popup menu inside overlay
var popupMenu = overlayLayer.addChild(new PopupMenu());
// Create herb details popup inside overlay
var herbPopup = overlayLayer.addChild(new HerbPopup());
// Category button event handlers now handled in main game down event
// Reset button handler is now attached directly to the button above
// Create journal popup inside overlay
var journalPopup = overlayLayer.addChild(new JournalPopup());
// Journal button aligned with TEA BASES and HERBS buttons
var journalButton = game.attachAsset('journalButton', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + (buttonWidth + buttonSpacing) * 2,
y: 2250
});
// journalButtonText removed - using graphic asset only
journalButton.down = function (x, y, obj) {
journalPopup.showJournal();
};
// Journal button event handler now handled in main game down event
// Ensure overlay layer is always on top after creating all other elements
game.removeChild(overlayLayer);
game.addChild(overlayLayer); // ensure overlay & popups are above HUD buttons
// Function to refresh HUD visibility based on popup states
function refreshHUD() {
var anyPopupOpen = popupMenu.isVisible || herbPopup.isVisible || journalPopup.isVisible;
journalButton.visible = !anyPopupOpen;
// journalButtonText removed - using graphic asset only
}
// Wrap popup menu methods to hide/show journal button
var _showMenu = popupMenu.showMenu;
popupMenu.showMenu = function (title, items, colors) {
_showMenu.call(popupMenu, title, items, colors);
refreshHUD();
};
var _hideMenu = popupMenu.hideMenu;
popupMenu.hideMenu = function () {
_hideMenu.call(popupMenu);
refreshHUD();
};
// Wrap herb popup methods to hide/show journal button
var _showPopup = herbPopup.showPopup;
herbPopup.showPopup = function (herbName, x, y) {
_showPopup.call(herbPopup, herbName, x, y);
refreshHUD();
};
var _hidePopup = herbPopup.hidePopup;
herbPopup.hidePopup = function () {
_hidePopup.call(herbPopup);
refreshHUD();
};
// Wrap journal popup methods to hide/show journal button
var _showJournal = journalPopup.showJournal;
journalPopup.showJournal = function () {
_showJournal.call(journalPopup);
refreshHUD();
};
var _hideJournal = journalPopup.hideJournal;
journalPopup.hideJournal = function () {
_hideJournal.call(journalPopup);
refreshHUD();
};
// Game state management
var gameStarted = false;
// How-to card overlay
var howtoOverlay = new Container();
game.addChild(howtoOverlay);
// Semi-transparent background
var howtoBackground = howtoOverlay.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.8,
tint: 0x000000
});
// How-to card
var howtoCard = howtoOverlay.attachAsset('howto_card', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200,
width: 1536 // 75% of screen width (2048 * 0.75) to preserve aspect ratio
});
// PLAY button
var playButton = howtoOverlay.attachAsset('craftButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2000,
width: 400,
height: 120,
tint: 0xFFFF00
});
var playButtonText = new Text2('PLAY', {
size: 64,
fill: 0xFFFFFF,
font: "'Times New Roman', serif"
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 1024;
playButtonText.y = 2000;
howtoOverlay.addChild(playButtonText);
playButton.down = function (x, y, obj) {
gameStarted = true;
howtoOverlay.visible = false;
LK.playMusic('BackgroundMusic');
spawnNewPatient();
};
// PLAY button handler now handled in main game down event
// Initially show how-to card
howtoOverlay.visible = true;