User prompt
Please fix the bug: 'Uncaught TypeError: setTimeout is not a function' in or related to this line: 'setTimeout(function () {' Line Number: 6260
User prompt
The "Disease" (BE03) event is incorrectly targeting Basic creatures that are buried underneath other creatures on a stack. Additionally, all Event header texts need to be perfectly center-aligned and moved up slightly, along with the main `gameStatusText`. Please make these exact 3 changes: 1. FIX DISEASE TARGETING TO ONLY HIT TOP CREATURES: - Locate the `processDiseaseEvent(onComplete)` function at the bottom of the script. - Look inside the nested `for` loops where the `targets` array is built. It currently looks like this: ```javascript for (var i = 0; i < terrain.creatureStack.length; i++) { if (terrain.creatureStack[i].creatureData.level === 'Basic') targets.push(terrain.creatureStack[i]); } ``` - Replace that ENTIRE `for (var i = 0; i < terrain.creatureStack.length; i++)` loop with this single check: ```javascript // ONLY check the top creature on the stack var topC = terrain.creatureStack[terrain.creatureStack.length - 1]; if (topC.creatureData.level === 'Basic') { targets.push(topC); } ``` 2. MOVE AND ALIGN THE GLOBAL GAME STATUS TEXT: - Locate `var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1"` (near the middle of the script, outside of any functions). - Change `gameStatusText.y = 60;` to `gameStatusText.y = 40;` (moved up by half a line). - Ensure `gameStatusText.x = 1024;` and `gameStatusText.anchor.set(0.5, 0);` remain unchanged, as this is already perfectly centered. 3. MOVE AND ALIGN ALL EVENT HEADER TEXTS: - I need you to find EVERY instance where an event creates a header text. Look for `new Text2(..., { size: 56, fill: ... align: 'center', anchorX: 0.5, anchorY: 0 })`. - Update the `y` coordinate for ALL of these headers from `120` to `90` (moved up by half a line, sitting perfectly under `gameStatusText`). - Update ALL of these specific lines across the entire script: - In `processSimplifySpecial`: Change `simplifyHeaderText.y = 120;` to `simplifyHeaderText.y = 90;` - In `processWatcherSpecial`: Change `notifyBox.y = 1366;` to `notifyBox.y = 90;` (and its anchor to top center). Wait, just change the `header.y = 120;` to `header.y = 90;` - In `processForerunnerSpecial`: Change `header.y = 120;` to `header.y = 90;` - In `processDevolutionEvent`: Change `devolutionHeaderText.y = 120;` to `devolutionHeaderText.y = 90;` - In `processTerraformerSpecial`: Change `terraformerHeaderText.y = 120;` to `terraformerHeaderText.y = 90;` - In `processDiseaseEvent`: Change `diseaseHeaderText.y = 120;` to `diseaseHeaderText.y = 90;` - In `processVolcanoEvent`: Change `volcanoHeaderText.y = 120;` to `volcanoHeaderText.y = 90;` - In `processSmiteEvent`: Change `header.y = 120;` to `header.y = 90;` - In `processHarshEvolutionEvent`: Change `header.y = 120;` to `header.y = 90;` - In `processMeteorEvent`: Change `header.y = 120;` to `header.y = 90;` ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I need to make a few visual adjustments to some card text sizes, remove an obsolete generic text, and build a "slot machine" style visual animation for the Terra-former (BH32) terrain swap. Please make these exact 4 changes: 1. SHRINK LONG SPECIAL TEXTS: - Locate the `CreatureCard` class cascading text section. - Find the `self.hunterHunterText` block. Change its `size` from `24` to `21`. - Find the `self.dominantDNAText` block. Change its `size` from `24` to `21`. 2. REMOVE 'SPECIAL' FROM AT07 (DISPLACER): - Locate the `TerrainCard` class. - Find the block that creates `self.specialText`. It currently says: `if (self.terrainData && self.terrainData.special && !self.terrainData.simplify) {` - Update that line so it also ignores Displacer: `if (self.terrainData && self.terrainData.special && !self.terrainData.simplify && !self.terrainData.displacer) {` 3. ADD THE TERRA-FORMER SHRINK ANIMATION: - Locate the `processTerraformerSpecial` function at the bottom of the script. - Inside the `highlight.down = function() {` block, we need to animate the old terrain shrinking away. - Replace the entire `highlight.down = function() { ... };` block with this animated version: ```javascript highlight.down = function() { if (!terraformerActive) return; var tTerrain = this.targetTerrain; var tX = tTerrain.gridX; var tY = tTerrain.gridY; var basicUnder = tTerrain.basicTerrainUnderneath; var isAdv = (this.targetLevel === 'Advanced'); for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isTerraformerHighlight) game.removeChild(game.children[j]); } if (terraformerHeaderText && terraformerHeaderText.parent) terraformerHeaderText.parent.removeChild(terraformerHeaderText); terraformerActive = false; // 1. Shrink the old terrain away tween(tTerrain, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function() { if (isAdv) { discardPile.push(tTerrain.terrainData); discardCountText.setText("Discard: " + discardPile.length); } if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain); // 2. Start the slot machine for the new Basic Terrain var targetBasicToSwap = isAdv ? basicUnder : tTerrain; if (basicTerrainPool.length > planetLayout.reduce((a, b) => a + b, 0)) { animateTerraformerSlotMachine(targetBasicToSwap, tX, tY); } else { // Pool empty, just revert to basic if (isAdv) { planetBoard[tY][tX] = basicUnder; planetSlots[tY][tX].terrainCard = basicUnder; } } } }); }; ``` 4. ADD THE TERRA-FORMER SLOT MACHINE ANIMATION: - Right BELOW the `processTerraformerSpecial` function, add this entirely new animation loop: ```javascript function animateTerraformerSlotMachine(oldBasicCard, tX, tY) { // Create a temporary visual card to flicker through the pool var slotCard = new TerrainCard(basicTerrainPool[0]); slotCard.x = oldBasicCard.x; slotCard.y = oldBasicCard.y; slotCard.scaleX = oldBasicCard.scaleX; slotCard.scaleY = oldBasicCard.scaleY; var basicIndex = game.getChildIndex(oldBasicCard); game.addChildAt(slotCard, basicIndex); if (oldBasicCard.parent) oldBasicCard.parent.removeChild(oldBasicCard); var flashes = 0; var maxFlashes = 15; // How many times it changes before stopping var speed = 50; // Milliseconds per flash function flashNext() { flashes++; if (flashes < maxFlashes) { // Randomly grab a card from the pool just for visual flicker var randData = basicTerrainPool[Math.floor(Math.random() * basicTerrainPool.length)]; slotCard.terrainData = randData; // Rebuild visual (destroy old children and run the constructor logic manually for the flicker) while(slotCard.children.length > 0) slotCard.removeChild(slotCard.children[0]); TerrainCard.call(slotCard, randData); speed += 15; // Slow down over time (friction) setTimeout(flashNext, speed); } else { // The Grand Finale! Pop the actual permanent card from the pool var finalData = basicTerrainPool.pop(); finalData.climate = oldBasicCard.terrainData.climate; // Inherit climate var finalCard = new TerrainCard(finalData); finalCard.gridX = tX; finalCard.gridY = tY; finalCard.isInPlay = true; finalCard.x = slotCard.x; finalCard.y = slotCard.y; finalCard.scaleX = slotCard.scaleX; finalCard.scaleY = slotCard.scaleY; planetBoard[tY][tX] = finalCard; planetSlots[tY][tX].terrainCard = finalCard; if (slotCard.parent) slotCard.parent.removeChild(slotCard); game.addChildAt(finalCard, basicIndex); // Flash yellow to signify the lock-in! finalCard.tint = 0xFFFF00; tween(finalCard, { tint: 0xFFFFFF }, { duration: 500 }); } } flashNext(); } ``` ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
This is Part 2 of implementing the "Virus" (AE04) Event. Now we need to automate the infection spread, damage, and healing during the Phase Transitions. Please make these exact 3 changes: 1. REWRITE THE DAWN PHASE FOR VIRUS HEALING: - Locate the `processDawnPhase()` function. - Replace the entire `processDawnPhase()` function with this updated version: ```javascript function processDawnPhase() { if (currentRound > 1 && !scryDeniedNextRound) { scryTokenActive = true; } creaturesWentExtinct = false; scryDeniedNextRound = false; var infected = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t && t.creatureStack) { for (var i = 0; i < t.creatureStack.length; i++) { if (t.creatureStack[i].virusMarkers > 0) infected.push(t.creatureStack[i]); } } } } var proceed = function() { gamePhase = 'draw'; processDrawPhase(); }; if (infected.length > 0) { showPhaseTransition("Processing virus behavior...", function() { for (var i = 0; i < infected.length; i++) { var c = infected[i]; var hasLinks = (c.creatureData.dietType === 'carnivore' && c.activeLinks.length > 0); for (var l = 0; l < linkLines.length; l++) { if (linkLines[l].herbivore === c || linkLines[l].carnivore === c) hasLinks = true; } if (!hasLinks) { c.virusMarkers--; c.updateVirusMarkers(); } } setTimeout(proceed, 1000); }); } else { showPhaseTransition("Dawn Phase - Processing...", proceed); } } ``` 2. REWRITE THE END TURN PHASE FOR VIRUS SPREADING: - Locate the `processEndTurnPhase()` function. - Replace the entire `processEndTurnPhase()` function with this updated version: ```javascript function processEndTurnPhase() { var newlyInfected = []; for (var l = 0; l < linkLines.length; l++) { var c1 = linkLines[l].carnivore; var c2 = linkLines[l].herbivore; if (c1.virusMarkers > 0 && newlyInfected.indexOf(c2) === -1) newlyInfected.push(c2); if (c2.virusMarkers > 0 && newlyInfected.indexOf(c1) === -1) newlyInfected.push(c1); } var msg = newlyInfected.length > 0 ? "The virus is spreading..." : "End Turn - Processing extinctions..."; showPhaseTransition(msg, function () { for (var i = 0; i < newlyInfected.length; i++) { newlyInfected[i].virusMarkers++; newlyInfected[i].updateVirusMarkers(); } processExtinctions(); turnNumber++; gamePhase = 'dawn'; processDawnPhase(); }); } ``` 3. INJECT NIGHT PHASE DAMAGE: - Locate the `processNightPhase()` function. - Replace the single line: `showPhaseTransition("Night Phase - Checking links...", function () {` with these 4 lines: ```javascript var vPres = false; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t && t.creatureStack) { for (var i = 0; i < t.creatureStack.length; i++) if (t.creatureStack[i].virusMarkers > 0) vPres = true; } } } var nightMsg = vPres ? "The virus is taking its toll..." : "Night Phase - Checking links..."; showPhaseTransition(nightMsg, function () { ``` - Scroll down INSIDE the `processNightPhase` callback loop. Right BELOW `if (!isTopCreature) { continue; }`, insert this virus damage logic: ```javascript if (creature.virusMarkers > 0) { creature.extinctionMarkers += 1; creature.updateExtinctionMarkers(); } ```
User prompt
We are implementing the "Virus" (AE04) Event. This is Part 1. Please make these exact 5 changes using exact string matches. 1. UPDATE DECK GENERATOR: - Search `createEventDecks()` for exactly this: ```javascript advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE03', name: 'Meteor', effect: 'catastrophic' }); ``` - Right BELOW that block, ADD this block: ```javascript advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE04', name: 'Virus', effect: 'catastrophic' }); ``` 2. UPDATE UI TEXT: - Search `showCardInfo` for exactly this: ```javascript if (evt.id === 'AE03') { infoString += " | Meteor! 1 terrain will be hit by a powerful AOE damage effect spreading extinction markers far and wide!"; } ``` - Right BELOW that block, ADD this block: ```javascript if (evt.id === 'AE04') { infoString += " | Virus! A creature breaks out in a deadly illness that spreads via links to or from it!"; } ``` 3. ADD VIRUS VARIABLES TO CREATURES: - Search the `CreatureCard` class for exactly this: ```javascript self.activeLinks = []; self.extinctionMarkers = 0; ``` - REPLACE it with: ```javascript self.activeLinks = []; self.extinctionMarkers = 0; self.virusMarkers = 0; self.virusVisuals = []; ``` 4. ADD VIRUS GRAPHICS AND DEATH RESET: - Search the `CreatureCard` class for exactly this: ```javascript self.extinctionMarkerVisuals.push(marker); } }; self.die = function () { if (self.isInPlay) { // Grateful interceptor ``` - REPLACE it with this block (which adds the visual generator and resets the virus on death): ```javascript self.extinctionMarkerVisuals.push(marker); } }; self.updateVirusMarkers = function () { for (var i = 0; i < self.virusVisuals.length; i++) { if (self.virusVisuals[i].parent) { self.virusVisuals[i].parent.removeChild(self.virusVisuals[i]); } } self.virusVisuals = []; for (var i = 0; i < self.virusMarkers; i++) { var vMark = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.12, scaleY: 0.12 }); vMark.tint = 0x39FF14; // Neon Green vMark.x = -40 + (i * 20); vMark.y = -80; var vText = new Text2("V", { size: 60, fill: 0x000000, font: "'Arial Black', 'Impact', sans-serif" }); vText.anchor.set(0.5, 0.5); vMark.addChild(vText); self.addChild(vMark); self.virusVisuals.push(vMark); } }; self.die = function () { if (self.isInPlay) { self.virusMarkers = 0; self.updateVirusMarkers(); // Grateful interceptor ``` 5. ADD EVENT ROUTER AND INTERACTION SYSTEM: - Search the `EventCard` class for exactly this: ```javascript } else if (self.eventData.id === 'AE03') { processMeteorEvent(finalizeEvent); } else { finalizeEvent(); // Fallback for unimplemented events } ``` - REPLACE it with: ```javascript } else if (self.eventData.id === 'AE03') { processMeteorEvent(finalizeEvent); } else if (self.eventData.id === 'AE04') { processVirusEvent(finalizeEvent); } else { finalizeEvent(); // Fallback for unimplemented events } ``` - Now, scroll to the VERY BOTTOM of the script file and paste this entire new function block: ```javascript var virusActive = false; function processVirusEvent(onComplete) { var targets = []; var backupTargets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t && t.creatureStack && t.creatureStack.length > 0) { var c = t.creatureStack[t.creatureStack.length - 1]; backupTargets.push(c); if (c.creatureData.level === 'Advanced' && c.creatureData.dietType === 'herbivore') { targets.push(c); } } } } var finalTargets = targets.length > 0 ? targets : backupTargets; if (finalTargets.length === 0) { onComplete(); return; } virusActive = true; var header = new Text2("Choose a creature to be the host...", { size: 56, fill: 0x39FF14, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; game.addChild(header); for (var i = 0; i < finalTargets.length; i++) { var target = finalTargets[i]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0x39FF14; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetCreature = target; highlight.isVirusHighlight = true; highlight.down = function() { if (!virusActive) return; this.targetCreature.virusMarkers += 1; this.targetCreature.updateVirusMarkers(); for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isVirusHighlight) game.removeChild(game.children[j]); } header.setText("Lets hope it's not too bad...\ntry to prevent links to or from that creature,\nclick anywhere to continue"); var clickCatcher = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 100, scaleY: 100 }); clickCatcher.alpha = 0.01; clickCatcher.x = 1024; clickCatcher.y = 1366; game.addChild(clickCatcher); clickCatcher.down = function() { if (header.parent) header.parent.removeChild(header); if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher); virusActive = false; onComplete(); }; }; game.addChild(highlight); } } ```
User prompt
I need to implement a new Advanced Event: "Meteor" (AE03). This is a multi-step cinematic AOE attack that forces the player to click to advance the damage phases. Please make these exact 4 changes: 1. UPDATE THE ADVANCED EVENT DECK & INFO STRINGS: - Locate `createEventDecks()`. In the `advancedEventDeck` push loops, change the loop to start at `i = 4` and add AE03: ```javascript advancedEventDeck = []; advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE01', name: 'Smite', effect: 'catastrophic' }); advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE02', name: 'Harsh Evolution', effect: 'catastrophic' }); advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE03', name: 'Meteor', effect: 'catastrophic' }); for (var i = 4; i <= 5; i++) { advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE0' + i, name: 'Advanced Event ' + i, effect: 'catastrophic' }); } ``` - Locate `showCardInfo(card)`. Under the event checks, append: ```javascript if (evt.id === 'AE03') infoString += " | Meteor! 1 terrain will be hit by a powerful AOE damage effect spreading extinction markers far and wide!"; ``` 2. ADD HOVER HIGHLIGHTS FOR METEOR: - Locate `showPossibleMoves(card)`. Inside the `else if` chain for events, add the AE03 route: ```javascript } else if (card.eventData && card.eventData.id === 'AE03') { // Highlight terrains that have exactly 4 orthogonal neighbors for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t) { var nbrs = getAdjacentTerrains(x, y, false); if (nbrs.length === 4) planetSlots[y][x].highlight(); } } } } ``` 3. ADD THE EVENT ROUTER FOR AE03: - Locate the `EventCard` class, inside `self.down = function`. Update the router chain to include AE03: ```javascript } else if (self.eventData.id === 'AE02') { processHarshEvolutionEvent(finalizeEvent); } else if (self.eventData.id === 'AE03') { processMeteorEvent(finalizeEvent); } else { finalizeEvent(); } ``` 4. ADD THE METEOR CINEMATIC LOGIC: - Scroll to the very bottom of the script file and add this new multi-step interactive function: ```javascript var meteorActive = false; function processMeteorEvent(onComplete) { var targets = []; // Find all terrains with 4 orthogonal neighbors for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain) { var neighbors = getAdjacentTerrains(x, y, false); if (neighbors.length === 4) targets.push(terrain); } } } if (targets.length === 0) { var msg = new Text2("No suitable impact zones", { size: 56, fill: 0xFFFFFF, align: 'center', anchorX: 0.5, anchorY: 0 }); msg.x = 1024; msg.y = 120; game.addChild(msg); setTimeout(function() { if (msg.parent) msg.parent.removeChild(msg); onComplete(); }, 1500); return; } meteorActive = true; var header = new Text2("Choose a terrain for the meteor to strike!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; game.addChild(header); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xFF4500; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; // Orange-red highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isMeteorHighlight = true; highlight.down = function() { if (!meteorActive) return; var epicenter = this.targetTerrain; for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isMeteorHighlight) game.removeChild(game.children[j]); header.setText("The central impact hits for 2 extinction markers\nclick anywhere to confirm"); // Create massive invisible button to intercept clicks anywhere on screen var clickCatcher = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 100, scaleY: 100 }); clickCatcher.alpha = 0.01; clickCatcher.x = 1024; clickCatcher.y = 1366; game.addChild(clickCatcher); clickCatcher.down = function() { // Phase 1: Apply 2 markers to Epicenter if (epicenter.creatureStack && epicenter.creatureStack.length > 0) { var topC = epicenter.creatureStack[epicenter.creatureStack.length - 1]; topC.extinctionMarkers += 2; topC.updateExtinctionMarkers(); } header.setText("Now the blast wave hits all around\nclick anywhere to confirm"); // Advance click catcher to Phase 2 clickCatcher.down = function() { // Phase 2: Apply 1 marker to orthogonal neighbors var blastZone = getAdjacentTerrains(epicenter.gridX, epicenter.gridY, false); for (var b = 0; b < blastZone.length; b++) { var bT = blastZone[b]; if (bT && bT.creatureStack && bT.creatureStack.length > 0) { var bC = bT.creatureStack[bT.creatureStack.length - 1]; bC.extinctionMarkers += 1; bC.updateExtinctionMarkers(); } } // Cleanup and finish if (header.parent) header.parent.removeChild(header); if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher); meteorActive = false; onComplete(); }; }; }; game.addChild(highlight); } } ```
User prompt
I need to implement the first two Advanced Events: "Smite" (AE01) and "Harsh Evolution" (AE02). Please make these exact 5 changes: 1. UPDATE THE ADVANCED EVENT DECK GENERATOR: - Locate `createEventDecks()`. Replace the `advancedEventDeck` generation block with this: ```javascript advancedEventDeck = []; advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE01', name: 'Smite', effect: 'catastrophic' }); advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE02', name: 'Harsh Evolution', effect: 'catastrophic' }); for (var i = 3; i <= 10; i++) { advancedEventDeck.push({ type: 'advanced', cardType: 'event', level: 'Advanced', id: 'AE0' + i, name: 'Advanced Event ' + i, effect: 'catastrophic' }); } ``` 2. UPDATE EVENT UI STRINGS: - Locate `showCardInfo(card)`. Under the `else if (card.eventData)` block, append these descriptions: ```javascript if (evt.id === 'AE01') infoString += " | Smite! Destroys a stack completely! All creatures there are killed, all land there are removed from play, nothing remains!"; if (evt.id === 'AE02') infoString += " | Harsh Evolution! One of your advanced herbivores will be destroyed!"; ``` 3. ADD HOVER HIGHLIGHTS FOR SMITE & HARSH EVOLUTION: - Locate `showPossibleMoves(card)`. Inside the `else if (card.eventData && card.eventData.id === 'BE05')` block, add these routes below it: ```javascript } else if (card.eventData && card.eventData.id === 'AE01') { // Highlight all occupied planet slots for Smite for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { if (planetBoard[y][x]) planetSlots[y][x].highlight(); } } } else if (card.eventData && card.eventData.id === 'AE02') { // Highlight all Advanced Herbivores for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t && t.creatureStack) { for (var i = 0; i < t.creatureStack.length; i++) { var c = t.creatureStack[i]; if (c.creatureData.dietType === 'herbivore' && c.creatureData.level === 'Advanced') planetSlots[y][x].highlight(); } } } } ``` 4. ADD THE EVENT ROUTER FOR AE01 & AE02: - Locate the `EventCard` class, inside `self.down = function`. - Find the routing block (e.g., `if (self.eventData.id === 'BE01')`). Update it to include AE01 and AE02: ```javascript if (self.eventData.id === 'BE01') { for (var i = 0; i < 3; i++) { if (mainDeck.length > 0) discardPile.push(mainDeck.pop()); } deckCountText.setText("Deck: " + mainDeck.length); discardCountText.setText("Discard: " + discardPile.length); finalizeEvent(); } else if (self.eventData.id === 'BE02') { processDevolutionEvent(finalizeEvent); } else if (self.eventData.id === 'BE03') { processDiseaseEvent(finalizeEvent); } else if (self.eventData.id === 'BE04') { processVolcanoEvent(function(){}); // Creates terrain, phase changes automatically } else if (self.eventData.id === 'BE05') { processRisingSeasEvent(function(){}); // Creates terrain, phase changes automatically } else if (self.eventData.id === 'AE01') { processSmiteEvent(finalizeEvent); } else if (self.eventData.id === 'AE02') { processHarshEvolutionEvent(finalizeEvent); } else { finalizeEvent(); } ``` 5. ADD THE SMITE AND HARSH EVOLUTION LOGIC: - Scroll to the very bottom of the script file and add these two new interactive functions: ```javascript var smiteActive = false; function processSmiteEvent(onComplete) { smiteActive = true; var header = new Text2("Select a place to utterly destroy!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; game.addChild(header); for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var target = planetBoard[y][x]; if (target) { var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xFF0000; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isSmiteHighlight = true; highlight.down = function() { if (!smiteActive) return; var tTerrain = this.targetTerrain; // Kill all creatures WITHOUT triggering new events while (tTerrain.creatureStack.length > 0) { var c = tTerrain.creatureStack.pop(); for (var j = c.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(c, c.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === c) animateLinkFallOff(linkLines[j].carnivore, c); graveyard.push(c.creatureData); scryDeniedNextRound = true; if (c.parent) c.parent.removeChild(c); } // Destroy all terrain on this spot (Advanced and Basic) if (tTerrain.basicTerrainUnderneath && tTerrain.basicTerrainUnderneath.parent) tTerrain.basicTerrainUnderneath.parent.removeChild(tTerrain.basicTerrainUnderneath); if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain); // Wipe the board data clean so it can be rebuilt planetBoard[tTerrain.gridY][tTerrain.gridX] = null; planetSlots[tTerrain.gridY][tTerrain.gridX].terrainCard = null; graveyardCountText.setText("Graveyard: " + graveyard.length); for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isSmiteHighlight) game.removeChild(game.children[j]); if (header.parent) header.parent.removeChild(header); smiteActive = false; onComplete(); }; game.addChild(highlight); } } } } var harshEvolutionActive = false; function processHarshEvolutionEvent(onComplete) { var targets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.creatureStack) { for (var i = 0; i < terrain.creatureStack.length; i++) { var c = terrain.creatureStack[i]; if (c.creatureData.dietType === 'herbivore' && c.creatureData.level === 'Advanced') targets.push(c); } } } } if (targets.length === 0) { var msg = new Text2("No creatures to target", { size: 56, fill: 0xFFFFFF, align: 'center', anchorX: 0.5, anchorY: 0 }); msg.x = 1024; msg.y = 120; game.addChild(msg); setTimeout(function() { if (msg.parent) msg.parent.removeChild(msg); onComplete(); }, 1500); return; } harshEvolutionActive = true; var header = new Text2("Select an advanced creature to bite the dust!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; game.addChild(header); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xDDA0DD; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetCreature = target; highlight.isHarshHighlight = true; highlight.down = function() { if (!harshEvolutionActive) return; var tC = this.targetCreature; // Remove creature from stack and trigger normal death (which spawns the AE) var stack = planetBoard[tC.gridY][tC.gridX].creatureStack; stack.splice(stack.indexOf(tC), 1); for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC); tC.die(); graveyardCountText.setText("Graveyard: " + graveyard.length); for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isHarshHighlight) game.removeChild(game.children[j]); if (header.parent) header.parent.removeChild(header); harshEvolutionActive = false; onComplete(); }; game.addChild(highlight); } } ```
User prompt
continuing with BE05: - In `createEventDecks()`, add `BE05` to the `basicEventDeck` pushes: ```javascript basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE05', name: 'Rising Seas' }); ``` - In `showCardInfo(card)`, add the description for BE05: ```javascript if (evt.id === 'BE05') infoString += " | Rising Seas: This event changes a flat land into an advanced sea terrain card, and all the creatures there that don't swim or fly, must move or die."; ``` 4. ADD BE05 HOVER HIGHLIGHTS: - Locate `showPossibleMoves(card)`. At the bottom of this function, add an `else if` for the event: ```javascript } else if (card.eventData && card.eventData.id === 'BE05') { var flatTargets = []; var emptyFlats = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var t = planetBoard[y][x]; if (t && t.terrainData && t.terrainData.landType === 'flat') { if (t.creatureStack && t.creatureStack.length > 0) flatTargets.push(planetSlots[y][x]); else emptyFlats.push(planetSlots[y][x]); } } } var finalTargets = flatTargets.length > 0 ? flatTargets : emptyFlats; for (var i = 0; i < finalTargets.length; i++) finalTargets[i].highlight(); } ``` 5. ADD BE05 EVENT ROUTER: - Locate the `EventCard` class, inside `self.down`. Add the BE05 route: ```javascript } else if (self.eventData.id === 'BE05') { processRisingSeasEvent(function(){}); // Creates terrain, phase changes automatically } else { ``` 6. ADD RISING SEAS INTERACTION LOGIC: - Scroll to the very bottom of the script file and add this new interaction system: ```javascript var risingSeasActive = false; var risingSeasHeaderText = null; function processRisingSeasEvent(onComplete) { var targets = []; var emptyTargets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.terrainData && terrain.terrainData.landType === 'flat') { if (terrain.creatureStack && terrain.creatureStack.length > 0) targets.push(terrain); else emptyTargets.push(terrain); } } } var finalTargets = targets.length > 0 ? targets : emptyTargets; if (finalTargets.length === 0) { var msg = new Text2("Event has no effect.", { size: 56, fill: 0xFFFFFF, align: 'center', anchorX: 0.5, anchorY: 0 }); msg.x = 1024; msg.y = 120; game.addChild(msg); setTimeout(function() { if (msg.parent) msg.parent.removeChild(msg); hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); }, 1500); return; } risingSeasActive = true; risingSeasHeaderText = new Text2("Choose a land to raise the sea level!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); risingSeasHeaderText.x = 1024; risingSeasHeaderText.y = 120; game.addChild(risingSeasHeaderText); for (var t = 0; t < finalTargets.length; t++) { var target = finalTargets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xDDA0DD; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isRisingSeasHighlight = true; highlight.down = function() { if (!risingSeasActive) return; var selectedFlat = this.targetTerrain; for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isRisingSeasHighlight) game.removeChild(game.children[j]); } // Advanced Terrain replacement logic if (selectedFlat.terrainData.level === 'Advanced') { discardPile.push(selectedFlat.terrainData); discardCountText.setText("Discard: " + discardPile.length); } var seaData = { type: 'advanced', level: 'Advanced', cardType: 'terrain', subtype: 'water', waterType: 'sea', id: 'BE05_TERRAIN', colorBand: 'darkblue', climateRequirement: 'any', name: 'Rising Seas', special: true }; var seaCard = new TerrainCard(seaData); var mX = selectedFlat.gridX; var mY = selectedFlat.gridY; seaCard.creatureStack = selectedFlat.creatureStack || []; seaCard.gridX = mX; seaCard.gridY = mY; seaCard.isInPlay = true; seaCard.x = selectedFlat.x; seaCard.y = selectedFlat.y; seaCard.scaleX = selectedFlat.scaleX; seaCard.scaleY = selectedFlat.scaleY; planetBoard[mY][mX] = seaCard; planetSlots[mY][mX].terrainCard = seaCard; seaCard.basicTerrainUnderneath = (selectedFlat.terrainData.level === 'Advanced') ? selectedFlat.basicTerrainUnderneath : selectedFlat; var mIndex = game.getChildIndex(selectedFlat); if (selectedFlat.terrainData.level === 'Advanced' && selectedFlat.parent) selectedFlat.parent.removeChild(selectedFlat); game.addChildAt(seaCard, mIndex + 1); for (var i = 0; i < seaCard.creatureStack.length; i++) { var c = seaCard.creatureStack[i]; game.removeChild(c); game.addChild(c); } for (var l = 0; l < linkLines.length; l++) { game.removeChild(linkLines[l]); game.addChild(linkLines[l]); } processRisingSeasFlee(seaCard); }; game.addChild(highlight); } } function processRisingSeasFlee(seaTerrain) { var finishSeas = function() { if (risingSeasHeaderText && risingSeasHeaderText.parent) risingSeasHeaderText.parent.removeChild(risingSeasHeaderText); risingSeasActive = false; hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); }; if (!seaTerrain.creatureStack || seaTerrain.creatureStack.length === 0) { finishSeas(); return; } var topC = seaTerrain.creatureStack[seaTerrain.creatureStack.length - 1]; if (topC.creatureData.flying || topC.creatureData.swimming || topC.creatureData.fly || topC.creatureData.swim) { risingSeasHeaderText.setText("The creatures are happy here!"); setTimeout(function() { finishSeas(); }, 1500); return; } risingSeasHeaderText.setText("This creature is in danger and must relocate!"); setTimeout(function() { var validEscapes = []; var neighbors = getAdjacentTerrains(seaTerrain.gridX, seaTerrain.gridY, true); seaTerrain.creatureStack.pop(); for (var n = 0; n < neighbors.length; n++) { if (neighbors[n] && canPlaceCreatureOnTerrain(topC, neighbors[n])) validEscapes.push(neighbors[n]); } seaTerrain.creatureStack.push(topC); if (validEscapes.length === 0) { risingSeasHeaderText.setText("The creature could not relocate and has died..."); setTimeout(function() { var stack = seaTerrain.creatureStack; stack.pop(); graveyard.push(topC.creatureData); scryDeniedNextRound = true; for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC); if (topC.parent) topC.parent.removeChild(topC); processRisingSeasFlee(seaTerrain); }, 2000); } else { risingSeasHeaderText.setText("Relocate the creature quick!"); var fleeHighlights = []; for (var e = 0; e < validEscapes.length; e++) { var esc = validEscapes[e]; var hlt = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); hlt.tint = 0xFFFF00; hlt.alpha = 0.6; hlt.scaleX = esc.scaleX * 1.15; hlt.scaleY = esc.scaleY * 1.15; hlt.x = esc.x; hlt.y = esc.y; hlt.targetEscape = esc; hlt.isFleeHighlight = true; hlt.down = function() { var chosenEsc = this.targetEscape; for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]); var movingC = seaTerrain.creatureStack.pop(); placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); setTimeout(function() { processRisingSeasFlee(seaTerrain); }, 400); }; game.addChild(hlt); fleeHighlights.push(hlt); } } }, 1500); } ```
User prompt
I need to fix some text overlapping/sizing on AT12, rename the "Extinction Trigger" mechanic to "Grateful", move the danger text down, and implement the new "Rising Seas" (BE05) event. Please make these exact changes: 1. FIX AT12 'SIMPLIFY' AND REMOVE 'SPECIAL' TEXT: - Locate the `TerrainCard` class. - Find the block that adds the "Special" text: `if (self.terrainData && self.terrainData.special) {` - Change that line to: `if (self.terrainData && self.terrainData.special && !self.terrainData.simplify) {` (This prevents "Special" from printing on AT12). - Just below that, find the block for `simplifyText`. Change its `size` from `24` to `36`. 2. RENAME 'EXTINCTION TRIGGER' TO 'GRATEFUL': - In `createInitialDeck()`, find `BH05` inside the `basicHerbivoreConfigs` loop. Change `extinctionTrigger: config.id === 'BH05' ? true : false,` to `grateful: config.id === 'BH05' ? true : false,`. - In `showCardInfo(card)`, find the info string for `extinctionTrigger`. Replace it with: ```javascript if (creature && creature.grateful) infoString += " | Grateful: If this creature goes extinct it adds +3 points as an end of game score bonus instead of generating an event card!"; ``` - In the `CreatureCard` class cascading text section, replace the `extinctionTrigger` block with: ```javascript if (self.creatureData && self.creatureData.grateful) { self.gratefulText = new Text2("Grateful", { size: 20, fill: 0xFF69B4, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.gratefulText.anchor.set(0.5, 0.5); self.gratefulText.x = 0; self.gratefulText.y = nextYOffset; self.addChild(self.gratefulText); nextYOffset += 28; } ``` - In the `CreatureCard` class `self.die = function ()`, change `if (self.creatureData.extinctionTrigger)` to `if (self.creatureData.grateful)`. 3. MOVE DANGER TEXT AND ADD BE05 DECK CONFIG: - Locate `updateDangerHighlights()`. Find the `warningText.y = 250;` line (near the bottom of the function). Change it to `warningText.y = 330;` (moved down 2 lines).
User prompt
I need to fix the "Shy" logic, add visual text for "Fussy", reposition some UI elements, clean up the Event deck placeholders, and completely rewrite the Volcano (BE04) event flow to include cinematic pauses and interactive death mechanics. Please make these exact 6 changes: 1. FIX SHY VETO AND ADD FUSSY TEXT: - Locate `canPlaceCreatureOnTerrain`. Find the line `// Shy creatures CANNOT be placed on occupied terrain` and COMPLETELY DELETE the `if (creature.shy) { return false; }` block right below it. (Shy should only grant permission for empty terrains, not block normal play!). - Locate the `CreatureCard` class cascading text section. Add this block for Fussy: ```javascript if (self.creatureData && self.creatureData.fussy) { self.fussyText = new Text2("Fussy", { size: 24, fill: 0x800080, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.fussyText.anchor.set(0.5, 0.5); self.fussyText.x = 0; self.fussyText.y = nextYOffset; self.addChild(self.fussyText); nextYOffset += 28; } ``` 2. REMOVE EVENT PLACEHOLDERS: - Locate `createEventDecks()`. - Change the basic event loop from `for (var i = 3; i <= 10; i++)` to `for (var i = 5; i <= 5; i++)`. (This leaves BE01-BE05). - Change the advanced event loop from `for (var i = 1; i <= 10; i++)` to `for (var i = 1; i <= 5; i++)`. 3. REPOSITION DANGER WARNING TEXT: - Locate `updateDangerHighlights()`. Find where `warningText` is created (near the bottom). - Change the text, alignment, and position to this: ```javascript var warningText = new Text2("Warning!\nCreature/s in danger!", { size: 40, fill: 0xFF0000, align: 'right' }); warningText.anchor.set(1, 0); warningText.x = 2000; warningText.y = 250; // Sits right under the Scry text on the right side ``` 4. REPOSITION EVENT DEBUG BOX: - Locate `showEventCardDebugBox()`. Look inside the `else { // Normal Event Mode` block. - Change `eventDebugBox.y = 1000;` to `eventDebugBox.y = 900;`. - Update the text inside it to center it across two lines: ```javascript var debugText = new Text2("Event card/s in hand!\nClick the cards to resolve them.", { size: 34, fill: 0xFFFFFF, align: 'center' }); ``` 5. REWRITE VOLCANO EVENT INITIALIZATION: - Locate `processVolcanoEvent(onComplete)`. Replace the `highlight.down = function() { ... }` block inside it with this: ```javascript highlight.down = function() { if (!volcanoActive) return; var selectedMountain = this.targetTerrain; for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isVolcanoHighlight) game.removeChild(game.children[j]); } // 1. Transform into Volcano Terrain instantly var volcanoData = { type: 'advanced', level: 'Advanced', cardType: 'terrain', subtype: 'land', landType: 'mountain', id: 'VOLCANO', colorBand: 'hot', climateRequirement: 'any', name: 'Volcano', climate: 'hot', special: true }; var volcanoCard = new TerrainCard(volcanoData); var mX = selectedMountain.gridX; var mY = selectedMountain.gridY; volcanoCard.creatureStack = selectedMountain.creatureStack || []; selectedMountain.terrainData.climate = 'hot'; volcanoCard.gridX = mX; volcanoCard.gridY = mY; volcanoCard.isInPlay = true; volcanoCard.x = selectedMountain.x; volcanoCard.y = selectedMountain.y; volcanoCard.scaleX = selectedMountain.scaleX; volcanoCard.scaleY = selectedMountain.scaleY; planetBoard[mY][mX] = volcanoCard; planetSlots[mY][mX].terrainCard = volcanoCard; volcanoCard.basicTerrainUnderneath = selectedMountain.basicTerrainUnderneath || selectedMountain; var mIndex = game.getChildIndex(selectedMountain); game.addChildAt(volcanoCard, mIndex + 1); for (var i = 0; i < volcanoCard.creatureStack.length; i++) { var c = volcanoCard.creatureStack[i]; game.removeChild(c); game.addChild(c); } for (var l = 0; l < linkLines.length; l++) { game.removeChild(linkLines[l]); game.addChild(linkLines[l]); } // 2. Start fleeing checks processVolcanoFlee(volcanoCard, onComplete); }; ``` 6. REWRITE VOLCANO FLEEING AND DEATH LOGIC: - Completely REPLACE the `processVolcanoFlee` function with this interactive cinematic version: ```javascript function processVolcanoFlee(mountain, onComplete) { var finishVolcano = function() { if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText); volcanoActive = false; hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); onComplete(); }; if (!mountain.creatureStack || mountain.creatureStack.length === 0) { finishVolcano(); return; } var topC = mountain.creatureStack[mountain.creatureStack.length - 1]; if (topC.creatureData.flying || topC.creatureData.swimming || topC.creatureData.fly || topC.creatureData.swim) { volcanoHeaderText.setText("Lucky creatures!"); setTimeout(function() { finishVolcano(); }, 1500); return; } volcanoHeaderText.setText("Unlucky creature - Flee to survive!"); setTimeout(function() { var validEscapes = []; var neighbors = getAdjacentTerrains(mountain.gridX, mountain.gridY, true); mountain.creatureStack.pop(); for (var n = 0; n < neighbors.length; n++) { if (neighbors[n] && canPlaceCreatureOnTerrain(topC, neighbors[n])) validEscapes.push(neighbors[n]); } mountain.creatureStack.push(topC); if (validEscapes.length === 0) { volcanoHeaderText.setText("This creature cannot escape and will be destroyed!\nClick it to say goodbye"); var doomHlt = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); doomHlt.tint = 0xFF0000; doomHlt.alpha = 0.7; doomHlt.scaleX = topC.scaleX * 1.15; doomHlt.scaleY = topC.scaleY * 1.15; doomHlt.x = topC.x; doomHlt.y = topC.y; doomHlt.down = function() { if (doomHlt.parent) doomHlt.parent.removeChild(doomHlt); var stack = mountain.creatureStack; stack.pop(); graveyard.push(topC.creatureData); scryDeniedNextRound = true; for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC); if (topC.parent) topC.parent.removeChild(topC); processVolcanoFlee(mountain, onComplete); }; game.addChild(doomHlt); } else { volcanoHeaderText.setText("Choose a new home!"); var fleeHighlights = []; for (var e = 0; e < validEscapes.length; e++) { var esc = validEscapes[e]; var hlt = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); hlt.tint = 0xFFFF00; hlt.alpha = 0.6; hlt.scaleX = esc.scaleX * 1.15; hlt.scaleY = esc.scaleY * 1.15; hlt.x = esc.x; hlt.y = esc.y; hlt.targetEscape = esc; hlt.isFleeHighlight = true; hlt.down = function() { var chosenEsc = this.targetEscape; for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]); var movingC = mountain.creatureStack.pop(); placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); setTimeout(function() { processVolcanoFlee(mountain, onComplete); }, 400); // Short pause before evaluating the next creature }; game.addChild(hlt); fleeHighlights.push(hlt); } } }, 1500); // Cinematic pause before showing escapes or death } ```
User prompt
The Giant Snail's "Shell" ability isn't triggering because `applyCreatureEffect` is no longer being called when creatures are placed. Also, the "Volcano" event (BE04) needs to physically transform into an Advanced Terrain card and permanently alter the board instead of just discarding itself. Please make these exact 3 changes: 1. RESTORE CREATURE EFFECTS ON PLACEMENT: - Locate the `placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY)` function. - Scroll down towards the bottom of that function, right below the `tween(creatureCard, { ... });` placement animation block. - Insert this line to ensure the Shell (and other on-play effects) actually trigger: ```javascript applyCreatureEffect(creatureCard); ``` 2. FIX THE SHELL INTERCEPTOR UI: - Locate `showEventCardDebugBox()`. - Inside the `if (game.activeShellCreature)` block, find the text: `"Shell effect! Select the event you wish to negate. Shell will then be removed."` - Ensure the `y` coordinate for `game.dangerWarningText` is set to `200` (not `130`) so it doesn't overlap the gameStatusText: `game.dangerWarningText.y = 200;` 3. TRANSFORM VOLCANO INTO ADVANCED TERRAIN: - Locate the `processVolcanoEvent(onComplete)` function. - Find the `highlight.down = function() {` block inside it. - Change the `processVolcanoFlee` call to pass the `selectedMountain` so the event knows where to drop the new Volcano terrain when the fleeing is finished. Update the call to look like this: ```javascript processVolcanoFlee(selectedMountain, onComplete); ``` - Now locate the `processVolcanoFlee(mountain, onComplete)` function. - Find the block right at the top that handles when the event is over: ```javascript if (!mountain.creatureStack || mountain.creatureStack.length === 0) { if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText); volcanoActive = false; onComplete(); return; // Event over } ``` - REPLACE that block, AND the "Lucky creatures" block right below it, with this new logic that builds the Volcano terrain: ```javascript var finishVolcano = function() { if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText); volcanoActive = false; // Create the Volcano Advanced Terrain var volcanoData = { type: 'advanced', level: 'Advanced', cardType: 'terrain', subtype: 'land', landType: 'mountain', id: 'VOLCANO', colorBand: 'hot', climateRequirement: 'any', name: 'Volcano', climate: 'hot', special: true }; var volcanoCard = new TerrainCard(volcanoData); var mX = mountain.gridX; var mY = mountain.gridY; // Inherit existing stack and force climate change volcanoCard.creatureStack = mountain.creatureStack || []; mountain.terrainData.climate = 'hot'; // Place it physically on the board volcanoCard.gridX = mX; volcanoCard.gridY = mY; volcanoCard.isInPlay = true; volcanoCard.x = mountain.x; volcanoCard.y = mountain.y; volcanoCard.scaleX = mountain.scaleX; volcanoCard.scaleY = mountain.scaleY; planetBoard[mY][mX] = volcanoCard; planetSlots[mY][mX].terrainCard = volcanoCard; volcanoCard.basicTerrainUnderneath = mountain.basicTerrainUnderneath || mountain; var mIndex = game.getChildIndex(mountain); game.addChildAt(volcanoCard, mIndex + 1); for (var i = 0; i < volcanoCard.creatureStack.length; i++) { var c = volcanoCard.creatureStack[i]; game.removeChild(c); game.addChild(c); } for (var l = 0; l < linkLines.length; l++) { game.removeChild(linkLines[l]); game.addChild(linkLines[l]); } // The event card is NOT discarded, it became the volcano! We call a custom complete to bypass the discard hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); }; if (!mountain.creatureStack || mountain.creatureStack.length === 0) { finishVolcano(); return; } var topC = mountain.creatureStack[mountain.creatureStack.length - 1]; if (topC.creatureData.flying || topC.creatureData.swimming) { volcanoHeaderText.setText("Lucky creatures!"); setTimeout(function() { finishVolcano(); }, 1500); return; } ``` - Finally, locate the `EventCard` class and find `if (self.eventData.id === 'BE04')`. Change it to: ```javascript } else if (self.eventData.id === 'BE04') { // Do not run finalizeEvent here, because Volcano transforms into terrain and handles its own phase transition processVolcanoEvent(function(){}); } ```
User prompt
This is Part 2 of the Event Engine update. We are implementing the highly interactive "Volcano" (BE04) event. Please make this exact change: 1. ADD THE VOLCANO EVENT SYSTEM: - Scroll to the very bottom of the script, right after `processDiseaseEvent()`, and paste this entire block of code: ```javascript var volcanoActive = false; var volcanoHeaderText = null; function processVolcanoEvent(onComplete) { var targets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') { // Priority: Only mountains with creatures, OR any mountain if none have creatures if (terrain.creatureStack && terrain.creatureStack.length > 0) { targets.push(terrain); } } } } // If no occupied mountains exist, grab all empty mountains if (targets.length === 0) { for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') { targets.push(terrain); } } } } if (targets.length === 0) { onComplete(); return; } // No mountains at all volcanoActive = true; volcanoHeaderText = new Text2("Select a Mountain to become a raging volcano!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); volcanoHeaderText.x = 1024; volcanoHeaderText.y = 120; game.addChild(volcanoHeaderText); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xDDA0DD; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isVolcanoHighlight = true; highlight.down = function() { if (!volcanoActive) return; var selectedMountain = this.targetTerrain; // Cleanup mountain highlights for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isVolcanoHighlight) game.removeChild(game.children[j]); } // Start the recursive fleeing logic processVolcanoFlee(selectedMountain, onComplete); }; game.addChild(highlight); } } function processVolcanoFlee(mountain, onComplete) { if (!mountain.creatureStack || mountain.creatureStack.length === 0) { if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText); volcanoActive = false; onComplete(); return; // Event over } var topC = mountain.creatureStack[mountain.creatureStack.length - 1]; if (topC.creatureData.flying || topC.creatureData.swimming) { volcanoHeaderText.setText("Lucky creatures!"); setTimeout(function() { if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText); volcanoActive = false; onComplete(); }, 1500); return; } volcanoHeaderText.setText("Unlucky creature - Flee to survive!"); // Find valid escape routes var validEscapes = []; var neighbors = getAdjacentTerrains(mountain.gridX, mountain.gridY, true); // Diagonals included // Temporarily pop the creature to prevent self-collision during checks mountain.creatureStack.pop(); for (var n = 0; n < neighbors.length; n++) { var adj = neighbors[n]; if (adj && canPlaceCreatureOnTerrain(topC, adj)) { validEscapes.push(adj); } } // Put it back mountain.creatureStack.push(topC); if (validEscapes.length === 0) { // Die without event card var stack = mountain.creatureStack; stack.pop(); graveyard.push(topC.creatureData); scryDeniedNextRound = true; for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC); if (topC.parent) topC.parent.removeChild(topC); // Check next creature down processVolcanoFlee(mountain, onComplete); } else { volcanoHeaderText.setText("Choose a new home!"); var fleeHighlights = []; for (var e = 0; e < validEscapes.length; e++) { var esc = validEscapes[e]; var hlt = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); hlt.tint = 0xFFFF00; hlt.alpha = 0.6; hlt.scaleX = esc.scaleX * 1.15; hlt.scaleY = esc.scaleY * 1.15; hlt.x = esc.x; hlt.y = esc.y; hlt.targetEscape = esc; hlt.isFleeHighlight = true; hlt.down = function() { var chosenEsc = this.targetEscape; for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]); // Move creature physically var movingC = mountain.creatureStack.pop(); placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); // Check next creature down processVolcanoFlee(mountain, onComplete); }; game.addChild(hlt); fleeHighlights.push(hlt); } } }
User prompt
Please make these exact 4 changes: 1. UPDATE THE EVENT DECK GENERATOR: - Locate `createEventDecks()`. Replace the `basicEventDeck.push` section with this updated list: ```javascript basicEventDeck = []; basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE01', name: "Time fly's" }); basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE02', name: 'Devolution' }); basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE03', name: 'Disease' }); basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE04', name: 'Volcano' }); for (var i = 5; i <= 10; i++) { basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE0' + i, name: 'Basic Event ' + i }); } ``` 2. UPDATE EVENT UI STRINGS: - Locate `showCardInfo(card)`. Under the `else if (card.eventData)` block, update the descriptions: ```javascript if (evt.id === 'BE01') infoString += " | Time fly's: The top 3 cards of the main deck are discarded"; if (evt.id === 'BE02') infoString += " | Devolution: Choose an advanced carnivore, it will be removed from play into the discard pile"; if (evt.id === 'BE03') infoString += " | Disease: Place an extinction marker on a Basic Creature...Twice!"; if (evt.id === 'BE04') infoString += " | Volcano: Place on a mountain. Creatures without flying/swimming must flee to adjacent stacks or die!"; ``` 3. ADD THE EVENT ROUTER FOR DISEASE: - Locate the `EventCard` class, inside `self.down = function`. - Find the `if (self.eventData.id === 'BE01')` logic block at the bottom of the function. Update the router to include BE03: ```javascript if (self.eventData.id === 'BE01') { for (var i = 0; i < 3; i++) { if (mainDeck.length > 0) discardPile.push(mainDeck.pop()); } deckCountText.setText("Deck: " + mainDeck.length); discardCountText.setText("Discard: " + discardPile.length); finalizeEvent(); } else if (self.eventData.id === 'BE02') { processDevolutionEvent(finalizeEvent); } else if (self.eventData.id === 'BE03') { processDiseaseEvent(finalizeEvent); } else if (self.eventData.id === 'BE04') { processVolcanoEvent(finalizeEvent); // To be added in next prompt } else { finalizeEvent(); } ``` 4. ADD THE DISEASE INTERACTION LOGIC: - Scroll to the very bottom of the script file and add this new function: ```javascript var diseaseActive = false; var diseaseHeaderText = null; function processDiseaseEvent(onComplete) { var targets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.creatureStack) { for (var i = 0; i < terrain.creatureStack.length; i++) { if (terrain.creatureStack[i].creatureData.level === 'Basic') targets.push(terrain.creatureStack[i]); } } } } if (targets.length === 0) { onComplete(); return; } diseaseActive = true; var picks = 0; diseaseHeaderText = new Text2("Select a creature to become sick!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); diseaseHeaderText.x = 1024; diseaseHeaderText.y = 120; game.addChild(diseaseHeaderText); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xDDA0DD; highlight.alpha = 0.6; highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetCreature = target; highlight.isDiseaseHighlight = true; highlight.down = function() { if (!diseaseActive) return; var tC = this.targetCreature; tC.extinctionMarkers += 1; tC.updateExtinctionMarkers(); picks++; if (picks === 1) { diseaseHeaderText.setText("And now another sickens...."); } else if (picks === 2) { for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isDiseaseHighlight) game.removeChild(game.children[j]); } if (diseaseHeaderText && diseaseHeaderText.parent) diseaseHeaderText.parent.removeChild(diseaseHeaderText); diseaseActive = false; onComplete(); } }; game.addChild(highlight); } } ```
User prompt
I need to upgrade the Event system to support separate discard piles, deck reshuffling, and interactive event resolution. I also need to implement the first two Basic Events: "Time fly's" (BE01) and "Devolution" (BE02). Please make these exact 5 changes: 1. FIX EVENT DISCARD PILES & UI: - In your global variables (near the top), replace `var eventDiscardPile = [];` with TWO arrays: ```javascript var basicEventDiscard = []; var advancedEventDiscard = []; ``` - Locate the `game.update` loop at the bottom of the script. Change the `eventDiscardCountText.setText` line to this: ```javascript eventDiscardCountText.setText("Event Discards: B:" + basicEventDiscard.length + " | A:" + advancedEventDiscard.length); ``` 2. ADD DECK RESHUFFLING LOGIC: - Locate `function addEventCardToDeck(creatureLevel)`. Replace that ENTIRE function with this updated version that checks for empty decks and reshuffles the discards: ```javascript function addEventCardToDeck(creatureLevel) { var eventCard = null; if (creatureLevel === 'Advanced') { if (advancedEventDeck.length === 0 && advancedEventDiscard.length > 0) { advancedEventDeck = advancedEventDiscard.slice(); advancedEventDiscard = []; shuffleEventDeck(advancedEventDeck); } if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop(); else if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop(); } else { if (basicEventDeck.length === 0 && basicEventDiscard.length > 0) { basicEventDeck = basicEventDiscard.slice(); basicEventDiscard = []; shuffleEventDeck(basicEventDeck); } if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop(); else if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop(); } if (eventCard) discardPile.push(eventCard); } ``` 3. GENERATE THE NEW EVENTS & UI TEXT: - Locate `createEventDecks()`. Replace the `basicEventDeck` generation block with this: ```javascript basicEventDeck = []; basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE01', name: "Time fly's" }); basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE02', name: 'Devolution' }); for (var i = 3; i <= 10; i++) { basicEventDeck.push({ type: 'basic', cardType: 'event', level: 'Basic', id: 'BE0' + i, name: 'Basic Event ' + i }); } ``` - Locate `showCardInfo(card)`. At the bottom of that function, right before `cardInfoText = new Text2`, insert this block so Event cards display their rules: ```javascript } else if (card.eventData) { var evt = card.eventData; infoString = evt.name + " | Level: " + evt.level + " Event"; if (evt.id === 'BE01') infoString += " | Time fly's: The top 3 cards of the main deck are discarded"; if (evt.id === 'BE02') infoString += " | Devolution: Choose an advanced carnivore, it will be removed from play into the discard pile"; ``` 4. REWRITE EVENT CARD EXECUTION: - Locate the `EventCard` class, and find `self.down = function (x, y, obj) {`. - Replace that ENTIRE `self.down` block with this new Execution Engine: ```javascript self.down = function (x, y, obj) { if (!self.isInPlay && playerHand.indexOf(self) !== -1) { if (self.isShellTarget) return; // Handled by Shell interceptor var handIndex = playerHand.indexOf(self); if (handIndex !== -1) playerHand.splice(handIndex, 1); if (self.parent) self.parent.removeChild(self); // Define cleanup callback so interactive events can pause resolution var finalizeEvent = function() { if (self.eventData.level === 'Advanced') advancedEventDiscard.push(self.eventData); else basicEventDiscard.push(self.eventData); updateHandPositions(); var moreEvents = false; for (var i = 0; i < playerHand.length; i++) { if (playerHand[i].eventData) { moreEvents = true; break; } } if (!moreEvents) { hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); if (game.eventDebugBox && game.eventDebugBox.parent) { game.eventDebugBox.parent.removeChild(game.eventDebugBox); game.eventDebugBox = null; } var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); } }; // Execute specific event logic if (self.eventData.id === 'BE01') { for (var i = 0; i < 3; i++) { if (mainDeck.length > 0) discardPile.push(mainDeck.pop()); } deckCountText.setText("Deck: " + mainDeck.length); discardCountText.setText("Discard: " + discardPile.length); finalizeEvent(); } else if (self.eventData.id === 'BE02') { processDevolutionEvent(finalizeEvent); } else { finalizeEvent(); // Fallback for unimplemented events } } }; ``` 5. ADD DEVOLUTION BOARD INTERACTION LOGIC: - Scroll to the very bottom of your script file and add this new interaction function: ```javascript var devolutionActive = false; var devolutionHeaderText = null; function processDevolutionEvent(onComplete) { var targets = []; for (var y = 0; y < planetHeight; y++) { for (var x = 0; x < planetWidth; x++) { var terrain = planetBoard[y][x]; if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) { var topC = terrain.creatureStack[terrain.creatureStack.length - 1]; if (topC.creatureData.dietType === 'carnivore' && topC.creatureData.level === 'Advanced') targets.push(topC); } } } if (targets.length === 0) { onComplete(); // No targets, event fizzles instantly } else { devolutionActive = true; devolutionHeaderText = new Text2("Select an advanced carnivore to go back in time!", { size: 56, fill: 0xFF0000, align: 'center', anchorX: 0.5, anchorY: 0 }); devolutionHeaderText.x = 1024; devolutionHeaderText.y = 120; game.addChild(devolutionHeaderText); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xDDA0DD; highlight.alpha = 0.6; // Light purple highlight.scaleX = target.scaleX * 1.15; highlight.scaleY = target.scaleY * 1.15; highlight.x = target.x; highlight.y = target.y; highlight.targetCreature = target; highlight.isDevolutionHighlight = true; highlight.down = function() { if (!devolutionActive) return; var tC = this.targetCreature; for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target); for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC); var stack = planetBoard[tC.gridY][tC.gridX].creatureStack; var idx = stack.indexOf(tC); if (idx !== -1) stack.splice(idx, 1); discardPile.push(tC.creatureData); discardCountText.setText("Discard: " + discardPile.length); if (tC.parent) tC.parent.removeChild(tC); for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isDevolutionHighlight) game.removeChild(game.children[j]); } if (devolutionHeaderText && devolutionHeaderText.parent) devolutionHeaderText.parent.removeChild(devolutionHeaderText); devolutionActive = false; onComplete(); // Finish event resolution }; game.addChild(highlight); } } } ```
User prompt
The previous integration failed because of mixed syntax (`+= 40` vs `= nextYOffset + 40`). Please make these exact, literal changes: 1. UPDATE THE 'DELICATE' UI TEXT: - Locate the `showCardInfo(card)` function. - Find the exact line: `infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to Advanced, it gets an unmitigable extinction marker during the night phase.";` - Replace it entirely with: ```javascript infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to or is an advanced terrain, it gets an unmitigable extinction marker during the night phase."; ``` 2. IMPROVE PLACEMENT HIGHLIGHT VISIBILITY: - Locate the `PlanetSlot` class near the top of the file. - Inside it, find the `self.highlight = function () {` block. - Find the lines defining `self.pinkBorder.tint`, `alpha`, `scaleX`, and `scaleY`. - Replace those specific lines with these: ```javascript self.pinkBorder.tint = 0xFFFF00; // Bright Yellow self.pinkBorder.alpha = 0.6; self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.15; self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.15; ``` 3. FIX SPECIAL TEXT STARTING POSITION: - Locate the `CreatureCard` class. - Find the exact line: `var nextYOffset = -80; // Start position below card name` - Replace it with: ```javascript var nextYOffset = -50; // Start position lower to avoid overlapping the creature name ``` 4. FIX SPECIAL TEXT SPACING (PART A): - Search inside the `CreatureCard` class for the exact string: `nextYOffset = nextYOffset + 40;` - Replace EVERY instance of that string with: `nextYOffset = nextYOffset + 28;` 5. FIX SPECIAL TEXT SPACING (PART B): - Search inside the `CreatureCard` class for the exact string: `nextYOffset += 40;` - Replace EVERY instance of that string with: `nextYOffset += 28;`
User prompt
I need to implement 2 new special rules: "Extinction Trigger" (BH05) and "Shell" (BH33). Please make these exact 5 changes: 1. UPDATE THE DECK GENERATORS & INFO STRINGS: - In `createInitialDeck()`, inside the `basicHerbivoreConfigs` push loop, add: `extinctionTrigger: config.id === 'BH05' ? true : false,` `shell: config.id === 'BH33' ? true : false,` - In `showCardInfo(card)`, append these to the info checks: ```javascript if (creature && creature.extinctionTrigger) infoString += " | Extinction Trigger! If this creature goes extinct it adds +3 points as an end of game score bonus instead of generating an event card!"; if (creature && creature.shell) infoString += " | Shell: When placed, gets a shell token. The next event drawn is negated and returned to the event deck! If covered, the shell is lost."; ``` 2. ADD VISUAL TEXT TO THE CARDS: - In the `CreatureCard` class cascading text section, add: ```javascript if (self.creatureData && self.creatureData.extinctionTrigger) { self.extinctionTriggerText = new Text2("Extinction Trigger", { size: 20, fill: 0xFF69B4, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.extinctionTriggerText.anchor.set(0.5, 0.5); self.extinctionTriggerText.x = 0; self.extinctionTriggerText.y = nextYOffset; self.addChild(self.extinctionTriggerText); nextYOffset += 40; } if (self.creatureData && self.creatureData.shell) { self.shellText = new Text2("Shell", { size: 24, fill: 0xD2B48C, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.shellText.anchor.set(0.5, 0.5); self.shellText.x = 0; self.shellText.y = nextYOffset; self.addChild(self.shellText); nextYOffset += 40; } ``` 3. ADD THE BONUS PILE AND FIX THE GRAVEYARD LOGIC: - At the top of your global variables section (near `var graveyard = [];`), add `var bonusPile = [];`. - Locate the `CreatureCard` class and find the `self.die = function () {` block. - Replace the graveyard logic inside `self.die` with this interceptor: ```javascript if (self.creatureData.extinctionTrigger) { // Extinction Trigger bypasses the graveyard, events, and scry penalty bonusPile.push(self.creatureData); } else { // Normal death graveyard.push(self.creatureData); scryDeniedNextRound = true; // Deny scry addEventCardToDeck(self.creatureData.level); // Draw event if (self.creatureData.unlucky) addEventCardToDeck(self.creatureData.level); } ``` *(Keep the `tween(self, { alpha: 0 ...` death animation below it intact).* 4. IMPLEMENT THE SHELL PLACEMENT & REMOVAL: - Locate `applyCreatureEffect(creatureCard)`. Inside it, add the shell generator: ```javascript if (creature.shell) { var shellMarker = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); shellMarker.tint = 0x8B4513; // Saddle Brown shellMarker.x = 0; shellMarker.y = -100; // Top prominent placement creatureCard.addChild(shellMarker); creatureCard.shellMarker = shellMarker; game.activeShellCreature = creatureCard; // Track globally } ``` - Locate `placeCreatureOnStack`. When a creature is covered, we must destroy the shell. Find the `if (creatureCard.creatureData.dietType === 'carnivore' && terrainCard.creatureStack.length > 0)` block. Right BEFORE it, add: ```javascript if (terrainCard.creatureStack.length > 0) { var coveredC = terrainCard.creatureStack[terrainCard.creatureStack.length - 1]; if (coveredC.shellMarker) { coveredC.removeChild(coveredC.shellMarker); coveredC.shellMarker = null; if (game.activeShellCreature === coveredC) game.activeShellCreature = null; } } ``` 5. INTERCEPT THE EVENT SYSTEM WITH THE SHELL: - Locate `showEventCardDebugBox()`. Completely replace that entire function with this updated Shell-aware logic: ```javascript function showEventCardDebugBox() { if (game.activeShellCreature) { // Shell Interceptor Mode if (!game.dangerWarningText) { game.dangerWarningText = new Text2("Shell effect! Select the event you wish to negate. Shell will then be removed.", { size: 36, fill: 0x00FF00 }); game.dangerWarningText.anchor.set(0.5, 0.5); game.dangerWarningText.x = 1024; game.dangerWarningText.y = 130; game.addChild(game.dangerWarningText); } // Make all events in hand clickable for negation for (var i = 0; i < playerHand.length; i++) { var card = playerHand[i]; if (card.eventData) { card.isShellTarget = true; card.tint = 0x00FF00; // Glow green to indicate selectable // Temporarily override the down function card.originalDown = card.down; card.down = function() { var c = this; // Remove shell globally if (game.activeShellCreature && game.activeShellCreature.shellMarker) { game.activeShellCreature.removeChild(game.activeShellCreature.shellMarker); game.activeShellCreature.shellMarker = null; } game.activeShellCreature = null; if (game.dangerWarningText) { game.dangerWarningText.parent.removeChild(game.dangerWarningText); game.dangerWarningText = null; } // Return event to deck instead of resolving var hIdx = playerHand.indexOf(c); if (hIdx !== -1) playerHand.splice(hIdx, 1); if (c.eventData.level === 'Advanced') advancedEventDeck.push(c.eventData); else basicEventDeck.push(c.eventData); if (c.parent) c.parent.removeChild(c); // Clean up other events in hand (restore original down functions) var moreEvents = false; for (var k = 0; k < playerHand.length; k++) { if (playerHand[k].eventData) { playerHand[k].tint = 0xFFFFFF; playerHand[k].down = playerHand[k].originalDown; playerHand[k].isShellTarget = false; moreEvents = true; } } updateHandPositions(); // If no more events, progress phase if (!moreEvents) { hasEventCardsInHand = false; gamePhase = 'noon'; gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber); var remaining = playerHand.slice(); if (remaining.length > 0) discardCards(remaining); } else { // If other events remain, show normal debug box showEventCardDebugBox(); } }; } } } else { // Normal Event Mode var eventDebugBox = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 1.0 }); eventDebugBox.tint = 0xA020F0; eventDebugBox.alpha = 0.95; eventDebugBox.x = 1024; eventDebugBox.y = 1000; game.addChild(eventDebugBox); var debugText = new Text2("Event card/s in hand! Click the cards to resolve them.", { size: 34, fill: 0xFFFFFF }); debugText.anchor.set(0.5, 0.5); debugText.x = 0; debugText.y = 0; eventDebugBox.addChild(debugText); game.eventDebugBox = eventDebugBox; } } ```
User prompt
I need to implement a new special rule: "Terra-former" (BH32). This rule allows the player to click and devolve/swap an adjacent terrain tile for a random unused basic terrain from the pool. Please make these exact 4 changes: 1. UPDATE THE DECK GENERATOR & INFO STRINGS: - In `createInitialDeck()`, locate the `basicHerbivoreConfigs` array. - Inside the push loop for `basicHerbivoreConfigs`, add this property: `terraformer: config.id === 'BH32' ? true : false,` - Locate `showCardInfo(card)`. Under the `if (creature)` block, add this check: ```javascript if (creature.terraformer) { infoString += " | Terra-former: When played, choose an adjacent empty Advanced terrain and devolve it, swapping the Basic terrain beneath. If no Advanced terrains are available, swap an empty Basic terrain instead."; } ``` 2. ADD VISUAL TEXT TO THE CREATURE CARD: - Locate the `CreatureCard` class. Scroll down to the cascading text section (where `toughText`, etc., are created). - Add this text block: ```javascript if (self.creatureData && self.creatureData.terraformer) { self.terraformerText = new Text2("Terra-former", { size: 24, fill: 0x8B4513, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.terraformerText.anchor.set(0.5, 0.5); self.terraformerText.x = 0; self.terraformerText.y = nextYOffset; self.addChild(self.terraformerText); nextYOffset += 40; } ``` 3. ADD THE ON-PLAY TRIGGER: - Locate the `applyCreatureEffect(creatureCard)` function. - Inside that function, add this trigger: ```javascript if (creature.terraformer) { processTerraformerSpecial(creatureCard.gridX, creatureCard.gridY); } ``` 4. ADD THE TERRA-FORMER INTERACTIVE LOGIC: - Scroll to the very bottom of the script file and add this new function: ```javascript var terraformerActive = false; var terraformerHeaderText = null; function processTerraformerSpecial(originX, originY) { var targets = []; var neighbors = getAdjacentTerrains(originX, originY, true); // true includes diagonals var targetLevel = 'Advanced'; // First pass: Check for empty Advanced terrains for (var i = 0; i < neighbors.length; i++) { var adj = neighbors[i]; if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) { targets.push(adj); } } // Second pass: If no Advanced terrains found, check for empty Basic terrains if (targets.length === 0) { targetLevel = 'Basic'; for (var i = 0; i < neighbors.length; i++) { var adj = neighbors[i]; if (adj && adj.terrainData && adj.terrainData.level === 'Basic' && (!adj.creatureStack || adj.creatureStack.length === 0)) { targets.push(adj); } } } if (targets.length > 0) { terraformerActive = true; terraformerHeaderText = new Text2("Select 1 terrain card to be removed,\nthe basic terrain will then shift", { size: 56, fill: 0xFFFF00, align: 'center', anchorX: 0.5, anchorY: 0 }); terraformerHeaderText.x = 1024; terraformerHeaderText.y = 120; game.addChild(terraformerHeaderText); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0xFFFF00; highlight.alpha = 0.6; // Yellow highlight highlight.scaleX = target.scaleX * 1.1; highlight.scaleY = target.scaleY * 1.1; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isTerraformerHighlight = true; highlight.targetLevel = targetLevel; highlight.down = function() { if (!terraformerActive) return; var tTerrain = this.targetTerrain; var tX = tTerrain.gridX; var tY = tTerrain.gridY; var basicUnder = tTerrain.basicTerrainUnderneath; // 1. If Advanced, discard the Advanced card if (this.targetLevel === 'Advanced') { discardPile.push(tTerrain.terrainData); discardCountText.setText("Discard: " + discardPile.length); if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain); } // 2. Grab a fresh basic terrain from the unused pool if available var targetBasicToSwap = (this.targetLevel === 'Advanced') ? basicUnder : tTerrain; if (basicTerrainPool.length > planetLayout.reduce((a, b) => a + b, 0)) { // Pop a truly unused card from the pool var newBasicData = basicTerrainPool.pop(); // Inherit the climate of the grid slot newBasicData.climate = targetBasicToSwap.terrainData.climate; var newBasicCard = new TerrainCard(newBasicData); newBasicCard.gridX = tX; newBasicCard.gridY = tY; newBasicCard.isInPlay = true; newBasicCard.x = targetBasicToSwap.x; newBasicCard.y = targetBasicToSwap.y; newBasicCard.scaleX = targetBasicToSwap.scaleX; newBasicCard.scaleY = targetBasicToSwap.scaleY; planetBoard[tY][tX] = newBasicCard; planetSlots[tY][tX].terrainCard = newBasicCard; var basicIndex = game.getChildIndex(targetBasicToSwap); if (targetBasicToSwap.parent) targetBasicToSwap.parent.removeChild(targetBasicToSwap); game.addChildAt(newBasicCard, basicIndex); } else { // If pool is empty, just revert Advanced to Basic without swapping if (this.targetLevel === 'Advanced') { planetBoard[tY][tX] = basicUnder; planetSlots[tY][tX].terrainCard = basicUnder; } } // Cleanup UI for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isTerraformerHighlight) game.removeChild(game.children[j]); } if (terraformerHeaderText && terraformerHeaderText.parent) terraformerHeaderText.parent.removeChild(terraformerHeaderText); terraformerActive = false; }; game.addChild(highlight); } } } ```
User prompt
I need to implement 2 new special rules for Advanced Terrain cards: "Simplify" (AT12) and "Displacer" (AT07). Please make these exact 4 changes: 1. UPDATE THE DECK GENERATOR & INFO STRINGS: - In `createInitialDeck()`, locate the `mainDeck.push` loop for `seaCardConfigs`. Add this property inside it: `displacer: seaCardConfigs[i].id === 'AT07' ? true : false,` - Locate the `mainDeck.push` loop for `flatCardConfigs`. Add this property inside it: `simplify: flatCardConfigs[i].id === 'AT12' ? true : false,` - Locate `showCardInfo(card)`. Under the `if (terrain)` block (where AT15 and AT18 are checked), add these two checks: ```javascript if (terrain.simplify) { infoString += " | Simplify: When this terrain is placed, if there are any other Advanced terrain cards adjacent (including diagonals) that have no creature cards upon them you must choose 1 of them and devolve it!"; } if (terrain.displacer) { infoString += " | Displacer: If played onto a stack with 1 or more creatures, move the top creature into the main deck discard pile!"; } ``` 2. ADD VISUAL TEXT TO THE TERRAIN CARDS: - Locate the `TerrainCard` class. Scroll down to where `self.specialText` is added (`if (self.terrainData && self.terrainData.special) { ... }`). - Immediately BELOW the `self.addChild(self.specialText);` line, add this cascading text logic for the new rules: ```javascript var nextTerrainYOffset = 40; if (self.terrainData && self.terrainData.simplify) { var simplifyText = new Text2("Simplify", { size: 24, fill: 0x00BFFF, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); simplifyText.anchor.set(0.5, 0.5); simplifyText.x = 0; simplifyText.y = nextTerrainYOffset; self.addChild(simplifyText); nextTerrainYOffset += 40; } if (self.terrainData && self.terrainData.displacer) { var displacerText = new Text2("Displacer", { size: 24, fill: 0xFF8C00, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); displacerText.anchor.set(0.5, 0.5); displacerText.x = 0; displacerText.y = nextTerrainYOffset; self.addChild(displacerText); nextTerrainYOffset += 40; } ``` 3. ADD THE ON-PLACEMENT TRIGGERS: - Locate the `placeTerrainOnTerrain` function. - Scroll down to the bottom of this function, right AFTER the loop that re-renders `linkLines`, but BEFORE `updateHandPositions();`. - Insert this block to trigger Displacer and Simplify: ```javascript // Trigger Displacer if (advancedTerrainCard.terrainData.displacer && advancedTerrainCard.creatureStack.length > 0) { var topC = advancedTerrainCard.creatureStack.pop(); if (topC.creatureData.dietType === 'carnivore') { for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target); } else { for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC); } var cData = topC.creatureData || topC.terrainData || topC.eventData; discardPile.push(cData); discardCountText.setText("Discard: " + discardPile.length); if (topC.parent) topC.parent.removeChild(topC); } // Trigger Simplify if (advancedTerrainCard.terrainData.simplify) { processSimplifySpecial(gridX, gridY); } ``` 4. ADD SIMPLIFY INTERACTIVE DEVOLUTION LOGIC: - Scroll to the very bottom of the script file and add this new function: ```javascript var simplifyActive = false; var simplifyHeaderText = null; function processSimplifySpecial(originX, originY) { var targets = []; var neighbors = getAdjacentTerrains(originX, originY, true); // true includes diagonals for (var i = 0; i < neighbors.length; i++) { var adj = neighbors[i]; if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) { targets.push(adj); } } if (targets.length > 0) { simplifyActive = true; simplifyHeaderText = new Text2("Select a terrain for devolution!", { size: 56, fill: 0xFFFF00, align: 'center', anchorX: 0.5, anchorY: 0 }); simplifyHeaderText.x = 1024; simplifyHeaderText.y = 120; game.addChild(simplifyHeaderText); for (var t = 0; t < targets.length; t++) { var target = targets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0x0000FF; highlight.alpha = 0.5; highlight.scaleX = target.scaleX * 1.1; highlight.scaleY = target.scaleY * 1.1; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isSimplifyHighlight = true; highlight.down = function() { if (!simplifyActive) return; var tTerrain = this.targetTerrain; var tX = tTerrain.gridX; var tY = tTerrain.gridY; var basicUnder = tTerrain.basicTerrainUnderneath; discardPile.push(tTerrain.terrainData); discardCountText.setText("Discard: " + discardPile.length); planetBoard[tY][tX] = basicUnder; planetSlots[tY][tX].terrainCard = basicUnder; if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain); for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isSimplifyHighlight) game.removeChild(game.children[j]); } if (simplifyHeaderText && simplifyHeaderText.parent) simplifyHeaderText.parent.removeChild(simplifyHeaderText); simplifyActive = false; }; game.addChild(highlight); } } } ```
User prompt
I need to implement 2 new special rules: "Watcher" (BC06) and "Slow" (BC06). Please make these exact 4 changes: 1. UPDATE THE DECK GENERATOR & INFO STRINGS: - In `createInitialDeck()`, inside the `basicCarnivoreConfigs` push loop, add these properties: `watcher: config.id === 'BC06' ? true : false,` `slow: config.id === 'BC06' ? true : false,` - In `showCardInfo(card)`, append these to the info checks: ```javascript if (creature && creature.watcher) { infoString += " | Watcher: When played you may return an event card from the main discard pile to its respective event deck!"; } if (creature && creature.slow) { infoString += " | Slow: This creature is not quick enough to catch creatures that have flying!"; } ``` 2. ADD VISUAL TEXT TO THE CARDS: - In the `CreatureCard` class cascading text section, add: ```javascript if (self.creatureData && self.creatureData.watcher) { self.watcherText = new Text2("Watcher", { size: 24, fill: 0x9370DB, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.watcherText.anchor.set(0.5, 0.5); self.watcherText.x = 0; self.watcherText.y = nextYOffset; self.addChild(self.watcherText); nextYOffset += 40; } if (self.creatureData && self.creatureData.slow) { self.slowText = new Text2("Slow", { size: 24, fill: 0x8B4513, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.slowText.anchor.set(0.5, 0.5); self.slowText.x = 0; self.slowText.y = nextYOffset; self.addChild(self.slowText); nextYOffset += 40; } ``` 3. ADD SLOW LINK VETO & ON-PLAY TRIGGER: - In `isValidLinkTarget(carnivore, herbivore)`, at the very top (right after `return false;`), insert the Slow veto: ```javascript if (carnivore.creatureData.slow && (herbivore.creatureData.flying || herbivore.creatureData.fly)) { return false; // Slow creatures cannot link to flying creatures } ``` - In `applyCreatureEffect(creatureCard)`, add the Watcher trigger: ```javascript if (creature && creature.watcher) { processWatcherSpecial(); } ``` 4. ADD WATCHER UI LOGIC: - At the bottom of the script, add this new function and global variables: ```javascript var watcherActive = false; var watcherList = []; function processWatcherSpecial() { watcherList = []; for (var i = 0; i < discardPile.length; i++) { if (discardPile[i].cardType === 'event') watcherList.push(discardPile[i]); } watcherActive = true; if (watcherList.length === 0) { var notifyBox = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.5, tint: 0x4169E1, alpha: 0.9, x: 1024, y: 1366 }); var notifyText = new Text2("No Event cards in Discard - Watcher does not apply", { size: 28, fill: 0xFFFFFF, anchorX: 0.5, anchorY: 0.5 }); notifyBox.addChild(notifyText); game.addChild(notifyBox); notifyBox.down = function() { if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox); watcherActive = false; }; } else { var header = new Text2("Choose an event to return to the event deck", { size: 56, fill: 0xFFFF00, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; header.watcherHeader = true; game.addChild(header); for (var i = 0; i < watcherList.length; i++) { var cardContainer = createCardFromData(watcherList[i]); cardContainer.scaleX = 0.6; cardContainer.scaleY = 0.6; cardContainer.x = 512 + (i % 2) * 600; cardContainer.y = 500 + Math.floor(i / 2) * 280; cardContainer.cardDataIndex = i; cardContainer.watcherSelectable = true; cardContainer.down = function() { var selectedData = watcherList[this.cardDataIndex]; for (var d = discardPile.length - 1; d >= 0; d--) { if (discardPile[d] === selectedData || discardPile[d].name === selectedData.name) { discardPile.splice(d, 1); break; } } if (selectedData.level === 'Advanced') advancedEventDeck.push(selectedData); else basicEventDeck.push(selectedData); for (var k = game.children.length - 1; k >= 0; k--) { var c = game.children[k]; if (c.watcherSelectable || c.watcherHeader) game.removeChild(c); } basicEventCountText.setText("Basic Events: " + basicEventDeck.length); advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length); discardCountText.setText("Discard: " + discardPile.length); watcherActive = false; }; game.addChild(cardContainer); } } } ``` - Finally, inside `onMouseMove(x, y)`, add hit-detection for Watcher right below the `forerunnerActive` check: ```javascript if (!cardUnderMouse && typeof watcherActive !== 'undefined' && watcherActive) { for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.watcherSelectable) { var hw = (child.terrainData ? 126 : 80) * child.scaleX; var hh = (child.terrainData ? 168 : 110) * child.scaleY; if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) { cardUnderMouse = child; break; } } } } ```
User prompt
I need to implement 3 new special rules: "Watcher" (BC06), "Slow" (BC06), and "Simplify" (AT12). Please make these exact 5 changes: 1. UPDATE THE DECK GENERATORS & INFO STRINGS: - In `createInitialDeck()`, inside the `basicCarnivoreConfigs` push loop, add: `watcher: config.id === 'BC06' ? true : false,` `slow: config.id === 'BC06' ? true : false,` - In the `flatCardConfigs` array (before the push loop), ensure `AT12` has `special: true` (it already should). - In the `mainDeck.push` loop for `flatCardConfigs`, add: `simplify: config.id === 'AT12' ? true : false,` - In `showCardInfo(card)`, append these to the info checks: ```javascript if (creature && creature.watcher) infoString += " | Watcher: When played you may return an event card from the main discard pile to its respective event deck!"; if (creature && creature.slow) infoString += " | Slow: This creature is not quick enough to catch creatures that have flying!"; if (terrain && terrain.simplify) infoString += " | Simplify: When placed, if there are empty adjacent Advanced terrains (including diagonals), you must devolve one!"; ``` 2. ADD VISUAL TEXT TO THE CARDS: - In the `CreatureCard` class cascading text section, add: ```javascript if (self.creatureData && self.creatureData.watcher) { self.watcherText = new Text2("Watcher", { size: 24, fill: 0x9370DB, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.watcherText.anchor.set(0.5, 0.5); self.watcherText.x = 0; self.watcherText.y = nextYOffset; self.addChild(self.watcherText); nextYOffset += 40; } if (self.creatureData && self.creatureData.slow) { self.slowText = new Text2("Slow", { size: 24, fill: 0x8B4513, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.slowText.anchor.set(0.5, 0.5); self.slowText.x = 0; self.slowText.y = nextYOffset; self.addChild(self.slowText); nextYOffset += 40; } ``` 3. ADD SLOW LINK VETO & ON-PLAY TRIGGERS: - In `isValidLinkTarget`, at the very top (right after `return false;`), insert the Slow veto: ```javascript if (carnivore.creatureData.slow && (herbivore.creatureData.flying || herbivore.creatureData.fly)) return false; ``` - In `applyCreatureEffect(creatureCard)`, add the Watcher trigger: ```javascript if (creature.watcher) processWatcherSpecial(); ``` - In `placeTerrainOnTerrain`, right below the `if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18')` block, add the Simplify trigger: ```javascript if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.simplify) { processSimplifySpecial(gridX, gridY); } ``` 4. ADD WATCHER UI LOGIC (Similar to Forerunner): - At the bottom of the script, add this block: ```javascript var watcherActive = false; var watcherList = []; function processWatcherSpecial() { watcherList = []; for (var i = 0; i < discardPile.length; i++) { if (discardPile[i].cardType === 'event') watcherList.push(discardPile[i]); } watcherActive = true; if (watcherList.length === 0) { var notifyBox = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.5, tint: 0x4169E1, alpha: 0.9, x: 1024, y: 1366 }); var notifyText = new Text2("No Event cards in Discard - Watcher does not apply", { size: 28, fill: 0xFFFFFF, anchorX: 0.5, anchorY: 0.5 }); notifyBox.addChild(notifyText); game.addChild(notifyBox); notifyBox.down = function() { if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox); watcherActive = false; }; } else { var header = new Text2("Choose an event to return to the event deck", { size: 56, fill: 0xFFFF00, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; header.watcherHeader = true; game.addChild(header); for (var i = 0; i < watcherList.length; i++) { var cardContainer = createCardFromData(watcherList[i]); cardContainer.scaleX = 0.6; cardContainer.scaleY = 0.6; cardContainer.x = 512 + (i % 2) * 600; cardContainer.y = 500 + Math.floor(i / 2) * 280; cardContainer.cardDataIndex = i; cardContainer.watcherSelectable = true; cardContainer.down = function() { var selectedData = watcherList[this.cardDataIndex]; for (var d = discardPile.length - 1; d >= 0; d--) { if (discardPile[d] === selectedData) { discardPile.splice(d, 1); break; } } if (selectedData.level === 'Advanced') advancedEventDeck.push(selectedData); else basicEventDeck.push(selectedData); for (var k = game.children.length - 1; k >= 0; k--) { var c = game.children[k]; if (c.watcherSelectable || c.watcherHeader) game.removeChild(c); } basicEventCountText.setText("Basic Events: " + basicEventDeck.length); advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length); discardCountText.setText("Discard: " + discardPile.length); watcherActive = false; }; game.addChild(cardContainer); } } } ``` - In `onMouseMove`, right below the `forerunnerActive` check, add hit-detection for Watcher: ```javascript if (!cardUnderMouse && typeof watcherActive !== 'undefined' && watcherActive) { for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.watcherSelectable && Math.abs(child.x - x) < 80 * child.scaleX && Math.abs(child.y - y) < 110 * child.scaleY) { cardUnderMouse = child; break; } } } ``` 5. ADD SIMPLIFY BOARD INTERACTION LOGIC: - At the bottom of the script, add this block: ```javascript var simplifyActive = false; var simplifyTargets = []; var simplifyHeaderText = null; function processSimplifySpecial(originX, originY) { simplifyTargets = []; var neighbors = getAdjacentTerrains(originX, originY, true); // True includes diagonals for (var i = 0; i < neighbors.length; i++) { var adj = neighbors[i]; if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) { simplifyTargets.push(adj); } } if (simplifyTargets.length > 0) { simplifyActive = true; simplifyHeaderText = new Text2("Select a terrain for devolution!", { size: 56, fill: 0xFFFF00, anchorX: 0.5, anchorY: 0 }); simplifyHeaderText.x = 1024; simplifyHeaderText.y = 120; game.addChild(simplifyHeaderText); for (var t = 0; t < simplifyTargets.length; t++) { var target = simplifyTargets[t]; var highlight = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5 }); highlight.tint = 0x0000FF; highlight.alpha = 0.5; // Blue highlight highlight.scaleX = target.scaleX * 1.1; highlight.scaleY = target.scaleY * 1.1; highlight.x = target.x; highlight.y = target.y; highlight.targetTerrain = target; highlight.isSimplifyHighlight = true; game.addChild(highlight); } } } ``` - In `game.up = function (x, y, obj) {`, AT THE VERY TOP of the function, insert this interceptor to handle the clicks: ```javascript if (simplifyActive) { for (var k = game.children.length - 1; k >= 0; k--) { var child = game.children[k]; if (child.isSimplifyHighlight) { var tHW = 126 * child.scaleX; var tHH = 168 * child.scaleY; if (Math.abs(child.x - x) < tHW && Math.abs(child.y - y) < tHH) { var targetTerrain = child.targetTerrain; var tX = targetTerrain.gridX; var tY = targetTerrain.gridY; var basicUnderneath = targetTerrain.basicTerrainUnderneath; // Devolve! discardPile.push(targetTerrain.terrainData); discardCountText.setText("Discard: " + discardPile.length); // Restore basic terrain visually and logically planetBoard[tY][tX] = basicUnderneath; planetSlots[tY][tX].terrainCard = basicUnderneath; game.removeChild(targetTerrain); // Remove advanced card // Cleanup UI for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isSimplifyHighlight) game.removeChild(game.children[j]); } if (simplifyHeaderText && simplifyHeaderText.parent) simplifyHeaderText.parent.removeChild(simplifyHeaderText); simplifyActive = false; simplifyTargets = []; return; // End click } } } return; // If they clicked somewhere else, ignore it until they pick a target } ```
User prompt
I need to implement 2 new special rules: "Shy" (AC05) and "Unlucky" (AC07). Please make these exact 4 changes: 1. UPDATE THE DECK GENERATOR & INFO STRINGS: - Locate `createInitialDeck()`. - In the `advancedCarnivoreConfigs` array push loop, add these properties: `shy: config.id === 'AC05' ? true : false,` `unlucky: config.id === 'AC07' ? true : false,` - Locate `showCardInfo(card)`, and add these to the info checks: ```javascript if (creature && creature.shy) { infoString += " | Shy: Can only be played on an empty Advanced terrain card, no need to evolve or devour!"; } if (creature && creature.unlucky) { infoString += " | Unlucky: If this card becomes extinct it will generate an extra advanced event!"; } ``` 2. ADD VISUAL TEXT TO THE CARDS: - Locate the `CreatureCard` class. Scroll down to the cascading text section. - Add these text blocks using the established pattern: ```javascript if (self.creatureData && self.creatureData.shy) { self.shyText = new Text2("Shy", { size: 24, fill: 0xADD8E6, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.shyText.anchor.set(0.5, 0.5); self.shyText.x = 0; self.shyText.y = nextYOffset; self.addChild(self.shyText); nextYOffset += 40; } if (self.creatureData && self.creatureData.unlucky) { self.unluckyText = new Text2("Unlucky", { size: 24, fill: 0x8B0000, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.unluckyText.anchor.set(0.5, 0.5); self.unluckyText.x = 0; self.unluckyText.y = nextYOffset; self.addChild(self.unluckyText); nextYOffset += 40; } ``` 3. ADD PLACEMENT BYPASS FOR SHY: - Locate `canPlaceCreatureOnTerrain(creatureCard, terrainCard)`. - Scroll down to the bottom of the function where the "No creatures in stack" check lives: ```javascript } else { // No creatures in stack - advanced creatures cannot be placed on empty terrain if (creature.level === 'Advanced') { return false; // Advanced creatures require existing creatures to stack on } } ``` - Replace that ENTIRE `} else { ... }` block with this: ```javascript } else { // No creatures in stack if (creature.shy) { // Shy MUST be placed on empty Advanced terrain (terrain.level is already checked above) return true; } else if (creature.level === 'Advanced') { return false; // Normal Advanced creatures require existing creatures to stack on } } ``` - Also, we need to strictly enforce that Shy CANNOT be placed on a stack. Right below the line `if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {`, add: ```javascript if (creature.shy) { return false; // Shy creatures cannot be placed on occupied terrain } ``` 4. ADD GRAVEYARD TRIGGER FOR UNLUCKY: - Locate the `CreatureCard` class and find the `self.die = function () {` block. - Look for the line that says `addEventCardToDeck(self.creatureData.level);`. - Right BELOW that line, add this: ```javascript if (self.creatureData.unlucky) { addEventCardToDeck(self.creatureData.level); // Draw a second event card! } ```
User prompt
This is Part 2 of implementing the "Poisonous", "Delicate", and "Forerunner" rules. We need to implement the Night Phase logic, Danger Highlights, and the Forerunner UI selection system. Please make these exact 4 changes: 1. ADD THE ON-PLAY TRIGGER FOR FORERUNNER: - Locate the `applyCreatureEffect(creatureCard)` function. - Inside that function, add this trigger: ```javascript if (creature.forerunner) { processForerunnerSpecial(); } ``` 2. UPDATE NIGHT PHASE AND DANGER HIGHLIGHTS: - Locate `getCreaturesInDanger()`. Inside the innermost loop checking `creature`, add the logic for Delicate and Poisonous. Insert this right below `if (!isTopCreature) { continue; }`: ```javascript if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') { if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature); } ``` Further down inside the `else if (creature.creatureData.dietType === 'carnivore')` block, insert this: ```javascript for (var p = 0; p < creature.activeLinks.length; p++) { if (creature.activeLinks[p].target.creatureData.poisonous) { if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature); break; } } ``` - Locate `processNightPhase()`. Inside the innermost loop checking `creature`, insert this right below `if (!isTopCreature) { continue; }`: ```javascript if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') { creature.extinctionMarkers += 1; creature.updateExtinctionMarkers(); } ``` Further down inside the `if (creature.creatureData.dietType === 'carnivore')` block, insert this: ```javascript for (var p = 0; p < creature.activeLinks.length; p++) { if (creature.activeLinks[p].target.creatureData.poisonous) { creature.extinctionMarkers += 1; creature.updateExtinctionMarkers(); } } ``` 3. ADD THE FORERUNNER SELECTION SYSTEM: - Scroll to the very bottom of your script, right below where the `clearAT18UI()` function ends, and add this entire block of new Forerunner logic: ```javascript var forerunnerActive = false; var forerunnerList = []; function processForerunnerSpecial() { forerunnerList = discardPile.slice(); // Copy entire discard pile forerunnerActive = true; if (forerunnerList.length === 0) { var notifyBox = LK.getAsset('terrainLandFlat', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.5, tint: 0x4169E1, alpha: 0.9, x: 1024, y: 1366 }); var notifyText = new Text2("Discard pile is empty - Forerunner does not apply", { size: 28, fill: 0xFFFFFF, anchorX: 0.5, anchorY: 0.5 }); notifyBox.addChild(notifyText); game.addChild(notifyBox); notifyBox.down = function() { if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox); forerunnerActive = false; }; } else { var header = new Text2("Choose a card to place on top of the main deck,\nall others will be returned to the discard pile", { size: 48, fill: 0xFFFF00, align: 'center', anchorX: 0.5, anchorY: 0 }); header.x = 1024; header.y = 120; header.forerunnerHeader = true; game.addChild(header); var cardYOffset = 500; var cardYSpacing = 280; for (var i = 0; i < forerunnerList.length; i++) { var cardContainer = createCardFromData(forerunnerList[i]); cardContainer.scaleX = 0.6; cardContainer.scaleY = 0.6; cardContainer.x = 512 + (i % 2) * 600; cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing; cardContainer.cardDataIndex = i; cardContainer.forerunnerSelectable = true; cardContainer.down = function() { var selectedData = forerunnerList[this.cardDataIndex]; for (var d = discardPile.length - 1; d >= 0; d--) { if (discardPile[d] === selectedData || (discardPile[d].id === selectedData.id)) { discardPile.splice(d, 1); break; } } mainDeck.push(selectedData); for (var k = game.children.length - 1; k >= 0; k--) { var c = game.children[k]; if (c.forerunnerSelectable || c.forerunnerHeader) game.removeChild(c); } deckCountText.setText("Deck: " + mainDeck.length); discardCountText.setText("Discard: " + discardPile.length); forerunnerActive = false; }; game.addChild(cardContainer); } } } ``` 4. ENABLE HOVER/ZOOM FOR THE FORERUNNER UI: - Locate the `onMouseMove(x, y)` function. - Look for the `// Check AT18 Special Selection Cards` block. - Right BELOW that AT18 block, insert the Forerunner hit-detection: ```javascript // Check Forerunner Special Selection Cards if (!cardUnderMouse && typeof forerunnerActive !== 'undefined' && forerunnerActive) { for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.forerunnerSelectable) { var hw = (child.terrainData ? 126 : 80) * child.scaleX; var hh = (child.terrainData ? 168 : 110) * child.scaleY; if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) { cardUnderMouse = child; break; } } } } ```
User prompt
We are adding 3 new special rules: "Poisonous", "Delicate", and "Forerunner". I will give you the logic in two parts. This is Part 1. Please make these exact 4 changes to the Data, UI, and Placement logic: 1. UPDATE THE DECK GENERATORS: - Locate `createInitialDeck()`. - Inside the `basicHerbivoreConfigs` array push loop, add these properties: `poisonous: config.id === 'BH20' ? true : false,` `delicate: config.id === 'BH26' ? true : false,` - Inside the `basicCarnivoreConfigs` array push loop, add these properties: `delicate: config.id === 'BC01' ? true : false,` `forerunner: config.id === 'BC07' ? true : false,` 2. ADD THE UI INFORMATION TEXT: - Locate the `showCardInfo(card)` function. - Add these checks to the `infoString` builder (under the other special rules): ```javascript if (creature && creature.poisonous) { infoString += " | Poisonous! Any carnivore cards feeding upon this creature will get an extinction marker during the night phase due to the toxin."; } if (creature && creature.delicate) { infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to Advanced, it gets an unmitigable extinction marker during the night phase."; } if (creature && creature.forerunner) { infoString += " | Forerunner: When you play this card, choose 1 card from the main discard pile and put it on top of the main deck."; } ``` 3. ADD VISUAL TEXT TO THE CARDS: - Locate the `CreatureCard` class. Scroll down to the cascading text section (where `toughText`, `squishyText`, etc., are created). - Add these three text blocks using the established pattern: ```javascript if (self.creatureData && self.creatureData.poisonous) { self.poisonousText = new Text2("Poisonous", { size: 24, fill: 0x8A2BE2, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.poisonousText.anchor.set(0.5, 0.5); self.poisonousText.x = 0; self.poisonousText.y = nextYOffset; self.addChild(self.poisonousText); nextYOffset += 40; } if (self.creatureData && self.creatureData.delicate) { self.delicateText = new Text2("Delicate", { size: 24, fill: 0xFFC0CB, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.delicateText.anchor.set(0.5, 0.5); self.delicateText.x = 0; self.delicateText.y = nextYOffset; self.addChild(self.delicateText); nextYOffset += 40; } if (self.creatureData && self.creatureData.forerunner) { self.forerunnerText = new Text2("Forerunner", { size: 24, fill: 0xFFD700, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.forerunnerText.anchor.set(0.5, 0.5); self.forerunnerText.x = 0; self.forerunnerText.y = nextYOffset; self.addChild(self.forerunnerText); nextYOffset += 40; } ``` 4. ADD PLACEMENT VETO FOR DELICATE: - Locate `canPlaceCreatureOnTerrain(creatureCard, terrainCard)`. - At the very top of the function, right below `if (!creature) { return false; }`, add: ```javascript if (creature.delicate && terrain.level === 'Advanced') { return false; // Delicate creatures cannot be placed on Advanced terrain } ```
User prompt
I need to implement 3 new special rules: "Dominant DNA", "Nutritious", and "Bountiful". Please make these exact 5 changes: 1. CLEAN UP AND UPDATE DECK CONFIGS: - Locate `createInitialDeck()`. - Find the `AH11_DOMINANT` object inside the `advancedHerbivoreConfigs` array and COMPLETELY DELETE it. - Find the `AH11` object inside `advancedHerbivoreConfigs`. Change its `landType: 'mountain'` to `landType: 'any'`. 2. ADD PROPERTIES TO DECK PUSH LOOPS: - In the `mainDeck.push` loop for `basicHerbivoreConfigs`, add this property: `nutritious: config.id === 'BH11' ? true : false,` - In the `mainDeck.push` loop for `advancedCarnivoreConfigs`, add this property: `dominantDNA: config.id === 'AC01' ? true : false,` - In the `mainDeck.push` loop for `advancedHerbivoreConfigs`, add these properties: `dominantDNA: config.id === 'AH11' ? true : false,` `bountiful: config.id === 'AH12' ? true : false,` 3. OVERRIDE SAFE LINKS: - Locate the `CreatureCard` class. - Find the lines where `self.linkRequirement = 3;` is set (inside the `if (self.creatureData.fatty)` block). - Right below that block, add the new safe links overrides: ```javascript if (self.creatureData.nutritious) { self.safeLinks = 2; } if (self.creatureData.bountiful) { self.safeLinks = 3; } ``` 4. UPDATE PLACEMENT LOGIC FOR DOMINANT DNA: - Locate `canPlaceCreatureOnTerrain(creatureCard, terrainCard)`. - Scroll down to the comment `// Check creature stacking rules - verify top card in stack`. - Right BEFORE the line `if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {`, insert this bypass logic: ```javascript // Dominant DNA bypasses ALL creature stacking restrictions (but still obeys terrain/climate checked above) if (creature.dominantDNA && terrainCard.creatureStack.length > 0) { return true; } ``` - Further down in the same function, DELETE the old `if (creature.dominantDNA)` block that was nested inside the `else if (creature.level === 'Advanced')` section, as it is now handled by the bypass above. 5. UPDATE VISUALS AND UI TEXT: - Locate `showCardInfo(card)`. Update the `infoString` checks to match this: ```javascript if (creature && creature.dominantDNA) { infoString += " | Dominant DNA: This creature can evolve from any creature or devour any creature!"; } if (creature && creature.nutritious) { infoString += " | Nutritious: This creature can sustain up to 2 links!"; } if (creature && creature.bountiful) { infoString += " | Bountiful: This creature can sustain up to 3 links!"; } ``` - Locate the visual text generation at the bottom of the `CreatureCard` class. Ensure `dominantDNAText` is there, and add the text generators for Nutritious and Bountiful using the same format: ```javascript if (self.creatureData && self.creatureData.nutritious) { self.nutritiousText = new Text2("Nutritious", { size: 24, fill: 0x32CD32, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.nutritiousText.anchor.set(0.5, 0.5); self.nutritiousText.x = 0; self.nutritiousText.y = nextYOffset; self.addChild(self.nutritiousText); nextYOffset += 40; } if (self.creatureData && self.creatureData.bountiful) { self.bountifulText = new Text2("Bountiful", { size: 24, fill: 0xFFD700, font: "'Arial Black', 'Impact', sans-serif", fontWeight: "bold" }); self.bountifulText.anchor.set(0.5, 0.5); self.bountifulText.x = 0; self.bountifulText.y = nextYOffset; self.addChild(self.bountifulText); nextYOffset += 40; } ```
User prompt
In a previous update, the math for Herbivore extinction markers was fixed, but the `if (creature.extinctionMarkers === 0)` wrapper was accidentally left behind. This is making Herbivores invincible after taking their first marker. Please make this exact change: 1. REMOVE THE HERBIVORE EXTINCTION ARMOR: - Locate the `processNightPhase()` function. - Look inside it for the block that handles herbivores. It currently looks like this: ```javascript } else if (creature.creatureData.dietType === 'herbivore') { // For herbivores: ONLY add extinction marker if it has 0 markers AND has excess links // Once a herbivore has 1+ extinction markers, do NOT add more during Night phase if (creature.extinctionMarkers === 0) { // Count links pointing to them AT THIS MOMENT ONLY // ... (rest of the logic) } } Replace that ENTIRE else if block with this unprotected version: code JavaScript } else if (creature.creatureData.dietType === 'herbivore') { // Count links pointing to them AT THIS MOMENT ONLY var linksToThisHerbivore = 0; for (var j = 0; j < linkLines.length; j++) { if (linkLines[j].herbivore === creature) { linksToThisHerbivore++; } } // Add extinction markers equal to the number of excess links EVERY Night Phase var excessLinks = linksToThisHerbivore - creature.safeLinks; if (excessLinks > 0) { // No cap, no armor! Add exactly as many markers as there are excess links creature.extinctionMarkers += excessLinks; creature.updateExtinctionMarkers(); } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CreatureCard = Container.expand(function (creatureData) {
var self = Container.call(this);
self.creatureData = creatureData || {
type: 'basic',
level: 'Basic',
dietType: 'herbivore',
name: 'Basic Herbivore',
terrainRequirement: 'land',
climateRequirement: 'any'
};
// Create creature visual
var creatureAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureBasicHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
creatureAsset = self.attachAsset('creatureAdvancedHerbivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureBasicCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
creatureAsset = self.attachAsset('creatureAdvancedCarnivore', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add creature type color band at bottom
var typeStripAsset;
if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeBasicHerbivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'herbivore') {
typeStripAsset = 'creatureTypeAdvancedHerbivore';
} else if (self.creatureData.level === 'Basic' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeBasicCarnivore';
} else if (self.creatureData.level === 'Advanced' && self.creatureData.dietType === 'carnivore') {
typeStripAsset = 'creatureTypeAdvancedCarnivore';
}
if (typeStripAsset) {
var typeStrip = self.attachAsset(typeStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
typeStrip.y = 95; // Position at bottom of creature card
}
// Add creature name
self.nameText = new Text2(self.creatureData.name, {
size: 18,
fill: 0xFFFFFF
});
self.nameText.anchor.set(0.5, 0);
self.nameText.x = 0;
self.nameText.y = -100;
self.addChild(self.nameText);
// Add requirement text
var reqText = "Req: ";
if (self.creatureData.terrainRequirement && self.creatureData.terrainRequirement !== 'any') {
reqText += self.creatureData.terrainRequirement;
}
if (self.creatureData.climateRequirement && self.creatureData.climateRequirement !== 'any') {
if (reqText !== "Req: ") {
reqText += ", ";
}
reqText += self.creatureData.climateRequirement;
}
if (reqText !== "Req: ") {
self.requirementText = new Text2(reqText, {
size: 14,
fill: 0xFFFFFF
});
self.requirementText.anchor.set(0.5, 0);
self.requirementText.x = 0;
self.requirementText.y = 60;
self.addChild(self.requirementText);
}
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
self.virusMarkers = 0;
self.virusVisuals = [];
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
if (self.creatureData.level === 'Basic') {
self.linkRequirement = 1; // Basic carnivores need 1 link
} else if (self.creatureData.level === 'Advanced') {
self.linkRequirement = 2; // Advanced carnivores need 2 links
}
} else if (self.creatureData.dietType === 'herbivore') {
if (self.creatureData.level === 'Basic') {
self.safeLinks = 1; // Basic herbivores can safely handle 1 link
} else if (self.creatureData.level === 'Advanced') {
self.safeLinks = 2; // Advanced herbivores can safely handle 2 links
}
}
if (self.creatureData.fatty) {
self.linkRequirement = 3;
}
if (self.creatureData.nutritious) {
self.safeLinks = 2;
}
if (self.creatureData.bountiful) {
self.safeLinks = 3;
}
// Create visual link markers for carnivores
self.createLinkMarkers = function () {
if (self.creatureData.dietType === 'carnivore' && self.linkRequirement > 0) {
for (var i = 0; i < self.linkRequirement; i++) {
var linkMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
linkMarker.tint = 0xFF0000; // Red color for unlinked markers
linkMarker.x = (i - (self.linkRequirement - 1) / 2) * 25;
linkMarker.y = -80;
linkMarker.isLinked = false;
linkMarker.targetHerbivore = null;
linkMarker.carnivore = self;
linkMarker.markerIndex = i;
// Link markers are no longer directly interactive
linkMarker.move = function (x, y, obj) {};
linkMarker.down = function (x, y, obj) {};
self.addChild(linkMarker);
self.linkMarkers.push(linkMarker);
}
// Create personal "Alter Links" button for this carnivore
self.createPersonalAlterLinksButton = function () {
if (self.isInPlay) {
var alterButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2
});
alterButton.tint = 0xFFD700; // Gold color
var alterButtonText = new Text2("Alter Links", {
size: 12,
fill: 0x000000
});
alterButtonText.anchor.set(0.5, 0.5);
alterButton.addChild(alterButtonText);
alterButton.x = 0;
alterButton.y = 0;
alterButton.carnivoreCard = self;
alterButton.down = function () {
if (gamePhase === 'dusk' && linkAdjustmentMode) {
resetCardLinks(self);
}
};
self.addChild(alterButton);
self.personalAlterButton = alterButton;
}
};
}
};
// Create extinction marker visuals
self.extinctionMarkerVisuals = [];
self.updateExtinctionMarkers = function () {
// Remove existing visuals
for (var i = 0; i < self.extinctionMarkerVisuals.length; i++) {
if (self.extinctionMarkerVisuals[i].parent) {
self.extinctionMarkerVisuals[i].parent.removeChild(self.extinctionMarkerVisuals[i]);
}
}
self.extinctionMarkerVisuals = [];
// Create new visuals for current extinction markers
for (var i = 0; i < self.extinctionMarkers; i++) {
var marker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
marker.tint = 0x000000; // Black for extinction markers
marker.x = (i - (self.extinctionMarkers - 1) / 2) * 20;
marker.y = 80;
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
self.updateVirusMarkers = function () {
for (var i = 0; i < self.virusVisuals.length; i++) {
if (self.virusVisuals[i].parent) {
self.virusVisuals[i].parent.removeChild(self.virusVisuals[i]);
}
}
self.virusVisuals = [];
for (var i = 0; i < self.virusMarkers; i++) {
var vMark = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.12,
scaleY: 0.12
});
vMark.tint = 0x39FF14; // Neon Green
vMark.x = -40 + i * 20;
vMark.y = -80;
var vText = new Text2("V", {
size: 60,
fill: 0x000000,
font: "'Arial Black', 'Impact', sans-serif"
});
vText.anchor.set(0.5, 0.5);
vMark.addChild(vText);
self.addChild(vMark);
self.virusVisuals.push(vMark);
}
};
self.die = function () {
if (self.isInPlay) {
self.virusMarkers = 0;
self.updateVirusMarkers();
// Grateful interceptor
if (self.creatureData.grateful) {
// Grateful bypasses the graveyard, events, and scry penalty
bonusPile.push(self.creatureData);
} else {
// Normal death
graveyard.push(self.creatureData);
scryDeniedNextRound = true; // Deny scry
addEventCardToDeck(self.creatureData.level); // Draw event
if (self.creatureData.unlucky) addEventCardToDeck(self.creatureData.level);
}
// Visual death effect
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('creatureDie').play();
}
};
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{1G_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
// Determine if creature has any special properties
var hasAnySpecial = self.creatureData && (self.creatureData.tough || self.creatureData.squishy || self.creatureData.stinky || self.creatureData.flying || self.creatureData.swimming || self.creatureData.efficient || self.creatureData.whaleFood || self.creatureData.whaleChow || self.creatureData.bully || self.creatureData.seaBound || self.creatureData.id === 'AC03');
// Do NOT add "Special" text - start cascading special text directly below card name
var nextYOffset = -50; // Start position lower to avoid overlapping the creature name
// Add "Tough" text if present
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.toughText.anchor.set(0.5, 0.5);
self.toughText.x = 0;
self.toughText.y = nextYOffset;
self.addChild(self.toughText);
nextYOffset = nextYOffset + 28;
}
// Add "Squishy" text if present
if (self.creatureData && self.creatureData.squishy) {
self.squishyText = new Text2("Squishy", {
size: 24,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.squishyText.anchor.set(0.5, 0.5);
self.squishyText.x = 0;
self.squishyText.y = nextYOffset;
self.addChild(self.squishyText);
nextYOffset = nextYOffset + 40;
}
// Add "Fatty" text if present
if (self.creatureData && self.creatureData.fatty) {
self.fattyText = new Text2("Fatty", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.fattyText.anchor.set(0.5, 0.5);
self.fattyText.x = 0;
self.fattyText.y = nextYOffset;
self.addChild(self.fattyText);
nextYOffset = nextYOffset + 40;
}
// Add "Stinky" text if present
if (self.creatureData && self.creatureData.stinky) {
self.stinkyText = new Text2("Stinky", {
size: 24,
fill: 0x228B22,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.stinkyText.anchor.set(0.5, 0.5);
self.stinkyText.x = 0;
self.stinkyText.y = nextYOffset;
self.addChild(self.stinkyText);
nextYOffset = nextYOffset + 40;
}
// Add "Flying" text if present
if (self.creatureData && self.creatureData.flying) {
self.flyingText = new Text2("Flying", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.flyingText.anchor.set(0.5, 0.5);
self.flyingText.x = 0;
self.flyingText.y = nextYOffset;
self.addChild(self.flyingText);
nextYOffset = nextYOffset + 40;
}
// Add "Swimming" text if present
if (self.creatureData && self.creatureData.swimming) {
self.swimmingText = new Text2("Swimming", {
size: 24,
fill: 0x1E90FF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.swimmingText.anchor.set(0.5, 0.5);
self.swimmingText.x = 0;
self.swimmingText.y = nextYOffset;
self.addChild(self.swimmingText);
nextYOffset = nextYOffset + 40;
}
// Add "Efficient" text if present
if (self.creatureData && self.creatureData.efficient) {
self.efficientText = new Text2("Efficient", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.efficientText.anchor.set(0.5, 0.5);
self.efficientText.x = 0;
self.efficientText.y = nextYOffset;
self.addChild(self.efficientText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow" text if present
if (self.creatureData && self.creatureData.whaleChow) {
self.whaleChowText = new Text2("Whale Chow", {
size: 24,
fill: 0x87CEEB,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowText.anchor.set(0.5, 0.5);
self.whaleChowText.x = 0;
self.whaleChowText.y = nextYOffset;
self.addChild(self.whaleChowText);
nextYOffset = nextYOffset + 40;
}
// Add "Bully" text if present (but only for AC02, not BH07 or BH13)
if (self.creatureData && self.creatureData.bully && self.creatureData.id === 'AC02') {
self.bullyText = new Text2("Bully", {
size: 24,
fill: 0xFF6347,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold" //{2U_bully4}
}); //{2U_bully5}
self.bullyText.anchor.set(0.5, 0.5);
self.bullyText.x = 0;
self.bullyText.y = nextYOffset;
self.addChild(self.bullyText);
nextYOffset = nextYOffset + 40;
} //{2U_bully6}
// Add "Hunter-Hunter" text if present (for BC04 - Tuna)
if (self.creatureData && self.creatureData.hunterHunter) {
self.hunterHunterText = new Text2("Hunter-Hunter", {
size: 24,
fill: 0x00FFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.hunterHunterText.anchor.set(0.5, 0.5);
self.hunterHunterText.x = 0;
self.hunterHunterText.y = nextYOffset;
self.addChild(self.hunterHunterText);
nextYOffset = nextYOffset + 40;
}
// Add "Sea Bound" text for AC02
if (self.creatureData && self.creatureData.id === 'AC02') {
self.seaBoundText = new Text2("Sea Bound", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.seaBoundText.anchor.set(0.5, 0.5);
self.seaBoundText.x = 0;
self.seaBoundText.y = nextYOffset;
self.addChild(self.seaBoundText);
nextYOffset = nextYOffset + 40;
}
// Add "Whale Chow Eater" text if present (for AC03 only)
if (self.creatureData && self.creatureData.id === 'AC03') {
self.whaleChowEaterText = new Text2("Whale Chow Eater", {
size: 24,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.whaleChowEaterText.anchor.set(0.5, 0.5);
self.whaleChowEaterText.x = 0;
self.whaleChowEaterText.y = nextYOffset;
self.addChild(self.whaleChowEaterText);
nextYOffset = nextYOffset + 40;
}
// Add "Dominant DNA" text if present
if (self.creatureData && self.creatureData.dominantDNA) {
self.dominantDNAText = new Text2("Dominant DNA", {
size: 24,
fill: 0x9932CC,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.dominantDNAText.anchor.set(0.5, 0.5);
self.dominantDNAText.x = 0;
self.dominantDNAText.y = nextYOffset;
self.addChild(self.dominantDNAText);
nextYOffset = nextYOffset + 40;
}
if (self.creatureData && self.creatureData.nutritious) {
self.nutritiousText = new Text2("Nutritious", {
size: 24,
fill: 0x32CD32,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.nutritiousText.anchor.set(0.5, 0.5);
self.nutritiousText.x = 0;
self.nutritiousText.y = nextYOffset;
self.addChild(self.nutritiousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.bountiful) {
self.bountifulText = new Text2("Bountiful", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.bountifulText.anchor.set(0.5, 0.5);
self.bountifulText.x = 0;
self.bountifulText.y = nextYOffset;
self.addChild(self.bountifulText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.poisonous) {
self.poisonousText = new Text2("Poisonous", {
size: 24,
fill: 0x8A2BE2,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.poisonousText.anchor.set(0.5, 0.5);
self.poisonousText.x = 0;
self.poisonousText.y = nextYOffset;
self.addChild(self.poisonousText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.delicate) {
self.delicateText = new Text2("Delicate", {
size: 24,
fill: 0xFFC0CB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.delicateText.anchor.set(0.5, 0.5);
self.delicateText.x = 0;
self.delicateText.y = nextYOffset;
self.addChild(self.delicateText);
nextYOffset += 40;
}
if (self.creatureData && self.creatureData.forerunner) {
self.forerunnerText = new Text2("Forerunner", {
size: 24,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.forerunnerText.anchor.set(0.5, 0.5);
self.forerunnerText.x = 0;
self.forerunnerText.y = nextYOffset;
self.addChild(self.forerunnerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shy) {
self.shyText = new Text2("Shy", {
size: 24,
//{3F_shy}
fill: 0xADD8E6,
//{3G_shy}
font: "'Arial Black', 'Impact', sans-serif",
//{3H_shy}
fontWeight: "bold" //{3I_shy}
}); //{3J_shy}
self.shyText.anchor.set(0.5, 0.5);
self.shyText.x = 0;
self.shyText.y = nextYOffset;
self.addChild(self.shyText);
nextYOffset += 28; //{3K_shy}
} //{3L_shy}
if (self.creatureData && self.creatureData.fussy) {
self.fussyText = new Text2("Fussy", {
size: 24,
fill: 0x800080,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.fussyText.anchor.set(0.5, 0.5);
self.fussyText.x = 0;
self.fussyText.y = nextYOffset;
self.addChild(self.fussyText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.unlucky) {
self.unluckyText = new Text2("Unlucky", {
size: 24,
//{3M_unlucky}
fill: 0x8B0000,
//{3N_unlucky}
font: "'Arial Black', 'Impact', sans-serif",
//{3O_unlucky}
fontWeight: "bold" //{3P_unlucky}
}); //{3Q_unlucky}
self.unluckyText.anchor.set(0.5, 0.5);
self.unluckyText.x = 0;
self.unluckyText.y = nextYOffset;
self.addChild(self.unluckyText);
nextYOffset += 28; //{3R_unlucky}
} //{3S_unlucky}
if (self.creatureData && self.creatureData.watcher) {
self.watcherText = new Text2("Watcher", {
size: 24,
fill: 0x9370DB,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.watcherText.anchor.set(0.5, 0.5);
self.watcherText.x = 0;
self.watcherText.y = nextYOffset;
self.addChild(self.watcherText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.slow) {
self.slowText = new Text2("Slow", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.slowText.anchor.set(0.5, 0.5);
self.slowText.x = 0;
self.slowText.y = nextYOffset;
self.addChild(self.slowText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.terraformer) {
self.terraformerText = new Text2("Terra-former", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.terraformerText.anchor.set(0.5, 0.5);
self.terraformerText.x = 0;
self.terraformerText.y = nextYOffset;
self.addChild(self.terraformerText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.grateful) {
self.gratefulText = new Text2("Grateful", {
size: 20,
fill: 0xFF69B4,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.gratefulText.anchor.set(0.5, 0.5);
self.gratefulText.x = 0;
self.gratefulText.y = nextYOffset;
self.addChild(self.gratefulText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.shell) {
self.shellText = new Text2("Shell", {
size: 24,
fill: 0xD2B48C,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.shellText.anchor.set(0.5, 0.5);
self.shellText.x = 0;
self.shellText.y = nextYOffset;
self.addChild(self.shellText);
nextYOffset += 28;
}
return self;
});
var EventCard = Container.expand(function (eventData) {
var self = Container.call(this);
self.eventData = eventData || {
type: 'basic',
cardType: 'event',
level: 'Basic',
name: 'Event Card',
effect: 'environmental'
};
// Create event card visual
var eventAsset = self.attachAsset('eventCard', {
anchorX: 0.5,
anchorY: 0.5
});
// Add event text - placeholder "EVENT!!!" in red
var eventText = new Text2("EVENT!!!", {
size: 32,
fill: 0xFF0000
});
eventText.anchor.set(0.5, 0.5);
eventText.x = 0;
eventText.y = 0;
self.addChild(eventText);
// Add event level indicator
var levelText = new Text2(self.eventData.level, {
size: 16,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 0;
levelText.y = -80;
self.addChild(levelText);
self.isInPlay = false;
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
if (self.isShellTarget) return; // Handled by Shell interceptor
var handIndex = playerHand.indexOf(self);
if (handIndex !== -1) playerHand.splice(handIndex, 1);
if (self.parent) self.parent.removeChild(self);
// Define cleanup callback so interactive events can pause resolution
var finalizeEvent = function finalizeEvent() {
if (self.eventData.level === 'Advanced') advancedEventDiscard.push(self.eventData);else basicEventDiscard.push(self.eventData);
updateHandPositions();
var moreEvents = false;
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
moreEvents = true;
break;
}
}
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
if (game.eventDebugBox && game.eventDebugBox.parent) {
game.eventDebugBox.parent.removeChild(game.eventDebugBox);
game.eventDebugBox = null;
}
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
}
};
// Execute specific event logic
if (self.eventData.id === 'BE01') {
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) discardPile.push(mainDeck.pop());
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
finalizeEvent();
} else if (self.eventData.id === 'BE02') {
processDevolutionEvent(finalizeEvent);
} else if (self.eventData.id === 'BE03') {
processDiseaseEvent(finalizeEvent);
} else if (self.eventData.id === 'BE04') {
// Do not run finalizeEvent here, because Volcano transforms into terrain and handles its own phase transition
processVolcanoEvent(function () {});
} else if (self.eventData.id === 'BE05') {
processRisingSeasEvent(finalizeEvent);
} else if (self.eventData.id === 'AE01') {
processSmiteEvent(finalizeEvent);
} else if (self.eventData.id === 'AE02') {
processHarshEvolutionEvent(finalizeEvent);
} else if (self.eventData.id === 'AE03') {
processMeteorEvent(finalizeEvent);
} else if (self.eventData.id === 'AE04') {
processVirusEvent(finalizeEvent);
} else {
finalizeEvent(); // Fallback for unimplemented events
}
}
};
return self;
});
var PlanetSlot = Container.expand(function (slotX, slotY) {
var self = Container.call(this);
self.slotX = slotX;
self.slotY = slotY;
self.terrainCard = null;
self.isHighlighted = false;
self.pinkBorder = null;
self.highlight = function () {
if (!self.isHighlighted && self.terrainCard) {
self.isHighlighted = true;
if (!self.pinkBorder) {
self.pinkBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
self.pinkBorder.tint = 0xFFFF00; // Bright Yellow
self.pinkBorder.alpha = 0.6;
self.pinkBorder.scaleX = self.terrainCard.scaleX * 1.15;
self.pinkBorder.scaleY = self.terrainCard.scaleY * 1.15;
self.pinkBorder.x = self.terrainCard.x;
self.pinkBorder.y = self.terrainCard.y;
// Bring to the absolute front of the game instead of hiding behind the terrain
game.addChild(self.pinkBorder);
}
}
};
self.unhighlight = function () {
if (self.isHighlighted) {
self.isHighlighted = false;
// Remove pink border
if (self.pinkBorder && self.pinkBorder.parent) {
self.pinkBorder.parent.removeChild(self.pinkBorder);
self.pinkBorder = null;
}
}
};
self.down = function (x, y, obj) {
if (selectedCard && selectedCard.creatureData && self.terrainCard && canPlaceCreatureOnTerrain(selectedCard, self.terrainCard)) {
placeCreatureOnStack(selectedCard, self.terrainCard, self.slotX, self.slotY);
}
};
return self;
});
var TerrainCard = Container.expand(function (terrainData) {
var self = Container.call(this);
self.terrainData = terrainData || {
type: 'basic',
level: 'Basic',
subtype: 'land',
landType: 'flat',
waterType: null,
climate: 'temperate'
};
// Create terrain card visual based on subtype and landType/waterType
var terrainAsset;
if (self.terrainData.subtype === 'land') {
if (self.terrainData.landType === 'flat') {
terrainAsset = self.attachAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'hills') {
terrainAsset = self.attachAsset('terrainLandHills', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.landType === 'mountain') {
terrainAsset = self.attachAsset('terrainLandMountain', {
anchorX: 0.5,
anchorY: 0.5
});
}
} else if (self.terrainData.subtype === 'water') {
if (self.terrainData.waterType === 'sea') {
terrainAsset = self.attachAsset('terrainWaterSea', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.terrainData.waterType === 'fresh') {
terrainAsset = self.attachAsset('terrainWaterFresh', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add terrain type indicator strip at top
var terrainStripAsset = self.terrainData.level === 'Advanced' ? 'advancedTerrainStrip' : 'basicTerrainStrip';
var terrainStrip = self.attachAsset(terrainStripAsset, {
anchorX: 0.5,
anchorY: 0.5
});
terrainStrip.y = -147;
// For advanced terrain cards, tint the strip based on colorBand
if (self.terrainData.level === 'Advanced' && self.terrainData.colorBand) {
var colorMap = {
'grey': 0xC0C0C0,
'darkblue': 0x87CEEB,
'brown': 0xD2B48C,
'purple': 0x800080,
'lightblue': 0xADD8E6
};
if (colorMap[self.terrainData.colorBand]) {
terrainStrip.tint = colorMap[self.terrainData.colorBand];
}
}
// Add terrain type text (subtype and second subtype in the color band)
var terrainTypeText = '';
var textColor = 0xFFFFFF; // Default white for advanced terrain
if (self.terrainData.level === 'Advanced') {
// For advanced terrain: display subtype and specific terrain type
terrainTypeText = self.terrainData.subtype.toUpperCase() + ' - ';
terrainTypeText += (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
} else {
// For basic terrain: just display the specific terrain type
terrainTypeText = (self.terrainData.landType || self.terrainData.waterType).toUpperCase();
textColor = 0x000000; // Black text for basic terrain
}
self.typeText = new Text2(terrainTypeText, {
size: 24,
fill: textColor,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.typeText.anchor.set(0.5, 0.5);
self.typeText.x = 0;
self.typeText.y = -147;
self.addChild(self.typeText);
// Add climate requirement display for advanced terrain cards (top right under color band with colored circles)
if (self.terrainData.level === 'Advanced' && self.terrainData.climateRequirement && self.terrainData.climateRequirement !== 'any') {
var climateDisplay = self.terrainData.climateRequirement;
var climateArray = [];
if (climateDisplay.indexOf('/') !== -1) {
// Multiple climate requirements - split them
climateArray = climateDisplay.split('/');
} else {
// Single climate requirement
climateArray = [climateDisplay];
}
// Create colored circles with climate letters
var circleSize = 30;
var circleSpacing = 35;
var startX = 100;
var startY = -115;
for (var c = 0; c < climateArray.length; c++) {
var climateType = climateArray[c].trim();
var circleColor = 0xFFFFFF;
var letterChar = '';
if (climateType.indexOf('hot') !== -1) {
circleColor = 0xFF4444;
letterChar = 'H';
} else if (climateType.indexOf('cold') !== -1) {
circleColor = 0x4444FF;
letterChar = 'C';
} else if (climateType.indexOf('temperate') !== -1) {
circleColor = 0xFFFF44;
letterChar = 'T';
}
// Create circle background
var climateCircle = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08
});
climateCircle.tint = circleColor;
climateCircle.x = startX + c * circleSpacing;
climateCircle.y = startY;
self.addChild(climateCircle);
// Add letter text on circle
var climateLetterText = new Text2(letterChar, {
size: 16,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLetterText.anchor.set(0.5, 0.5);
climateLetterText.x = startX + c * circleSpacing;
climateLetterText.y = startY;
self.addChild(climateLetterText);
}
}
// Add "Special" text in the center for any card with special: true
if (self.terrainData && self.terrainData.special && !self.terrainData.simplify) {
self.specialText = new Text2("Special", {
size: 32,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif",
fontWeight: "bold"
});
self.specialText.anchor.set(0.5, 0.5);
self.specialText.x = 0;
self.specialText.y = 0;
self.addChild(self.specialText);
}
var nextTerrainYOffset = 40;
if (self.terrainData && self.terrainData.simplify) {
var simplifyText = new Text2("Simplify", {
size: 36,
fill: 0x00BFFF,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
simplifyText.anchor.set(0.5, 0.5);
simplifyText.x = 0;
simplifyText.y = nextTerrainYOffset;
self.addChild(simplifyText);
nextTerrainYOffset += 40;
}
if (self.terrainData && self.terrainData.displacer) {
var displacerText = new Text2("Displacer", {
size: 24,
fill: 0xFF8C00,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
displacerText.anchor.set(0.5, 0.5);
displacerText.x = 0;
displacerText.y = nextTerrainYOffset;
self.addChild(displacerText);
nextTerrainYOffset += 40;
}
// Add card ID at center of bottom for advanced terrain cards
if (self.terrainData.level === 'Advanced' && self.terrainData.id) {
self.cardIdText = new Text2(self.terrainData.id, {
size: 20,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
self.cardIdText.anchor.set(0.5, 0.5);
self.cardIdText.x = 0;
self.cardIdText.y = 140; // Bottom center of card
self.addChild(self.cardIdText);
}
// Climate will be displayed as row background instead of on individual cards
self.gridX = -1;
self.gridY = -1;
self.creatureStack = [];
self.move = function (x, y, obj) {
var globalX = self.x + x * self.scaleX;
var globalY = self.y + y * self.scaleY;
onMouseMove(globalX, globalY);
};
self.down = function (x, y, obj) {
if (!self.isInPlay && playerHand.indexOf(self) !== -1) {
// Prevent selecting creature/terrain cards if event cards are in hand
if (hasEventCardsInHand) {
//{3H_event}
return;
}
selectedCard = self;
draggedCard = self;
originalCardPosition = {
x: self.x,
y: self.y
};
showPossibleMoves(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a252f
});
/****
* Game Code
****/
// UI elements
// Event card asset
// Creature card assets
// Climate indicator strips
// Terrain card assets - Water types with blue/grey strip
// Terrain card assets - Land types with green strip
// Zoom container for magnified card view
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
var zoomedCard = null;
var currentHoveredCardForZoom = null;
var cardBoundsForZoom = 80; // Half card width for zoom trigger
var cardHeightBoundsForZoom = 110; // Half card height for zoom trigger
// Global mouse listener for tracking cursor position
var currentMouseX = 0;
var currentMouseY = 0;
function onMouseMove(x, y) {
var cardUnderMouse = null;
// Check hand (reverse order so top cards are checked first)
for (var i = playerHand.length - 1; i >= 0; i--) {
var card = playerHand[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
// Check Scry Cards
if (!cardUnderMouse && typeof scryCards !== 'undefined' && scryCards.length > 0) {
for (var i = scryCards.length - 1; i >= 0; i--) {
var card = scryCards[i];
var hw = (card.terrainData ? 126 : 80) * card.scaleX;
var hh = (card.terrainData ? 168 : 110) * card.scaleY;
if (Math.abs(card.x - x) < hw && Math.abs(card.y - y) < hh) {
cardUnderMouse = card;
break;
}
}
}
// Check AT18 Special Selection Cards
if (!cardUnderMouse && typeof at18Active !== 'undefined' && at18Active) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.at18Selectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Forerunner Special Selection Cards
if (!cardUnderMouse && typeof forerunnerActive !== 'undefined' && forerunnerActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.forerunnerSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check Watcher Special Selection Cards
if (!cardUnderMouse && typeof watcherActive !== 'undefined' && watcherActive) {
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.watcherSelectable) {
var hw = (child.terrainData ? 126 : 80) * child.scaleX;
var hh = (child.terrainData ? 168 : 110) * child.scaleY;
if (Math.abs(child.x - x) < hw && Math.abs(child.y - y) < hh) {
cardUnderMouse = child;
break;
}
}
}
}
// Check board
if (!cardUnderMouse) {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
// 1. Check TOP creature
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
var topIndex = terrain.creatureStack.length - 1;
var topCreature = terrain.creatureStack[topIndex];
var cHW = 80 * topCreature.scaleX;
var cHH = 110 * topCreature.scaleY;
if (Math.abs(topCreature.x - x) < cHW && Math.abs(topCreature.y - y) < cHH) {
cardUnderMouse = topCreature;
}
// 2. Check LOWER creatures
if (!cardUnderMouse && topIndex > 0) {
for (var c = topIndex - 1; c >= 0; c--) {
var lowerC = terrain.creatureStack[c];
var lHW = 80 * lowerC.scaleX;
var lHH = 110 * lowerC.scaleY;
var cardBottom = lowerC.y + lHH;
if (Math.abs(lowerC.x - x) < lHW && y <= cardBottom && y >= cardBottom - 45 * lowerC.scaleY) {
cardUnderMouse = lowerC;
break;
}
}
}
}
// 3. Check Terrain
if (!cardUnderMouse) {
var tHW = 126 * terrain.scaleX;
var tHH = 168 * terrain.scaleY;
if (Math.abs(terrain.x - x) < tHW && Math.abs(terrain.y - y) < tHH) {
cardUnderMouse = terrain;
}
}
}
if (cardUnderMouse) break;
}
if (cardUnderMouse) break;
}
}
// Process state changes
if (cardUnderMouse && cardUnderMouse !== currentlyHoveredCard) {
if (currentlyHoveredCard) clearZoom();
zoomCard(cardUnderMouse);
showCardInfo(cardUnderMouse);
if (!cardUnderMouse.isInPlay) {
showPossibleMoves(cardUnderMouse);
}
currentlyHoveredCard = cardUnderMouse;
} else if (!cardUnderMouse && currentlyHoveredCard) {
clearZoom();
hideCardInfo();
hidePossibleMoves();
currentlyHoveredCard = null;
}
}
var currentlyHoveredCard = null;
game.move = function (x, y, obj) {
currentMouseX = x;
currentMouseY = y;
// Call centralized mouse move handler for zoom
onMouseMove(x, y);
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
// Game state variables
var currentRound = 0;
var maxRounds = 5;
var cardsDrawnThisTurn = 0;
var maxCardsPerTurn = 3;
var planetBoard = [];
var planetSlots = [];
var playerHand = [];
var maxHandSize = 3;
var mainDeck = [];
var basicTerrainPool = [];
var selectedCard = null;
var draggedCard = null;
var originalCardPosition = null;
var scryTokenActive = false; // Start without scry token
var scryCards = []; // Cards in scry mode
var scryMode = false; // Whether scry is active
var scryStep = 0; // Track which step of scry (1 = select for bottom, 2 = select to discard)
var scryHeaderText = null; // Header text for scry instructions
var creaturesWentExtinct = false; // Track if creatures went extinct this round
var cardInfoText = null;
var hoveredCard = null;
var drawPhase = 'initial'; // 'initial', 'forced'
var cardsDrawnInPhase = [];
var discardPile = [];
var basicEventDiscard = [];
var advancedEventDiscard = [];
var linkLines = []; // Visual link lines between carnivores and herbivores
var gamePhase = 'dawn'; // Track current game phase: 'dawn', 'draw', 'noon', 'dusk', 'night', 'end'
var turnNumber = 1;
var draggedLinkMarker = null;
var draggedLinkCarnivore = null;
var linkHighlights = [];
var eventCardsToPlay = [];
var mustPlayCard = null;
var basicEventDeck = [];
var advancedEventDeck = [];
var phaseTransitionTimer = null;
var scryDeniedNextRound = false;
var hasEventCardsInHand = false;
// Planet layout: 2-4-6-4-2 formation
var planetLayout = [2, 4, 6, 4, 2];
var planetWidth = 6; // Max cards in any row
var planetHeight = 5; // Number of rows
// UI Elements
var deckCountText = new Text2("Deck: " + mainDeck.length, {
size: 36,
fill: 0xFFFFFF
});
deckCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(deckCountText);
deckCountText.x = 120;
deckCountText.y = 150;
var discardCountText = new Text2("Discard: 0", {
size: 36,
fill: 0xFFFFFF
});
discardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(discardCountText);
discardCountText.x = 120;
discardCountText.y = 190;
var basicEventCountText = new Text2("Basic Events: 10", {
size: 28,
fill: 0x00FF00
});
basicEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(basicEventCountText);
basicEventCountText.x = 120;
basicEventCountText.y = 230;
var advancedEventCountText = new Text2("Advanced Events: 10", {
size: 28,
fill: 0xFF6600
});
advancedEventCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(advancedEventCountText);
advancedEventCountText.x = 120;
advancedEventCountText.y = 260;
var graveyardCountText = new Text2("Graveyard: 0", {
size: 28,
//{4f_graveyard}
fill: 0xFFFFFF
}); //{4g_graveyard}
graveyardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(graveyardCountText);
graveyardCountText.x = 120;
graveyardCountText.y = 290;
var eventDiscardCountText = new Text2("Event Discards: B:0 | A:0", {
size: 28,
fill: 0x9932CC
});
eventDiscardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(eventDiscardCountText);
eventDiscardCountText.x = 120;
eventDiscardCountText.y = 320;
var graveyard = [];
var bonusPile = [];
var at15PlayedLastTurn = false; // Track if AT15 was played to enforce draw 4 next turn
var at18Active = false; // Track if AT18 special is being processed
var at18CarnivoreList = []; // Carnivores available for AT18 selection
var scryText = new Text2("Scry", {
size: 32,
fill: 0xFFFFFF
});
scryText.anchor.set(0, 0);
scryText.tint = 0x888888; // Start gray/inactive
LK.gui.topRight.addChild(scryText);
scryText.x = -300;
scryText.y = 190;
// Add scry button functionality
scryText.down = function () {
if (scryTokenActive && !scryMode && mainDeck.length >= 3) {
activateScry();
}
};
// Initialize basic terrain pool (23 cards total)
// BT01-05: Land - Flatland
// BT06-10: Land - Hills
// BT11-14: Land - Mountains
// BT15-20: Water - Sea
// BT21-23: Water - Fresh Water
function createBasicTerrainPool() {
basicTerrainPool = [];
// BT01-05: Land - Flatland (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 1).toString().padStart(2, '0'),
subtype: 'land',
landType: 'flat',
climate: null // Will be assigned during setup
});
}
// BT06-10: Land - Hills (5 cards)
for (var i = 0; i < 5; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 6).toString().padStart(2, '0'),
subtype: 'land',
landType: 'hills',
climate: null // Will be assigned during setup
});
}
// BT11-14: Land - Mountains (4 cards)
for (var i = 0; i < 4; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 11).toString().padStart(2, '0'),
subtype: 'land',
landType: 'mountain',
climate: null // Will be assigned during setup
});
}
// BT15-20: Water - Sea (6 cards)
for (var i = 0; i < 6; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 15).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'sea',
climate: null // Will be assigned during setup
});
}
// BT21-23: Water - Fresh Water (3 cards)
for (var i = 0; i < 3; i++) {
basicTerrainPool.push({
type: 'basic',
level: 'Basic',
id: 'BT' + (i + 21).toString().padStart(2, '0'),
subtype: 'water',
waterType: 'fresh',
climate: null // Will be assigned during setup
});
}
// Shuffle terrain pool
for (var i = basicTerrainPool.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = basicTerrainPool[i];
basicTerrainPool[i] = basicTerrainPool[j];
basicTerrainPool[j] = temp;
}
}
// Setup planet board with terrain cards
function setupPlanet() {
// Create 2D array for planet board
for (var y = 0; y < planetHeight; y++) {
planetBoard[y] = [];
planetSlots[y] = [];
for (var x = 0; x < planetWidth; x++) {
planetBoard[y][x] = null;
planetSlots[y][x] = null;
}
}
// Place terrain cards according to 2-4-6-4-2 layout
var terrainIndex = 0;
var startY = 400;
var cardSpacing = 320;
// Add climate row backgrounds first
for (var row = 0; row < planetLayout.length; row++) {
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
// Create climate row background
var climateRowAsset = 'climateRow' + climate.charAt(0).toUpperCase() + climate.slice(1);
var climateRowBg = LK.getAsset(climateRowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
climateRowBg.x = 1024; // Center of screen
climateRowBg.y = startY + row * 420;
climateRowBg.alpha = 0.3; // Semi-transparent background
game.addChild(climateRowBg);
// Add climate label text
var climateLabel = new Text2(climate.toUpperCase(), {
size: 32,
fill: 0x000000,
font: "'Arial Black', 'Impact', 'Tahoma', sans-serif"
});
climateLabel.anchor.set(0.5, 0.5);
climateLabel.rotation = -Math.PI / 2; // Rotate 90 degrees counter-clockwise to make vertical
climateLabel.x = 50; // Move left to make fully visible
climateLabel.y = startY + row * 420;
game.addChild(climateLabel);
}
for (var row = 0; row < planetLayout.length; row++) {
var cardsInRow = planetLayout[row];
var startX = 1024 - cardsInRow * cardSpacing / 2 + cardSpacing / 2 + 50; // Shift cards right by 50px
// Assign climate based on row
var climate;
if (row === 0 || row === 4) {
climate = 'cold'; // Rows 1 & 5
} else if (row === 1 || row === 3) {
climate = 'temperate'; // Rows 2 & 4
} else {
climate = 'hot'; // Row 3
}
for (var col = 0; col < cardsInRow && terrainIndex < basicTerrainPool.length; col++) {
// Assign climate to terrain card
var terrainData = basicTerrainPool[terrainIndex];
terrainData.climate = climate;
// Create terrain card
var terrainCard = new TerrainCard(terrainData);
terrainCard.x = startX + col * cardSpacing;
terrainCard.y = startY + row * 420;
terrainCard.gridX = col;
terrainCard.gridY = row;
game.addChild(terrainCard);
// Store in board
var boardX = Math.floor((planetWidth - cardsInRow) / 2) + col;
planetBoard[row][boardX] = terrainCard;
// Create slot for this position
var slot = new PlanetSlot(boardX, row);
slot.terrainCard = terrainCard;
planetSlots[row][boardX] = slot;
terrainIndex++;
}
}
}
// Create main deck with advanced terrain cards and creature cards
function createInitialDeck() {
mainDeck = [];
// Add 20 Advanced Terrain Cards (AT01-AT20)
// AT01-02: Fresh Water - Grey band, no climate requirements
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT01',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'fresh',
id: 'AT02',
colorBand: 'grey',
climateRequirement: 'any',
name: 'Advanced Fresh Water'
});
// AT03-07: Sea Water - Dark Blue band
// AT03: Hot, AT04: Cold, AT05: Temperate, AT06: Hot or Temperate, AT07: Special
var seaCardConfigs = [{
id: 'AT03',
climate: 'hot',
special: false
}, {
id: 'AT04',
climate: 'cold',
special: false
}, {
id: 'AT05',
climate: 'temperate',
special: false
}, {
id: 'AT06',
climate: 'hot/temperate',
special: false
}, {
id: 'AT07',
climate: 'any',
special: true
}];
for (var i = 0; i < seaCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'sea',
id: seaCardConfigs[i].id,
colorBand: 'darkblue',
climateRequirement: seaCardConfigs[i].climate,
special: seaCardConfigs[i].special,
displacer: seaCardConfigs[i].id === 'AT07' ? true : false,
name: 'Advanced Sea Water'
});
}
// AT08-12: Flat Land - Brown band
// AT08: Hot, AT09: Temperate, AT10: Cold, AT11: Temperate or Cold, AT12: Temperate or Hot + Special
var flatCardConfigs = [{
id: 'AT08',
climate: 'hot',
special: false
}, {
id: 'AT09',
climate: 'temperate',
special: false
}, {
id: 'AT10',
climate: 'cold',
special: false
}, {
id: 'AT11',
climate: 'temperate/cold',
special: false
}, {
id: 'AT12',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < flatCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'flat',
id: flatCardConfigs[i].id,
colorBand: 'brown',
climateRequirement: flatCardConfigs[i].climate,
special: flatCardConfigs[i].special,
simplify: flatCardConfigs[i].id === 'AT12' ? true : false,
name: 'Advanced Flat Land'
});
}
// AT13-17: Hills - Purple band
// AT13: Temperate, AT14: Cold, AT15: Hot + Special, AT16: Temperate or Cold, AT17: Temperate or Hot
var hillsCardConfigs = [{
id: 'AT13',
climate: 'temperate',
special: false
}, {
id: 'AT14',
climate: 'cold',
special: false
}, {
id: 'AT15',
climate: 'hot',
special: true
}, {
id: 'AT16',
climate: 'temperate/cold',
special: false
}, {
id: 'AT17',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < hillsCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'hills',
id: hillsCardConfigs[i].id,
colorBand: 'purple',
climateRequirement: hillsCardConfigs[i].climate,
special: hillsCardConfigs[i].special,
name: 'Advanced Hills'
});
}
// AT18-20: Mountains - Light Blue band
// AT18: No climate requirements + Special, AT19: Temperate or Cold, AT20: Temperate or Hot
var mountainCardConfigs = [{
id: 'AT18',
climate: 'any',
special: true
}, {
id: 'AT19',
climate: 'temperate/cold',
special: false
}, {
id: 'AT20',
climate: 'temperate/hot',
special: false
}];
for (var i = 0; i < mountainCardConfigs.length; i++) {
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'mountain',
id: mountainCardConfigs[i].id,
colorBand: 'lightblue',
climateRequirement: mountainCardConfigs[i].climate,
special: mountainCardConfigs[i].special,
name: 'Advanced Mountains'
});
}
// Add 33 Basic Herbivore Cards (BH01-BH33)
var basicHerbivoreConfigs = [{
id: 'BH01',
name: 'Pond Turtle',
waterType: 'fresh',
colorBand: 'grey',
//{bh01_terrain}
climate: 'temperate',
//{bh01_climate}
special: false //{bh01_special}
}, {
id: 'BH02',
name: 'Crayfish',
waterType: 'fresh',
colorBand: 'grey',
climate: 'cold',
special: false
}, {
id: 'BH03',
name: 'Newt',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/cold',
special: true
}, {
id: 'BH04',
name: 'Toad',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true
}, {
id: 'BH05',
name: 'Mud Skipper',
waterType: 'fresh',
colorBand: 'grey',
climate: 'temperate/hot',
special: true,
grateful: true
}, {
id: 'BH06',
name: 'Tropical Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH07',
name: 'Plankton',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH08',
name: 'Limpet',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: false
}, {
id: 'BH09',
name: 'Red Crab',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'hot',
special: false
}, {
id: 'BH10',
name: 'Jelly Fish',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'cold',
special: true
}, {
id: 'BH11',
name: 'Mussel',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate',
special: true
}, {
id: 'BH12',
name: 'Whelk',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BH13',
name: 'Krill',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH14',
name: 'Hare',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: false
}, {
id: 'BH15',
name: 'Meerkat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH16',
name: 'Pheasant',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate',
special: true
}, {
id: 'BH17',
name: 'Tortoise',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: false
}, {
id: 'BH18',
name: 'Goose',
landType: 'flat',
colorBand: 'brown',
climate: 'cold',
special: true
}, {
id: 'BH19',
name: 'Pig',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: false
}, {
id: 'BH20',
name: 'Platypus',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/cold',
special: true
}, {
id: 'BH21',
name: 'Rabbit',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH22',
name: 'Chicken',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate',
special: false
}, {
id: 'BH23',
name: 'Ice Squirrel',
landType: 'hills',
colorBand: 'purple',
climate: 'cold',
special: false
}, {
id: 'BH24',
name: 'Pea Fowl',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH25',
name: 'Mole-Rat',
landType: 'hills',
colorBand: 'purple',
climate: 'hot',
special: false
}, {
id: 'BH26',
name: 'Guinnea pig',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/cold',
special: true
}, {
id: 'BH27',
name: 'Wombat',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'BH28',
name: 'Marmoset',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH29',
name: 'Porcupine',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate',
special: false
}, {
id: 'BH30',
name: 'Skunk',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'hot',
special: true
}, {
id: 'BH31',
name: 'Shaggy Sheep',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'cold',
special: true
}, {
id: 'BH32',
name: 'Worms',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/hot',
special: true
}, {
id: 'BH33',
name: 'Giant Snail',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true,
shell: true
}];
for (var i = 0; i < basicHerbivoreConfigs.length; i++) {
var config = basicHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BH18' || config.id === 'BH16' ? true : false,
swimming: config.id === 'BH02' || config.id === 'BH07' || config.id === 'BH10' || config.id === 'BH13' || config.id === 'BH20' ? true : false,
tough: config.id === 'BH31' ? true : false,
squishy: config.id === 'BH03' || config.id === 'BH27' ? true : false,
stinky: config.id === 'BH04' || config.id === 'BH30' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)
whaleFood: config.id === 'BH07' || config.id === 'BH13' ? true : false,
whaleChow: config.id === 'BH07' || config.id === 'BH13' ? true : false,
nutritious: config.id === 'BH11' ? true : false,
poisonous: config.id === 'BH20' ? true : false,
delicate: config.id === 'BH26' ? true : false,
terraformer: config.id === 'BH32' ? true : false,
grateful: config.id === 'BH05' ? true : false
});
}
// Add 8 Advanced Carnivore Cards (AC01-AC08)
var advancedCarnivoreConfigs = [{
id: 'AC01',
name: 'Pike',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'AC02',
name: 'Great White Shark',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true,
bully: true,
seaBound: true //{c4_bully}
}, {
id: 'AC03',
name: 'Whale',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'any',
special: true
}, {
id: 'AC04',
name: 'Snakes!',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC05',
name: 'Bear',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'AC06',
name: 'Buzzard',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'temperate/cold',
special: true
}, {
id: 'AC07',
name: 'Tiger',
landType: 'hills',
colorBand: 'purple',
climate: 'temperate/hot',
special: true
}, {
id: 'AC08',
name: 'Leopard',
landType: 'flat',
colorBand: 'brown',
climate: 'temperate/hot',
special: true
}];
for (var i = 0; i < advancedCarnivoreConfigs.length; i++) {
var config = advancedCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'AC06' ? true : false,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
isWhale: config.id === 'AC03' ? true : false,
fatty: config.id === 'AC03' ? true : false,
seaBound: config.id === 'AC02' ? true : false,
bully: config.id === 'AC02' ? true : false,
//{c9_bully}
dominantDNA: config.id === 'AC01' ? true : false,
shy: config.id === 'AC05' ? true : false,
unlucky: config.id === 'AC07' ? true : false
});
}
// Add 10 Basic Carnivore Cards (BC01-BC10)
var basicCarnivoreConfigs = [{
id: 'BC01',
name: 'King Fisher',
waterType: 'fresh',
colorBand: 'grey',
climate: 'any',
special: true
}, {
id: 'BC02',
name: 'Penguin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: false
}, {
id: 'BC03',
name: 'Puffin',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/cold',
special: true,
fussy: true //{d7_fussy}
}, {
id: 'BC04',
name: 'Tuna',
waterType: 'sea',
colorBand: 'darkblue',
climate: 'temperate/hot',
special: true,
hunterHunter: true //{dc_hh}
}, {
id: 'BC05',
name: 'Eagle',
landType: 'mountain',
colorBand: 'lightblue',
climate: 'any',
special: true
}, {
id: 'BC06',
name: 'Chameleon',
landType: 'any',
colorBand: 'brown',
climate: 'any',
special: true
}, {
id: 'BC07',
name: 'Bat',
landType: 'flat',
colorBand: 'brown',
climate: 'hot',
special: true
}, {
//{9D_bc08}
id: 'BC08',
name: 'Albatross',
landType: 'hills',
colorBand: 'purple',
//{9E_bc08}
climate: 'cold',
//{9F_bc08}
special: true //{9G_bc08}
}, {
//{9D_bc09}
id: 'BC09',
name: 'Pendo',
landType: 'flat',
colorBand: 'brown',
//{9E_bc09}
climate: 'temperate/cold',
//{9F_bc09}
special: false //{9G_bc09}
}, {
//{9D_bc10}
id: 'BC10',
name: 'Stoat',
landType: 'hills',
colorBand: 'purple',
//{9E_bc10}
climate: 'hot/temperate',
//{9F_bc10}
special: false //{9G_bc10}
}];
for (var i = 0; i < basicCarnivoreConfigs.length; i++) {
var config = basicCarnivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'basic',
level: 'Basic',
cardType: 'creature',
dietType: 'carnivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
flying: config.id === 'BC01' || config.id === 'BC03' || config.id === 'BC05' || config.id === 'BC07' || config.id === 'BC08' || config.id === 'BC09' ? true : false,
swimming: config.id === 'BC04' ? true : false,
efficient: config.id === 'BC03' ? true : false,
// Efficient for BC03 (Puffin)//{dU_comment}
hunterHunter: config.id === 'BC04' ? true : false,
// Hunter-Hunter for BC04 (Tuna)
fussy: config.id === 'BC03' ? true : false,
//{dU_fussy}
// Fussy for BC03 (Puffin)
delicate: config.id === 'BC01' ? true : false,
forerunner: config.id === 'BC07' ? true : false,
watcher: config.id === 'BC06' ? true : false,
slow: config.id === 'BC06' ? true : false
});
}
// Add 12 Advanced Herbivore Cards (AH01-AH12)
var advancedHerbivoreConfigs = [{
id: 'AH01',
name: 'Salmon',
waterType: 'fresh',
colorBand: 'grey',
//{ah01_terrain}
climate: 'any',
//{ah01_climate}
special: true //{ah01_special}
}, {
id: 'AH02',
name: 'Lobster',
waterType: 'sea',
colorBand: 'darkblue',
//{ah02_terrain}
climate: 'temperate',
//{ah02_climate}
special: false //{ah02_special}
}, {
id: 'AH03',
name: 'Herring',
waterType: 'sea',
colorBand: 'darkblue',
//{ah03_terrain}
climate: 'temperate/cold',
//{ah03_climate}
special: true //{ah03_special}
}, {
id: 'AH04',
name: 'Maceral',
waterType: 'sea',
colorBand: 'darkblue',
//{ah04_terrain}
climate: 'temperate/hot',
//{ah04_climate}
special: true //{ah04_special}
}, {
id: 'AH05',
name: 'Wildebeest',
landType: 'flat',
colorBand: 'brown',
//{ah05_terrain}
climate: 'hot',
//{ah05_climate}
special: false //{ah05_special}
}, {
id: 'AH06',
name: 'Moose',
landType: 'flat',
colorBand: 'brown',
//{ah06_terrain}
climate: 'cold',
//{ah06_climate}
special: false //{ah06_special}
}, {
id: 'AH07',
name: 'Gazelle',
landType: 'flat',
colorBand: 'brown',
//{ah07_terrain}
climate: 'temperate',
//{ah07_climate}
special: false //{ah07_special}
}, {
id: 'AH08',
name: 'Ibex',
landType: 'hills',
colorBand: 'purple',
//{ah08_terrain}
climate: 'hot',
//{ah08_climate}
special: false //{ah08_special}
}, {
id: 'AH09',
name: 'Arctic Hare',
landType: 'hills',
colorBand: 'purple',
//{ah09_terrain}
climate: 'cold',
//{ah09_climate}
special: false //{ah09_special}
}, {
id: 'AH10',
name: 'Elephant',
landType: 'hills',
colorBand: 'purple',
//{ah10_terrain}
climate: 'temperate',
//{ah10_climate}
special: true //{ah10_special}
}, {
id: 'AH11',
name: 'Mountain Goat',
landType: 'mountain',
colorBand: 'lightblue',
//{ah11_terrain}
climate: 'hot',
//{ah11_climate}
special: true //{ah11_special}
}, {
id: 'AH12',
name: 'Llama',
landType: 'mountain',
colorBand: 'lightblue',
//{ah12_terrain}
climate: 'temperate',
//{ah12_climate}
special: true //{ah12_special}
}]; //{ah12_end}
for (var i = 0; i < advancedHerbivoreConfigs.length; i++) {
var config = advancedHerbivoreConfigs[i];
var subtype = config.waterType ? 'water' : 'land';
var waterType = config.waterType || null;
var landType = config.landType || null;
mainDeck.push({
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'herbivore',
name: config.name,
id: config.id,
subtype: subtype,
waterType: waterType,
landType: landType,
colorBand: config.colorBand,
climateRequirement: config.climate,
special: config.special,
swimming: config.id === 'AC01' || config.id === 'AC02' || config.id === 'AC03' || config.id === 'AH01' || config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? true : false,
tough: config.id === 'AH10' ? true : false,
efficient: config.id === 'AC02' || config.id === 'AC04' || config.id === 'AC06' ? true : false,
// Efficient for AC02, AC04, AC06
whaleFood: config.id === 'AH01' ? false : config.id === 'AH02' || config.id === 'AH03' || config.id === 'AH04' ? false : false,
dominantDNA: config.id === 'AH11' ? true : false,
bountiful: config.id === 'AH12' ? true : false
});
}
// Shuffle deck
shuffleDeck();
}
function shuffleDeck() {
for (var i = mainDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = mainDeck[i];
mainDeck[i] = mainDeck[j];
mainDeck[j] = temp;
}
}
function shuffleEventDeck(eventDeck) {
for (var i = eventDeck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = eventDeck[i];
eventDeck[i] = eventDeck[j];
eventDeck[j] = temp;
}
}
function showCardInfo(card) {
hoveredCard = card;
// Remove existing info text if any
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
}
var infoString = "";
if (card.creatureData) {
// Creature card information
var creature = card.creatureData;
if (creature) {
infoString = creature.name + " | Level: " + creature.level + " " + creature.dietType;
if (creature && creature.terrainRequirement && creature.terrainRequirement !== 'any') {
infoString += " | Terrain: " + creature.terrainRequirement;
}
if (creature && creature.climateRequirement && creature.climateRequirement !== 'any') {
infoString += " | Climate: " + creature.climateRequirement;
}
// Add special rules text for Tough
if (creature && creature.tough) {
infoString += " | Tough: Creature requires 3 or more extinction markers before it must become extinct";
}
if (creature && creature.squishy) {
infoString += " | Squishy: Creature requires only 1 or more extinction markers before it must become extinct";
}
if (creature && creature.stinky) {
infoString += " | Stinky: Discard all non-Stinky creatures. Phew!";
}
if (creature && creature.efficient) {
infoString += " | Efficient: This creature can make diagonal links as well as adjacent ones";
}
if (creature && creature.whaleFood) {
infoString += " | Whale chow!: This creature can only be linked to whales";
}
if (creature && creature.id === 'AC03') {
infoString += " | Whale: This creature can link to Krill and Plankton";
}
if (creature && creature.hunterHunter) {
infoString += " | Hunter-Hunter: This creature can only link to Carnivore cards NOT Herbivores!";
} //{fl_hh}
if (creature && creature.fussy) {
infoString += " | Fussy: This creature can only link to Basic Herbivore cards NOT Advanced Herbivores!";
} //{fl_fussy}
if (creature && creature.bully) {
infoString += " | Bully: This creature can create links to Basic Carnivores as well as Basic Herbivores!";
} //{fl_bully}
if (creature && creature.fatty) {
infoString += " | Fatty! Requires 3 links to support it and gives x2 the normal advanced carnivore score at end of game!";
}
if (creature && creature.seaBound) {
infoString += " | Sea-bound: This creature can only create links to other Water creatures!";
}
if (creature && creature.dominantDNA) {
infoString += " | Dominant DNA: This creature can evolve from any creature or devour any creature!";
}
if (creature && creature.nutritious) {
infoString += " | Nutritious: This creature can sustain up to 2 links!";
}
if (creature && creature.bountiful) {
infoString += " | Bountiful: This creature can sustain up to 3 links!";
}
if (creature && creature.poisonous) {
infoString += " | Poisonous! Any carnivore cards feeding upon this creature will get an extinction marker during the night phase due to the toxin.";
}
if (creature && creature.delicate) {
infoString += " | Delicate: This card cannot be placed onto Advanced terrain. If basic terrain beneath it changes to or is an advanced terrain, it gets an unmitigable extinction marker during the night phase.";
}
if (creature && creature.forerunner) {
infoString += " | Forerunner: When you play this card, choose 1 card from the main discard pile and put it on top of the main deck.";
}
if (creature && creature.shy) {
infoString += " | Shy: Can only be played on an empty Advanced terrain card, no need to evolve or devour!";
} //{hJ_shy}
if (creature && creature.unlucky) {
infoString += " | Unlucky: If this card becomes extinct it will generate an extra advanced event!";
} //{hJ_unlucky}
if (creature && creature.watcher) {
infoString += " | Watcher: When played you may return an event card from the main discard pile to its respective event deck!";
}
if (creature && creature.slow) {
infoString += " | Slow: This creature is not quick enough to catch creatures that have flying!";
}
if (creature && creature.terraformer) {
infoString += " | Terra-former: When played, choose an adjacent empty Advanced terrain and devolve it, swapping the Basic terrain beneath. If no Advanced terrains are available, swap an empty Basic terrain instead.";
}
if (creature && creature.grateful) infoString += " | Grateful: If this creature goes extinct it adds +3 points as an end of game score bonus instead of generating an event card!";
if (creature && creature.shell) infoString += " | Shell: When placed, gets a shell token. The next event drawn is negated and returned to the event deck! If covered, the shell is lost.";
}
} else if (card.eventData) {
var evt = card.eventData;
infoString = evt.name + " | Level: " + evt.level + " Event";
if (evt.id === 'BE01') infoString += " | Time fly's: The top 3 cards of the main deck are discarded";
if (evt.id === 'BE02') infoString += " | Devolution: Choose an advanced carnivore, it will be removed from play into the discard pile";
if (evt.id === 'BE03') infoString += " | Disease: Place an extinction marker on a Basic Creature...Twice!";
if (evt.id === 'BE04') infoString += " | Volcano: Place on a mountain. Creatures without flying/swimming must flee to adjacent stacks or die!";
if (evt.id === 'BE05') infoString += " | Rising Seas: This event changes a flat land into an advanced sea terrain card, and all the creatures there that don't swim or fly, must move or die.";
if (evt.id === 'AE01') infoString += " | Smite! Destroys a stack completely! All creatures there are killed, all land there are removed from play, nothing remains!";
if (evt.id === 'AE02') infoString += " | Harsh Evolution! One of your advanced herbivores will be destroyed!";
if (evt.id === 'AE03') infoString += " | Meteor! 1 terrain will be hit by a powerful AOE damage effect spreading extinction markers far and wide!";
if (evt.id === 'AE04') infoString += " | Virus! A creature breaks out in a deadly illness that spreads via links to or from it!";
} else if (card.terrainData) {
// Terrain card information
var terrain = card.terrainData;
if (terrain) {
infoString = terrain.name || terrain.level + " " + (terrain.landType || terrain.waterType);
infoString += " | Level: " + terrain.level + " terrain";
if (terrain.climateRequirement && terrain.climateRequirement !== 'any') {
infoString += " | Climate Required: " + terrain.climateRequirement;
}
// Add special rules text for AT15
if (terrain.id === 'AT15') {
infoString += " | Ancient: Draw 4 cards next turn";
} //{ej_special}
// Add special rules text for AT18
if (terrain.id === 'AT18') {
infoString += " | Hostile: Put 1 Carnivore card from the discard pile onto the top of the play deck if any are present";
} //{ej_special_AT18}
if (terrain.simplify) {
infoString += " | Simplify: When this terrain is placed, if there are any other Advanced terrain cards adjacent (including diagonals) that have no creature cards upon them you must choose 1 of them and devolve it!";
} //{ej_simplify}
if (terrain.displacer) {
infoString += " | Displacer: If played onto a stack with 1 or more creatures, move the top creature into the main deck discard pile!";
} //{ej_displacer}
}
}
// Create info text at bottom of screen
cardInfoText = new Text2(infoString, {
size: 42,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1800
});
cardInfoText.anchor.set(0.5, 1);
cardInfoText.x = 1024; // Center of screen
cardInfoText.y = 2700; // Bottom of screen
game.addChild(cardInfoText);
}
function hideCardInfo() {
if (cardInfoText && cardInfoText.parent) {
cardInfoText.parent.removeChild(cardInfoText);
cardInfoText = null;
}
hoveredCard = null;
}
function zoomCard(card) {
// If a different card was previously zoomed, clear it
if (currentHoveredCardForZoom && currentHoveredCardForZoom !== card) {
clearZoom();
}
// If card is already zoomed, do nothing
if (currentHoveredCardForZoom === card && zoomedCard) {
return;
}
// Clear existing zoomed card
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
zoomedCard = null;
}
// Track current hovered card
currentHoveredCardForZoom = card;
// Create a clone of the card for zooming
var clonedCard;
if (card.creatureData) {
clonedCard = new CreatureCard(card.creatureData);
} else if (card.terrainData) {
clonedCard = new TerrainCard(card.terrainData);
} else if (card.eventData) {
clonedCard = new EventCard(card.eventData);
} else {
return;
}
// Set the cloned card's initial scale to match the card being hovered
clonedCard.scale.set(card.scale.x, card.scale.y);
// Position cloned card
clonedCard.x = 550;
clonedCard.y = 580;
// Apply explicit sizing based on card type
if (card.terrainData) {
clonedCard.scale.set(1.27, 1.31);
} else {
clonedCard.scale.set(2.0, 2.0);
}
clonedCard.alpha = 1;
// Add to zoom container
zoomContainer.addChild(clonedCard);
zoomedCard = clonedCard;
}
function clearZoom() {
if (zoomedCard && zoomedCard.parent) {
zoomedCard.parent.removeChild(zoomedCard);
}
zoomedCard = null;
currentHoveredCardForZoom = null;
}
function clearZoomIfNoCards() {
// Clear zoom when hand is empty (between turns or after card play)
if (playerHand.length === 0 && zoomedCard) {
clearZoom();
currentlyHoveredCard = null;
}
}
function showPossibleMoves(card) {
if (card.creatureData) {
// Highlight valid terrain placement for creatures
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
} else if (card.terrainData) {
// Highlight valid terrain placement for advanced terrain
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var existingTerrain = planetBoard[gridY][gridX];
if (existingTerrain && canPlaceTerrainOnTerrain(card, existingTerrain)) {
planetSlots[gridY][gridX].highlight();
}
}
}
} else if (card.eventData && card.eventData.id === 'BE05') {
var flatTargets = [];
var emptyFlats = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var t = planetBoard[y][x];
if (t && t.terrainData && t.terrainData.landType === 'flat') {
if (t.creatureStack && t.creatureStack.length > 0) flatTargets.push(planetSlots[y][x]);else emptyFlats.push(planetSlots[y][x]);
}
}
}
var finalTargets = flatTargets.length > 0 ? flatTargets : emptyFlats;
for (var i = 0; i < finalTargets.length; i++) finalTargets[i].highlight();
} else if (card.eventData && card.eventData.id === 'AE01') {
// Highlight all occupied planet slots for Smite
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
if (planetBoard[y][x]) planetSlots[y][x].highlight();
}
}
} else if (card.eventData && card.eventData.id === 'AE02') {
// Highlight all Advanced Herbivores
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var t = planetBoard[y][x];
if (t && t.creatureStack) {
for (var i = 0; i < t.creatureStack.length; i++) {
var c = t.creatureStack[i];
if (c.creatureData.dietType === 'herbivore' && c.creatureData.level === 'Advanced') planetSlots[y][x].highlight();
}
}
}
}
} else if (card.eventData && card.eventData.id === 'AE03') {
// Highlight terrains that have exactly 4 orthogonal neighbors
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var t = planetBoard[y][x];
if (t) {
var nbrs = getAdjacentTerrains(x, y, false);
if (nbrs.length === 4) planetSlots[y][x].highlight();
}
}
}
}
}
function hidePossibleMoves() {
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
if (planetSlots[gridY][gridX]) {
planetSlots[gridY][gridX].unhighlight();
}
}
}
}
function hasValidPlacements(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.creatureData) {
// Check creature placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceCreatureOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.terrainData) {
// Check terrain placements
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && canPlaceTerrainOnTerrain(card, terrain)) {
return true;
}
}
}
} else if (card.eventData) {
return true; // Event cards are always valid to play
}
}
return false;
}
function discardCards(cards) {
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
// Remove from hand
var handIndex = playerHand.indexOf(card);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Remove from game
if (card.parent) {
card.parent.removeChild(card);
}
// Extract raw card data and add to discard pile
var cardData = card.creatureData || card.terrainData || card.eventData;
discardPile.push(cardData);
}
updateHandPositions();
// Clear zoom if hand is now empty
clearZoomIfNoCards(); //{ip_new}
}
function drawCard() {
if (mainDeck.length === 0) {
endRound();
return null;
}
// Enforce maximum hand size of 3
if (playerHand.length >= maxHandSize) {
return null; // Hand is full
}
if (drawPhase === 'complete') {
return null; // Draw phase already complete
}
var cardData = mainDeck.pop();
var card;
// Create appropriate card type based on cardType
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
// Scale terrain cards in hand to match creature cards
card.scaleX = 0.7;
card.scaleY = 0.7;
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
if (drawPhase === 'initial') {
// Initial draw phase - draw up to 3 cards
playerHand.push(card);
cardsDrawnInPhase.push(card);
cardsDrawnThisTurn++;
if (cardsDrawnInPhase.length >= 3) {
// Check if any of the 3 cards have valid placements
if (!hasValidPlacements(cardsDrawnInPhase)) {
// Discard all 3 cards and enter forced draw phase
discardCards(cardsDrawnInPhase);
cardsDrawnInPhase = [];
drawPhase = 'forced';
// Continue drawing in forced phase
return drawCard();
} else {
// At least one card has valid placement, end draw phase
drawPhase = 'complete';
}
}
} else if (drawPhase === 'forced') {
// Forced draw phase - draw one card at a time until playable
if (hasValidPlacements([card])) {
// This card can be played, add to hand and must be played
playerHand.push(card);
drawPhase = 'complete';
// Mark this card as must play
card.mustPlay = true;
} else {
// Card cannot be played, discard it and draw another
discardCards([card]);
return drawCard();
}
} else {
return null; // Draw phase complete
}
// Position card in hand
updateHandPositions();
game.addChild(card);
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
LK.getSound('cardDraw').play();
// Check for Stinky effect on newly drawn card
checkAndProcessStinkyEffect(); //{j8_stinky}
return card;
}
function startDrawPhase() {
if (mainDeck.length === 0) {
endRound();
return;
}
drawPhase = 'initial';
cardsDrawnInPhase = [];
cardsDrawnThisTurn = 0;
// Draw initial 3 cards
for (var i = 0; i < 3; i++) {
if (mainDeck.length > 0) {
drawCard();
}
}
// Check for Stinky creatures after drawing initial 3 cards
checkAndProcessStinkyEffect();
}
function checkAndProcessStinkyEffect() {
// Check if any creature in hand has Stinky attribute
var hasStinkyCreature = false;
var stinkyCreature = null;
for (var i = 0; i < playerHand.length; i++) {
//{stinky_check_1}
var card = playerHand[i];
if (card.creatureData && card.creatureData.stinky) {
//{stinky_check_2}
hasStinkyCreature = true;
stinkyCreature = card;
break;
} //{stinky_check_3}
} //{stinky_check_4}
// If Stinky creature found, discard all non-stinky creatures
if (hasStinkyCreature) {
//{stinky_process_1}
var toDiscard = [];
for (var j = 0; j < playerHand.length; j++) {
//{stinky_process_2}
var c = playerHand[j];
// Discard only if it has creatureData AND does NOT have stinky property true
if (c.creatureData && !c.creatureData.stinky) {
//{stinky_process_3}
toDiscard.push(c);
} //{stinky_process_4}
} //{stinky_process_5}
// Execute discard
if (toDiscard.length > 0) {
//{stinky_process_6}
discardCards(toDiscard);
} //{stinky_process_7}
} //{stinky_process_8}
// Check for event cards in hand after stinky processing and set flag
hasEventCardsInHand = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
hasEventCardsInHand = true;
break;
}
}
}
function checkAndShowEventCardDebugBox() {
// Check if any event cards are in hand
var eventCardsInHand = [];
for (var i = 0; i < playerHand.length; i++) {
//{debug_event_1}
if (playerHand[i].eventData) {
//{debug_event_2}
eventCardsInHand.push(playerHand[i]);
} //{debug_event_3}
} //{debug_event_4}
// If event cards found, show debug box
if (eventCardsInHand.length > 0) {
//{debug_event_5}
showEventCardDebugBox();
} //{debug_event_6}
} //{debug_event_7}
function showEventCardDebugBox() {
if (game.activeShellCreature) {
// Shell Interceptor Mode
if (!game.dangerWarningText) {
game.dangerWarningText = new Text2("Shell effect! Select the event you wish to negate. Shell will then be removed.", {
size: 36,
fill: 0x00FF00
});
game.dangerWarningText.anchor.set(0.5, 0.5);
game.dangerWarningText.x = 1024;
game.dangerWarningText.y = 200;
game.addChild(game.dangerWarningText);
}
// Make all events in hand clickable for negation
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (card.eventData) {
card.isShellTarget = true;
card.tint = 0x00FF00;
card.originalDown = card.down;
card.down = function () {
var c = this;
// Remove shell globally
if (game.activeShellCreature && game.activeShellCreature.shellMarker) {
game.activeShellCreature.removeChild(game.activeShellCreature.shellMarker);
game.activeShellCreature.shellMarker = null;
}
game.activeShellCreature = null;
if (game.dangerWarningText) {
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
// Return event to deck instead of resolving
var hIdx = playerHand.indexOf(c);
if (hIdx !== -1) playerHand.splice(hIdx, 1);
if (c.eventData.level === 'Advanced') advancedEventDeck.push(c.eventData);else basicEventDeck.push(c.eventData);
if (c.parent) c.parent.removeChild(c);
// Clean up other events in hand (restore original down functions)
var moreEvents = false;
for (var k = 0; k < playerHand.length; k++) {
if (playerHand[k].eventData) {
playerHand[k].tint = 0xFFFFFF;
playerHand[k].down = playerHand[k].originalDown;
playerHand[k].isShellTarget = false;
moreEvents = true;
}
}
updateHandPositions();
// If no more events, progress phase
if (!moreEvents) {
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
} else {
// If other events remain, show normal debug box
showEventCardDebugBox();
}
};
}
}
} else {
// Normal Event Mode
var eventDebugBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.0
});
eventDebugBox.tint = 0xA020F0;
eventDebugBox.alpha = 0.95;
eventDebugBox.x = 1024;
eventDebugBox.y = 900;
game.addChild(eventDebugBox);
var debugText = new Text2("Event card/s in hand!\nClick the cards to resolve them.", {
size: 34,
fill: 0xFFFFFF,
align: 'center'
});
debugText.anchor.set(0.5, 0.5);
debugText.x = 0;
debugText.y = 0;
eventDebugBox.addChild(debugText);
game.eventDebugBox = eventDebugBox;
}
}
function updateHandPositions() {
var handY = 2400;
var handStartX = 1024 - playerHand.length * 200 / 2 + 100;
for (var i = 0; i < playerHand.length; i++) {
var card = playerHand[i];
if (!card.isInPlay) {
tween(card, {
x: handStartX + i * 200,
y: handY
}, {
duration: 300
});
}
}
}
function canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard) {
if (!advancedTerrainCard || !basicTerrainCard) {
return false;
}
var advancedTerrain = advancedTerrainCard.terrainData;
var basicTerrain = basicTerrainCard.terrainData;
// Check if this is actually an advanced terrain card
if (!advancedTerrain || advancedTerrain.level !== 'Advanced') {
return false;
}
// Check if target is a basic terrain card
if (!basicTerrain || basicTerrain.level !== 'Basic') {
return false;
}
// Check if terrain types match (land on land, water on water)
if (advancedTerrain.subtype !== basicTerrain.subtype) {
return false;
}
// Check if specific terrain types match
if (advancedTerrain.subtype === 'land') {
if (advancedTerrain.landType !== basicTerrain.landType) {
return false;
}
} else if (advancedTerrain.subtype === 'water') {
if (advancedTerrain.waterType !== basicTerrain.waterType) {
return false;
}
}
// Check climate requirements
if (advancedTerrain.climateRequirement && advancedTerrain.climateRequirement !== 'any') {
var climateMatches = false;
if (advancedTerrain.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = advancedTerrain.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === basicTerrain.climate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = advancedTerrain.climateRequirement === basicTerrain.climate;
}
if (!climateMatches) {
return false;
}
}
return true;
}
function canPlaceCreatureOnTerrain(creatureCard, terrainCard) {
if (!creatureCard || !terrainCard) {
return false;
}
var creature = creatureCard.creatureData;
var terrain = terrainCard.terrainData;
// Check if this is actually a creature card
if (!creature) {
return false;
}
// Delicate creatures cannot be placed on Advanced terrain
if (creature.delicate && terrain.level === 'Advanced') {
return false;
}
// Check terrain type matching based on creature's subtype
if (creature.subtype === 'water') {
// Creature requires water terrain
if (terrain.subtype !== 'water') {
return false;
}
// Check specific water type matching - creature waterType must match terrain waterType
if (creature.waterType && terrain.waterType && creature.waterType !== terrain.waterType) {
return false;
}
} else if (creature.subtype === 'land') {
// Creature requires land terrain
if (terrain.subtype !== 'land') {
return false;
}
// Check specific land type matching - allow 'any' as wildcard
if (creature.landType && creature.landType !== 'any' && terrain.landType && creature.landType !== terrain.landType) {
return false;
}
}
// If creature has no subtype or terrain type, it cannot be placed
if (!creature.subtype) {
return false;
}
// Check climate requirements - use basic terrain climate if advanced terrain is placed on basic
var terrainClimate = terrain.climate;
// If this is an advanced terrain on basic terrain, use the basic terrain's climate
if (terrainCard.basicTerrainUnderneath && terrainCard.basicTerrainUnderneath.terrainData) {
terrainClimate = terrainCard.basicTerrainUnderneath.terrainData.climate;
}
if (creature.climateRequirement && creature.climateRequirement !== 'any') {
var climateMatches = false;
if (creature.climateRequirement.indexOf('/') !== -1) {
// Handle multiple climate requirements (e.g., "temperate/hot")
var allowedClimates = creature.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === terrainClimate) {
climateMatches = true;
break;
}
}
} else {
// Single climate requirement
climateMatches = creature.climateRequirement === terrainClimate;
}
if (!climateMatches) {
return false;
}
}
// Advanced creatures can only be placed on advanced terrain
if (creature.level === 'Advanced') {
if (terrain.level !== 'Advanced') {
return false;
}
}
// Basic herbivores can only be placed on basic terrain (but advanced terrain counts as valid if it's on basic terrain)
if (creature.level === 'Basic' && creature.dietType === 'herbivore') {
// Allow placement on advanced terrain that's placed on basic terrain, or directly on basic terrain
if (terrain.level !== 'Basic' && !terrainCard.basicTerrainUnderneath) {
return false;
}
}
// Dominant DNA bypasses ALL creature stacking restrictions (but still obeys terrain/climate checked above)
if (creature.dominantDNA && terrainCard.creatureStack.length > 0) {
return true;
}
// Check creature stacking rules - verify top card in stack
if (terrainCard.creatureStack && terrainCard.creatureStack.length > 0) {
var topCreatureOnTerrain = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Prevent placing anything on advanced carnivores
if (topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place anything on advanced carnivores
}
// Prevent placing Basic Herbivores on Advanced Carnivores (strict enforcement)
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Advanced') {
return false; // Cannot place Basic Herbivore on Advanced Carnivore
}
// Prevent placing Basic Herbivores on any other carnivore
if (creature.level === 'Basic' && creature.dietType === 'herbivore' && topCreatureOnTerrain.creatureData.dietType === 'carnivore' && topCreatureOnTerrain.creatureData.level === 'Basic') {
return false; // Cannot place Basic Herbivore on Basic Carnivore
} //{kH_basic}
}
// Basic carnivores can be placed on any terrain type (basic or advanced) - no restriction needed
// Check creature stacking rules
if (terrainCard.creatureStack.length > 0) {
var topCreatureInStack = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Basic creature stacking rules
if (creature.level === 'Basic') {
// Basic herbivore restrictions
if (creature.dietType === 'herbivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with other basic herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced herbivores at top
}
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic herbivores cannot be placed on stacks with advanced carnivores at top
} //{l8_advanced}
}
// Basic carnivore restrictions
if (creature.dietType === 'carnivore') {
if (topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with other basic carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'carnivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced carnivores at top
}
if (topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore') {
return false; // Basic carnivores cannot be placed on stacks with advanced herbivores at top
}
}
}
// Advanced creature stacking rules
else if (creature.level === 'Advanced') {
// Advanced herbivores can only be placed on advanced terrain with basic herbivores at top
if (creature.dietType === 'herbivore') {
var validTarget = topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'herbivore';
if (!validTarget) {
return false;
}
}
// Advanced carnivores can be placed on stacks with basic carnivores OR advanced herbivores at top
else if (creature.dietType === 'carnivore') {
var validTarget = topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Basic' && topCreatureInStack.creatureData.dietType === 'carnivore' || topCreatureInStack.creatureData && topCreatureInStack.creatureData.level === 'Advanced' && topCreatureInStack.creatureData.dietType === 'herbivore';
if (!validTarget) {
return false;
}
}
}
} else {
// No creatures in stack
if (creature.shy) {
// Shy MUST be placed on empty Advanced terrain (terrain.level is already checked above)
return true; //{lF_shy}
} else if (creature.level === 'Advanced') {
return false; // Normal Advanced creatures require existing creatures to stack on
}
}
return true;
}
function placeTerrainOnTerrain(advancedTerrainCard, basicTerrainCard, gridX, gridY) {
if (!canPlaceTerrainOnTerrain(advancedTerrainCard, basicTerrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(advancedTerrainCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Set up advanced terrain card
advancedTerrainCard.gridX = gridX;
advancedTerrainCard.gridY = gridY;
advancedTerrainCard.isInPlay = true;
// Copy creature stack from basic terrain to advanced terrain
advancedTerrainCard.creatureStack = basicTerrainCard.creatureStack || [];
// Position advanced terrain card on top of basic terrain and adopt its dimensions
var targetX = basicTerrainCard.x;
var targetY = basicTerrainCard.y;
var targetScaleX = basicTerrainCard.scaleX;
var targetScaleY = basicTerrainCard.scaleY;
tween(advancedTerrainCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300
});
// Keep basic terrain underneath but update board references to advanced terrain
planetBoard[gridY][gridX] = advancedTerrainCard;
planetSlots[gridY][gridX].terrainCard = advancedTerrainCard;
// Store reference to basic terrain underneath
advancedTerrainCard.basicTerrainUnderneath = basicTerrainCard;
// Add advanced terrain to game at specific z-index (above basic terrain but below creatures)
var basicTerrainIndex = game.getChildIndex(basicTerrainCard);
game.addChildAt(advancedTerrainCard, basicTerrainIndex + 1);
// Check for invalid links when terrain changes (shouldn't affect creature types, but safety check)
if (advancedTerrainCard.creatureStack.length > 0) {
var topCreature = advancedTerrainCard.creatureStack[advancedTerrainCard.creatureStack.length - 1];
checkAndRemoveInvalidLinks(gridX, gridY, topCreature);
}
// Update creature positions if any exist and ensure they stay on top
for (var i = 0; i < advancedTerrainCard.creatureStack.length; i++) {
var creature = advancedTerrainCard.creatureStack[i];
// Store current position to prevent unwanted movement
var currentX = creature.x;
var currentY = creature.y;
// Remove and re-add creature to ensure it's on top
game.removeChild(creature);
game.addChild(creature);
// Keep creature at its current position - no animation needed
creature.x = currentX;
creature.y = currentY;
}
// Re-render all link lines to ensure they appear above the newly placed terrain
for (var linkIdx = 0; linkIdx < linkLines.length; linkIdx++) {
var linkLine = linkLines[linkIdx];
if (linkLine.parent) {
game.removeChild(linkLine);
game.addChild(linkLine);
}
}
// Trigger Displacer
if (advancedTerrainCard.terrainData.displacer && advancedTerrainCard.creatureStack.length > 0) {
var topC = advancedTerrainCard.creatureStack.pop();
if (topC.creatureData.dietType === 'carnivore') {
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
} else {
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
}
var cData = topC.creatureData || topC.terrainData || topC.eventData;
discardPile.push(cData);
discardCountText.setText("Discard: " + discardPile.length);
if (topC.parent) topC.parent.removeChild(topC);
}
// Trigger Simplify
if (advancedTerrainCard.terrainData.simplify) {
processSimplifySpecial(gridX, gridY);
}
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (advancedTerrainCard.mustPlay) {
drawPhase = 'complete';
}
// Check if AT15 was played and set flag for next turn
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT15') {
at15PlayedLastTurn = true;
}
// Check if AT18 was played and trigger special
if (advancedTerrainCard.terrainData && advancedTerrainCard.terrainData.id === 'AT18') {
processAT18Special();
} //{if_AT18}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
//{ig}</antml>
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(advancedTerrainCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (advancedTerrainCard.mustPlay || mustPlayCard === advancedTerrainCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function checkAndRemoveInvalidLinks(gridX, gridY, newTopCreature) {
// Check all links that involve creatures at this position
var invalidLinks = [];
// Check links FROM carnivores at this position
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
// Check each active link from this carnivore
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
var link = creature.activeLinks[j];
var targetHerbivore = link.target;
// If the new top creature is a carnivore and the target is now covered, link becomes invalid
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore' && targetHerbivore.gridX === gridX && targetHerbivore.gridY === gridY) {
invalidLinks.push({
carnivore: creature,
herbivore: targetHerbivore
});
}
}
}
}
}
// Check links TO herbivores at this position (if new creature is carnivore, herbivores below become invalid targets)
if (newTopCreature && newTopCreature.creatureData.dietType === 'carnivore') {
// Find all links pointing to herbivores at this position that are now covered
for (var linkIndex = linkLines.length - 1; linkIndex >= 0; linkIndex--) {
var linkLine = linkLines[linkIndex];
if (linkLine.herbivore.gridX === gridX && linkLine.herbivore.gridY === gridY) {
// This herbivore is now covered by a carnivore, so links to it are invalid
invalidLinks.push({
carnivore: linkLine.carnivore,
herbivore: linkLine.herbivore
});
}
}
}
// Remove invalid links with fall-off animation
for (var k = 0; k < invalidLinks.length; k++) {
var invalidLink = invalidLinks[k];
animateLinkFallOff(invalidLink.carnivore, invalidLink.herbivore);
}
}
function animateLinkFallOff(carnivore, herbivore) {
// Find the link line to animate
var linkLineToRemove = null;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore && !linkLines[i].isFallingOff) {
linkLineToRemove = linkLines[i];
break;
}
}
if (linkLineToRemove) {
// Mark link as falling off immediately to prevent duplicate animations
linkLineToRemove.isFallingOff = true;
// Remove from arrays immediately to clear logical ghost link
removeLink(carnivore, herbivore);
// Animate the link falling off
tween(linkLineToRemove, {
alpha: 0,
scaleY: linkLineToRemove.scaleY * 0.1,
y: linkLineToRemove.y + 50
}, {
duration: 500,
onFinish: function onFinish() {
// Remove the visual line after animation
if (linkLineToRemove.parent) linkLineToRemove.parent.removeChild(linkLineToRemove);
}
});
} else {
// No visual line found, just remove the link immediately
removeLink(carnivore, herbivore);
}
}
function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY) {
if (!canPlaceCreatureOnTerrain(creatureCard, terrainCard)) {
return false;
}
// Remove card from hand
var handIndex = playerHand.indexOf(creatureCard);
if (handIndex !== -1) {
playerHand.splice(handIndex, 1);
}
// Check for invalid links before placing the new creature
checkAndRemoveInvalidLinks(gridX, gridY, creatureCard);
// Remove shell from covered creature
if (terrainCard.creatureStack.length > 0) {
var coveredC = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (coveredC.shellMarker) {
coveredC.removeChild(coveredC.shellMarker);
coveredC.shellMarker = null;
if (game.activeShellCreature === coveredC) game.activeShellCreature = null;
}
}
// When ANY carnivore is played onto another creature, remove all links to and from the creature being covered
if (creatureCard.creatureData.dietType === 'carnivore' && terrainCard.creatureStack.length > 0) {
var creatureBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
// Remove all links FROM the creature being covered (if it's a carnivore)
if (creatureBelow.creatureData.dietType === 'carnivore') {
for (var linkIdx = creatureBelow.activeLinks.length - 1; linkIdx >= 0; linkIdx--) {
animateLinkFallOff(creatureBelow, creatureBelow.activeLinks[linkIdx].target);
}
}
// Remove all links TO the creature being covered (if it's a herbivore or carnivore)
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === creatureBelow || linkLines[linkIdx].carnivore === creatureBelow && creatureBelow.creatureData.dietType === 'carnivore') {
animateLinkFallOff(linkLines[linkIdx].carnivore, linkLines[linkIdx].herbivore);
}
}
}
// If placing an advanced herbivore on top of a basic herbivore, remove all links to the basic herbivore below
if (creatureCard.creatureData.dietType === 'herbivore' && creatureCard.creatureData.level === 'Advanced') {
if (terrainCard.creatureStack.length > 0) {
var basicHerbivoreBelow = terrainCard.creatureStack[terrainCard.creatureStack.length - 1];
if (basicHerbivoreBelow.creatureData.dietType === 'herbivore' && basicHerbivoreBelow.creatureData.level === 'Basic') {
// Remove all links pointing to the basic herbivore below
for (var linkIdx = linkLines.length - 1; linkIdx >= 0; linkIdx--) {
if (linkLines[linkIdx].herbivore === basicHerbivoreBelow) {
animateLinkFallOff(linkLines[linkIdx].carnivore, basicHerbivoreBelow);
}
}
}
}
}
// Push existing creatures down in the stack (move them south)
var stackOffset = 20;
for (var i = 0; i < terrainCard.creatureStack.length; i++) {
var existingCreature = terrainCard.creatureStack[i];
// Clear extinction markers when another creature is placed on top
existingCreature.extinctionMarkers = 0;
existingCreature.updateExtinctionMarkers();
tween(existingCreature, {
y: existingCreature.y + stackOffset
}, {
duration: 200
});
}
// Add new creature to top of stack
terrainCard.creatureStack.push(creatureCard);
creatureCard.gridX = gridX;
creatureCard.gridY = gridY;
creatureCard.isInPlay = true;
// Position new creature at the top position (where first creature would go)
var targetX = terrainCard.x;
var colorBarHeight = 42;
var targetY = terrainCard.y - terrainCard.height * terrainCard.scaleY / 2 + colorBarHeight + 150; // Position even lower, just below the color bar with 150px offset
// Calculate scale needed to match terrain card dimensions completely
var terrainWidth = 252; // Terrain card asset width
var terrainHeight = 336; // Terrain card asset height
var creatureWidth = 160; // Creature card asset width
var creatureHeight = 220; // Creature card asset height
// Scale to match terrain dimensions exactly, but reduce Y scale by half the color bar height with moderate scaling
var targetScaleX = terrainWidth / creatureWidth * terrainCard.scaleX;
var targetScaleY = (terrainHeight - colorBarHeight / 2) / creatureHeight * terrainCard.scaleY * 0.95; // Moderate 5% reduction to sit flush with color bar
// Start with small scale and animate up to target scale like advanced terrain
creatureCard.scaleX = 0.1;
creatureCard.scaleY = 0.1;
tween(creatureCard, {
x: targetX,
y: targetY,
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300,
onFinish: function onFinish() {
//{qr_new}
applyCreatureEffect(creatureCard); //{qs_new}
} //{qt_new}
}); //{qu_new}
// Create link markers for carnivores when placed
creatureCard.createLinkMarkers();
// Update hand positions
updateHandPositions();
// Clear selection
selectedCard = null;
draggedCard = null;
// Reset draw phase after successful placement
if (creatureCard.mustPlay) {
drawPhase = 'complete';
}
// Check if this was the last event card or must play card
if (eventCardsToPlay.length > 0) {
// Remove this card from event cards to play
var eventIndex = eventCardsToPlay.indexOf(creatureCard);
if (eventIndex !== -1) {
eventCardsToPlay.splice(eventIndex, 1);
}
// If no more event cards to play, clear mustPlayCard and check for phase progression
if (eventCardsToPlay.length === 0) {
mustPlayCard = null;
// Update event cards tracking
hasEventCardsInHand = false;
// Discard remaining non-event cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (!playerHand[i].eventData) {
remainingCards.push(playerHand[i]);
}
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
} else if (creatureCard.mustPlay || mustPlayCard === creatureCard) {
// This was a must play card, clear it and progress to dusk
mustPlayCard = null;
// Discard remaining cards
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
} else {
// Regular card played, discard remaining cards and progress to dusk
var remainingCards = [];
for (var i = 0; i < playerHand.length; i++) {
remainingCards.push(playerHand[i]);
}
if (remainingCards.length > 0) {
discardCards(remainingCards);
}
gamePhase = 'dusk';
}
// Reset turn
cardsDrawnThisTurn = 0;
LK.getSound('cardPlace').play();
// Show adjust links button after card placement
showAdjustLinksButton();
return true;
}
function applyCreatureEffect(creatureCard) {
var creature = creatureCard.creatureData;
// Stack effects can be implemented here without health mechanics
if (creature.forerunner) {
processForerunnerSpecial();
}
if (creature && creature.watcher) {
processWatcherSpecial();
}
if (creature && creature.terraformer) {
processTerraformerSpecial(creatureCard.gridX, creatureCard.gridY);
}
if (creature && creature.shell) {
var shellMarker = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
shellMarker.tint = 0x8B4513;
shellMarker.x = 0;
shellMarker.y = -100;
creatureCard.addChild(shellMarker);
creatureCard.shellMarker = shellMarker;
game.activeShellCreature = creatureCard;
}
}
function getAdjacentTerrains(gridX, gridY, includeDiagonals) {
var neighbors = [];
var directions = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: 1
}];
// Add diagonals if the creature is Efficient
if (includeDiagonals) {
directions.push({
x: -1,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 1
}, {
x: 1,
y: 1
});
}
for (var i = 0; i < directions.length; i++) {
var newX = gridX + directions[i].x;
var newY = gridY + directions[i].y;
if (newX >= 0 && newX < planetWidth && newY >= 0 && newY < planetHeight) {
neighbors.push(planetBoard[newY][newX]);
}
}
return neighbors;
}
function createLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.dietType !== 'carnivore') {
return false;
}
// Check if carnivore and herbivore are adjacent
if (!areCreaturesAdjacent(carnivore, herbivore)) {
return false;
}
// Additional check: ensure we're linking to the top creature only
var herbivoreGridX = herbivore.gridX;
var herbivoreGridY = herbivore.gridY;
var terrain = planetBoard[herbivoreGridY][herbivoreGridX];
if (terrain && terrain.creatureStack.length > 0) {
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topCreature !== herbivore) {
return false; // Can only link to top creature in stack
}
}
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
for (var i = 0; i < carnivore.activeLinks.length; i++) {
if (carnivore.activeLinks[i].target === herbivore) {
return false; // Basic carnivores can only have one link per herbivore
}
}
}
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, herbivore)) {
return false; // Cannot link due to special rules (Whale food, etc)
} //{mL_new}
// Find an unlinked marker
var availableMarker = null;
for (var i = 0; i < carnivore.linkMarkers.length; i++) {
if (!carnivore.linkMarkers[i].isLinked) {
availableMarker = carnivore.linkMarkers[i];
break;
}
}
if (!availableMarker) {
return false;
} // No available link markers
// Create the link
availableMarker.isLinked = true;
availableMarker.targetHerbivore = herbivore;
availableMarker.tint = 0x00FF00; // Green for linked
// Add to active links
carnivore.activeLinks.push({
marker: availableMarker,
target: herbivore
});
// Create visual link line
createLinkLine(carnivore, herbivore);
return true;
}
function removeLink(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
// Find and remove the link
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
if (link.target === herbivore) {
link.marker.isLinked = false;
link.marker.targetHerbivore = null;
link.marker.tint = 0xFF0000; // Red for unlinked
carnivore.activeLinks.splice(i, 1);
// Remove visual link line
removeLinkLine(carnivore, herbivore);
return true;
}
}
return false;
}
function isValidLinkTarget(carnivore, herbivore) {
if (!carnivore || !herbivore) {
return false;
}
if (carnivore.creatureData.slow && (herbivore.creatureData.flying || herbivore.creatureData.fly)) {
return false; // Slow creatures cannot link to flying creatures
}
if (carnivore.creatureData.seaBound) {
if (herbivore.creatureData.subtype !== 'water') {
return false; // Sea-bound creatures can only link to water creatures
}
}
var targetIsCarnivore = herbivore.creatureData.dietType === 'carnivore';
if (targetIsCarnivore) {
// Target is a carnivore
if (carnivore.creatureData.hunterHunter || carnivore.creatureData.bully) {
// Hunter-Hunter or Bully can link to carnivores
if (carnivore.creatureData.bully && herbivore.creatureData.level !== 'Basic') {
return false; // Bully can only link to Basic Carnivores
}
return true;
}
return false; // Cannot link to carnivore unless Hunter-Hunter or Bully
} else {
// Target is a herbivore
if (carnivore.creatureData.hunterHunter) {
return false; // Hunter-Hunter cannot link to herbivores
} //{n4_hh}
} //{n4_hh_end}
if (carnivore.creatureData.fussy) {
if (herbivore.creatureData.level !== 'Basic') {
return false; // Fussy can only link to Basic Herbivores
} //{n4_fussy}
} //{n4_fussy_end}
if (herbivore.creatureData.whaleFood) {
if (carnivore.creatureData.id !== 'AC03') {
return false; // Cannot link to Whale food unless carnivore is Whale
}
}
return true;
}
function areCreaturesAdjacent(creature1, creature2) {
if (!creature1.isInPlay || !creature2.isInPlay) {
return false;
}
var dx = Math.abs(creature1.gridX - creature2.gridX);
var dy = Math.abs(creature1.gridY - creature2.gridY);
// Check if creature1 has Efficient attribute
var creature1HasEfficient = creature1.creatureData && creature1.creatureData.efficient;
// Adjacent means exactly one grid space away (orthogonal or diagonal if Efficient)
if (creature1HasEfficient) {
// Efficient creatures can link to adjacent (including diagonal) creatures
return dx === 1 && dy === 0 || dx === 0 && dy === 1 || dx === 1 && dy === 1;
} else {
// Non-Efficient creatures can only link orthogonally (up, down, left, right)
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
}
function createLinkLine(carnivore, herbivore) {
// Count how many links ALREADY exist between this carnivore and herbivore BEFORE adding the new one
var existingLinkCount = 0;
for (var i = 0; i < linkLines.length; i++) {
if (linkLines[i].carnivore === carnivore && linkLines[i].herbivore === herbivore) {
existingLinkCount++;
}
} //{e4_duplicate}
// Create visual arrow line using a thicker rectangle
var line = LK.getAsset('terrainLandFlat', {
anchorX: 0,
anchorY: 0.5
});
line.tint = 0xFFFF00; // Yellow link line by default
line.alpha = 0.8;
// Position arrow starting from slightly inside carnivore border towards herbivore
var dx = herbivore.x - carnivore.x;
var dy = herbivore.y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Calculate starting position slightly inside carnivore border (about 20px inward)
var startOffsetX = Math.cos(angle) * 20;
var startOffsetY = Math.sin(angle) * 20;
var startX = carnivore.x + startOffsetX;
var startY = carnivore.y + startOffsetY;
// Calculate gap distance (reduce total distance by both card radii plus small gap)
var gapDistance = distance - 100; // 50px from each card edge creates gap
line.x = startX;
line.y = startY;
line.rotation = angle;
line.scaleX = gapDistance / 252; // Scale based on gap distance
line.scaleY = 0.08; // Thicker line (8% of original height)
line.carnivore = carnivore;
line.herbivore = herbivore;
line.linkIndex = existingLinkCount; // Track which link number this is
// Calculate perpendicular offset for multiple links to avoid stacking
if (existingLinkCount >= 1) {
// 2nd link goes one way (+90 deg), 3rd link goes the opposite way (-90 deg)
var perpendicularAngle = existingLinkCount === 1 ? angle + Math.PI / 2 : angle - Math.PI / 2;
var offsetDistance = 30; // Offset distance in pixels
var offsetX = Math.cos(perpendicularAngle) * offsetDistance;
var offsetY = Math.sin(perpendicularAngle) * offsetDistance;
// Apply offset to line position
line.x += offsetX;
line.y += offsetY;
// Change line color and create indicator based on link number
var indicatorString = "";
if (existingLinkCount === 1) {
line.tint = 0xFF00FF; // Magenta for 2nd link
indicatorString = "X2";
} else {
line.tint = 0x00BFFF; // Deep Sky Blue for 3rd link
indicatorString = "X3";
}
// Add the text indicator
var linkIndicator = new Text2(indicatorString, {
size: 20,
fill: 0xFFFFFF
});
linkIndicator.anchor.set(0.5, 0.5);
linkIndicator.x = 0;
linkIndicator.y = 0;
line.addChild(linkIndicator);
linkIndicator.lineOwner = line;
}
game.addChild(line);
linkLines.push(line);
}
function removeLinkLine(carnivore, herbivore) {
for (var i = linkLines.length - 1; i >= 0; i--) {
var line = linkLines[i];
if (line.carnivore === carnivore && line.herbivore === herbivore) {
if (!line.isFallingOff) {
if (line.parent) line.parent.removeChild(line);
}
linkLines.splice(i, 1);
return; // Remove only ONE link and return
}
}
}
function highlightValidLinkTargets(carnivore) {
// Clear existing highlights
clearLinkHighlights();
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Check if carnivore has Efficient attribute
var carnivoreHasEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
// Find adjacent herbivores that can be linked to (including diagonals for Efficient)
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
// Only consider the top creature in each stack
var topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
if (areCreaturesAdjacent(carnivore, topCreature)) {
// Check if this is a valid link target considering all special rules
if (!isValidLinkTarget(carnivore, topCreature)) {
// Cannot link to this herbivore due to special rules
continue;
} //{nx_new}
var canCreateLink = false;
// For basic carnivores, check if already linked to this herbivore
if (carnivore.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var j = 0; j < carnivore.activeLinks.length; j++) {
if (carnivore.activeLinks[j].target === topCreature) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
canCreateLink = true;
}
} else {
// Advanced carnivores can always create links if they have available markers
var hasAvailableMarker = false;
for (var k = 0; k < carnivore.linkMarkers.length; k++) {
if (!carnivore.linkMarkers[k].isLinked) {
hasAvailableMarker = true;
break;
}
}
if (hasAvailableMarker) {
canCreateLink = true;
}
}
if (canCreateLink) {
// Create highlight around this herbivore
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
// Use different colors for diagonal highlights if Efficient
if (carnivoreHasEfficient) {
var dx = Math.abs(carnivore.gridX - topCreature.gridX);
var dy = Math.abs(carnivore.gridY - topCreature.gridY);
if (dx === 1 && dy === 1) {
// Diagonal link for Efficient creature
highlight.tint = 0x00FFFF; // Cyan for diagonal
} else {
// Orthogonal link
highlight.tint = 0x00FF00; // Green for orthogonal
}
} else {
highlight.tint = 0x00FF00; // Green highlight
}
highlight.alpha = 0.3;
highlight.scaleX = topCreature.scaleX * 1.2;
highlight.scaleY = topCreature.scaleY * 1.2;
highlight.x = topCreature.x;
highlight.y = topCreature.y;
highlight.targetCreature = topCreature;
game.addChild(highlight);
linkHighlights.push(highlight);
}
}
}
}
}
}
function clearLinkHighlights() {
for (var i = 0; i < linkHighlights.length; i++) {
if (linkHighlights[i].parent) {
linkHighlights[i].parent.removeChild(linkHighlights[i]);
}
}
linkHighlights = [];
}
function updateLinkMarkerStates() {
// Update all link markers based on their state and game phase
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures - they cannot interact with links
continue;
} //{f9_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// Check if this carnivore will become extinct (insufficient links)
var willBecomeExtinct = creature.activeLinks.length < creature.linkRequirement;
for (var j = 0; j < creature.linkMarkers.length; j++) {
var marker = creature.linkMarkers[j];
if (gamePhase === 'dusk' && linkAdjustmentMode) {
// During dusk phase with adjustment mode active, make markers interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
// Make linked markers draggable to remove links
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
// Check if this marker can be legally linked
var hasValidTargets = false;
var isEfficient = creature.creatureData && creature.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var k = 0; k < adjacentTerrains.length; k++) {
var adjTerrain = adjacentTerrains[k];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (isValidLinkTarget(creature, topHerbivore)) {
// For basic carnivores, check if already linked to this herbivore
if (creature.creatureData.level === 'Basic') {
var hasExistingLink = false;
for (var m = 0; m < creature.activeLinks.length; m++) {
if (creature.activeLinks[m].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink) {
hasValidTargets = true;
break;
}
} else {
// Advanced carnivores can always link if they have available markers
hasValidTargets = true;
break;
}
}
}
if (hasValidTargets) {
break;
}
}
if (hasValidTargets) {
marker.tint = 0xFFFF00; // Yellow for linkable
// Make unlinked but valid markers draggable
marker.down = function (x, y, obj) {
draggedLinkMarker = this;
draggedLinkCarnivore = this.carnivore;
};
} else {
marker.tint = 0x808080; // Gray for non-linkable
marker.down = function (x, y, obj) {
// Do nothing for non-linkable markers
};
// Add N/A text
if (!marker.naText) {
marker.naText = new Text2("N/A", {
size: 8,
fill: 0xFFFFFF
});
marker.naText.anchor.set(0.5, 0.5);
marker.naText.x = 0;
marker.naText.y = 0;
marker.addChild(marker.naText);
}
}
}
} else {
// Outside dusk phase or not in adjustment mode, markers are not interactive
if (marker.isLinked) {
marker.tint = 0x00FF00; // Green for linked
} else {
marker.tint = 0xFF0000; // Red for unlinked
}
// Remove down handler and N/A text if present
marker.down = function (x, y, obj) {};
if (marker.naText && marker.naText.parent) {
marker.naText.parent.removeChild(marker.naText);
marker.naText = null;
}
}
// Flash markers if carnivore will become extinct
if (willBecomeExtinct) {
// Stop any existing flash animation
tween.stop(marker, {
alpha: true
});
// Start flashing animation
tween(marker, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(marker, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the flash cycle if still extinct
if (creature.activeLinks.length < creature.linkRequirement) {
updateLinkMarkerStates();
}
}
});
}
});
} else {
// Stop flashing if no longer about to become extinct
tween.stop(marker, {
alpha: true
});
marker.alpha = 1;
}
}
}
}
}
}
}
}
function autoCreateLinks() {
// Auto-create mandatory links for carnivores
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var carnivore = terrain.creatureStack[i];
if (carnivore.creatureData.dietType === 'carnivore') {
// Try to create links for this carnivore
while (carnivore.activeLinks.length < carnivore.linkRequirement) {
var linkCreated = false;
// Find adjacent herbivores
var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var j = 0; j < adjacentTerrains.length && !linkCreated; j++) {
var adjTerrain = adjacentTerrains[j];
if (adjTerrain && adjTerrain.creatureStack.length > 0) {
// Only consider the top creature in each adjacent stack
var topHerbivore = adjTerrain.creatureStack[adjTerrain.creatureStack.length - 1];
if (topHerbivore.creatureData.dietType === 'herbivore') {
// Check if we can create a link
var hasExistingLink = false;
for (var l = 0; l < carnivore.activeLinks.length; l++) {
if (carnivore.activeLinks[l].target === topHerbivore) {
hasExistingLink = true;
break;
}
}
if (!hasExistingLink && createLink(carnivore, topHerbivore)) {
linkCreated = true;
}
}
}
}
if (!linkCreated) {
break;
} // No more links possible
}
}
}
}
}
}
}
function processLinkConsequences() {
// Hide the adjust links button when leaving dusk phase
hideAdjustLinksButton();
// NOTE: Extinction markers for herbivores are only processed during night phase
// This function only handles cleanup, not marker assignment
}
function processNightPhase() {
// Show visual cue that night phase is processing
showPhaseTransition("Night Phase - Checking links...", function () {
// Add extinction markers to carnivores without sufficient links
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
// ONLY process the top creature in the stack - creatures underneath are not active
var isTopCreature = i === terrain.creatureStack.length - 1;
if (!isTopCreature) {
// Skip creatures that are underneath other creatures
continue;
} //{ho_underneath}
if (creature.creatureData.dietType === 'carnivore') {
// For carnivores: check if they have sufficient links AT THIS MOMENT
if (creature.activeLinks.length < creature.linkRequirement) {
creature.extinctionMarkers += 1; // Only 1 marker per turn regardless of shortage
creature.updateExtinctionMarkers();
}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
}
} else if (creature.creatureData.dietType === 'herbivore') {
// Check if Delicate creature is on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
// Count links pointing to them AT THIS MOMENT ONLY
var linksToThisHerbivore = 0;
for (var j = 0; j < linkLines.length; j++) {
if (linkLines[j].herbivore === creature) {
linksToThisHerbivore++;
} //{tg_new}
}
// Add extinction markers equal to the number of excess links EVERY Night Phase
var excessLinks = linksToThisHerbivore - creature.safeLinks;
if (excessLinks > 0) {
// No cap, no armor! Add exactly as many markers as there are excess links
creature.extinctionMarkers += excessLinks;
creature.updateExtinctionMarkers();
}
}
}
}
}
}
// Auto-progress to end turn phase
gamePhase = 'end';
processEndTurnPhase();
});
}
function processExtinctions() {
// Remove creatures with 2 or more extinction markers
var extinctCreatures = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = terrain.creatureStack.length - 1; i >= 0; i--) {
var creature = terrain.creatureStack[i];
var extinctionThreshold = 2;
if (creature.creatureData && creature.creatureData.tough) {
extinctionThreshold = 3;
} else if (creature.creatureData && creature.creatureData.squishy) {
extinctionThreshold = 1;
}
if (creature.extinctionMarkers >= extinctionThreshold) {
extinctCreatures.push(creature);
terrain.creatureStack.splice(i, 1);
// Tween all creatures that were physically positioned below the dead creature UP by 20 pixels
for (var j = 0; j < i; j++) {
var creatureBelow = terrain.creatureStack[j];
tween(creatureBelow, {
y: creatureBelow.y - 20
}, {
duration: 200
});
}
creaturesWentExtinct = true; // Mark that creatures went extinct this round
scryDeniedNextRound = true; // Deny scry for next round when extinction occurs
// Clear extinction markers when creature leaves play
creature.extinctionMarkers = 0;
creature.updateExtinctionMarkers();
// Remove all links involving this creature with fall-off animation
if (creature.creatureData.dietType === 'carnivore') {
for (var j = creature.activeLinks.length - 1; j >= 0; j--) {
animateLinkFallOff(creature, creature.activeLinks[j].target);
}
} else {
// Remove links from carnivores to this herbivore with fall-off animation
for (var j = linkLines.length - 1; j >= 0; j--) {
if (linkLines[j].herbivore === creature) {
animateLinkFallOff(linkLines[j].carnivore, creature);
}
}
}
creature.die();
}
}
}
}
}
}
function createEventDecks() {
basicEventDeck = [];
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE01',
name: "Time fly's"
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE02',
name: 'Devolution'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE03',
name: 'Disease'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE04',
name: 'Volcano'
});
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE05',
name: 'Rising Seas'
});
for (var i = 6; i <= 5; i++) {
basicEventDeck.push({
type: 'basic',
cardType: 'event',
level: 'Basic',
id: 'BE0' + i,
name: 'Basic Event ' + i
});
}
advancedEventDeck = [];
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE01',
name: 'Smite',
effect: 'catastrophic'
});
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE02',
name: 'Harsh Evolution',
effect: 'catastrophic'
});
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE03',
name: 'Meteor',
effect: 'catastrophic'
});
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE04',
name: 'Virus',
effect: 'catastrophic'
});
for (var i = 5; i <= 5; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE0' + i,
name: 'Advanced Event ' + i,
effect: 'catastrophic'
});
}
}
function addEventCardToDeck(creatureLevel) {
var eventCard = null;
if (creatureLevel === 'Advanced') {
if (advancedEventDeck.length === 0 && advancedEventDiscard.length > 0) {
advancedEventDeck = advancedEventDiscard.slice();
advancedEventDiscard = [];
shuffleEventDeck(advancedEventDeck);
}
if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();else if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();
} else {
if (basicEventDeck.length === 0 && basicEventDiscard.length > 0) {
basicEventDeck = basicEventDiscard.slice();
basicEventDiscard = [];
shuffleEventDeck(basicEventDeck);
}
if (basicEventDeck.length > 0) eventCard = basicEventDeck.pop();else if (advancedEventDeck.length > 0) eventCard = advancedEventDeck.pop();
}
if (eventCard) discardPile.push(eventCard);
}
function processDawnPhase() {
// Show visual cue that dawn phase is processing
showPhaseTransition("Dawn Phase - Processing...", function () {
gamePhase = 'draw';
processDrawPhase(); // Auto-progress to draw phase
});
}
function processDrawPhase() {
// Draw exactly 3 cards (or 4 if AT15 was played last turn), handling deck reshuffling as needed
var cardsToDrawTotal = 3;
if (at15PlayedLastTurn) {
cardsToDrawTotal = 4;
at15PlayedLastTurn = false; // Reset flag after using it
}
// Step 1: Create validHandFound boolean
var validHandFound = false;
// Step 2: Create master while loop that runs as long as validHandFound is false
while (!validHandFound) {
// Step 3: Draw cards until the player's hand reaches the required amount
var continueDrawing = true;
var cardsDrawnThisPhase = 0;
while (continueDrawing && playerHand.length < cardsToDrawTotal) {
// Check if deck is empty, reshuffle if needed
if (mainDeck.length === 0) {
// Shuffle discard pile to become new deck
if (discardPile.length > 0) {
mainDeck = discardPile.slice();
discardPile = [];
shuffleDeck();
currentRound++;
deckCountText.setText("Deck: " + mainDeck.length);
// Check for end game condition after round increment
if (currentRound >= 6) {
// Game ends immediately
LK.showYouWin();
return;
}
// Check and trigger scry status at beginning of new round
// Only activate scry if no extinctions occurred last round
if (!scryDeniedNextRound) {
scryTokenActive = true;
} //{ht_scry}
creaturesWentExtinct = false;
scryDeniedNextRound = false;
} else {
// No cards available, stop drawing
continueDrawing = false;
break; // Exit drawing loop if both deck and discard are empty
}
}
// Draw one card if deck has cards
if (mainDeck.length > 0) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
playerHand.push(card);
game.addChild(card);
// Update hand positions after each card is added
updateHandPositions();
cardsDrawnThisPhase++;
} else {
// No more cards in deck, stop drawing
continueDrawing = false;
}
}
// Step 4: After the hand is filled, update the hand positions. If the hand is completely empty, break the master loop.
updateHandPositions();
if (playerHand.length === 0) {
// Hand is empty because both deck and discard are empty
break; // Exit master while loop
} //{uv_new}
// Check for Stinky effect after drawing cards
checkAndProcessStinkyEffect();
// Update hand positions again in case Stinky effect discarded adjacent cards
updateHandPositions();
// Step 5: Check if the current hand has valid placements
if (hasValidPlacements(playerHand)) {
// Hand has at least one valid placement, set validHandFound to true to exit master loop
validHandFound = true; //{uC_new}
} else {
//{uD_new}
// Hand is unplayable, discard all cards and loop will repeat to draw a fresh hand
discardCards(playerHand.slice()); //{uB_new}
} //{uE_new}
} //{uF_new}
// Step 6: After the master loop finishes, update the deck and discard UI text, play the draw sound, and change the game phase to 'noon'
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
LK.getSound('cardDraw').play();
gamePhase = 'noon';
}
function createCardFromData(cardData) {
var card;
if (cardData.cardType === 'terrain') {
card = new TerrainCard(cardData);
card.scaleX = 0.6;
card.scaleY = 0.6;
} else if (cardData.cardType === 'event') {
// Create event card using EventCard class
card = new EventCard(cardData);
} else {
// Default to creature card
card = new CreatureCard(cardData);
}
return card;
}
function processNoonPhase() {
// Check for event cards in hand - they must be played first
eventCardsToPlay = [];
for (var i = 0; i < playerHand.length; i++) {
if (playerHand[i].eventData) {
eventCardsToPlay.push(playerHand[i]);
}
}
if (eventCardsToPlay.length > 0) {
// Player must play event cards first
mustPlayCard = eventCardsToPlay[0]; // Force first event card
return;
}
// Check if player has valid moves
var validCards = [];
for (var i = 0; i < playerHand.length; i++) {
if (hasValidPlacements([playerHand[i]])) {
validCards.push(playerHand[i]);
}
}
if (validCards.length === 0 && playerHand.length === 3) {
// Show no playable cards notification
showNoPlayableCardsNotification();
return;
}
// Player can choose which card to play (if multiple valid options)
if (validCards.length > 1) {
// Discard non-chosen cards (this would be handled by player interaction)
// For now, just continue - player will choose
}
}
function showPhaseTransition(message, callback) {
// Clear any existing transition timer
if (phaseTransitionTimer) {
LK.clearTimeout(phaseTransitionTimer);
}
// Create transition message
var transitionText = new Text2(message, {
size: 40,
fill: 0xFFFF00
});
transitionText.anchor.set(0.5, 0.5);
transitionText.x = 1024;
transitionText.y = 1366;
transitionText.alpha = 0;
game.addChild(transitionText);
// Fade in message
tween(transitionText, {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
// Show message for 1 second, then fade out and execute callback
phaseTransitionTimer = LK.setTimeout(function () {
tween(transitionText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (transitionText.parent) {
transitionText.parent.removeChild(transitionText);
}
if (callback) {
callback();
}
}
});
}, 1000);
}
});
}
function processDuskPhase() {
// Show visual cue that dusk phase is processing
showPhaseTransition("Dusk Phase - Arrange links...", function () {
// Show the adjust links button at start of dusk phase
showAdjustLinksButton();
// Update link marker states to make them interactive
updateLinkMarkerStates();
// Player now manually arranges links - no auto-progression
// Phase will advance when player clicks next phase button
});
}
function processEndTurnPhase() {
// Show visual cue that end turn phase is processing
showPhaseTransition("End Turn - Processing extinctions...", function () {
// Process extinctions
processExtinctions();
// Extinction markers persist - they are NOT cleared here
turnNumber++;
gamePhase = 'dawn'; // Start next turn
processDawnPhase(); // Auto-progress to next dawn phase
});
}
function endRound() {
currentRound++;
if (currentRound > maxRounds) {
// Game complete
LK.showYouWin();
return;
}
// Reshuffle deck
createInitialDeck();
// Reset turn counter and draw phase
cardsDrawnThisTurn = 0;
drawPhase = 'complete';
cardsDrawnInPhase = [];
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
}
// Draw card button functionality
var drawButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var drawButtonText = new Text2("DRAW", {
size: 32,
fill: 0xFFFFFF
});
drawButtonText.anchor.set(0.5, 0.5);
drawButton.addChild(drawButtonText);
drawButton.x = 1700;
drawButton.y = 2400;
game.addChild(drawButton);
drawButton.down = function () {
if (gamePhase === 'draw') {
processDrawPhase();
}
};
// Phase management buttons - redesigned for proper turn flow
var nextPhaseButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.8
});
nextPhaseButton.tint = 0x32CD32; // Green for next phase
var nextPhaseButtonText = new Text2("NEXT PHASE", {
size: 24,
fill: 0xFFFFFF
});
nextPhaseButtonText.anchor.set(0.5, 0.5);
nextPhaseButton.addChild(nextPhaseButtonText);
nextPhaseButton.x = 500;
nextPhaseButton.y = 2400;
game.addChild(nextPhaseButton);
var nextPhaseWarningBox = null;
function getCreaturesInDanger() {
var creaturesInDanger = [];
for (var gridY = 0; gridY < planetHeight; gridY++) {
//{kA_danger}
for (var gridX = 0; gridX < planetWidth; gridX++) {
//{kB_danger}
var terrain = planetBoard[gridY][gridX]; //{kC_danger}
if (terrain && terrain.creatureStack.length > 0) {
//{kD_danger}
for (var i = 0; i < terrain.creatureStack.length; i++) {
//{kE_danger}
var creature = terrain.creatureStack[i]; //{kF_danger}
// ONLY check top creatures in stack
var isTopCreature = i === terrain.creatureStack.length - 1; //{kG_danger}
if (!isTopCreature) {
//{kH_danger}
continue; //{kI_danger}
} //{kJ_danger}
// Check for Delicate creatures on Advanced terrain
if (creature.creatureData.delicate && terrain.terrainData.level === 'Advanced') {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
}
// Check herbivores for excess links
if (creature.creatureData.dietType === 'herbivore') {
//{kK_danger}
var linksToThisHerbivore = 0; //{kL_danger}
for (var j = 0; j < linkLines.length; j++) {
//{kM_danger}
if (linkLines[j].herbivore === creature) {
//{kN_danger}
linksToThisHerbivore++; //{kO_danger}
} //{kP_danger}
} //{kQ_danger}
var excessLinks = linksToThisHerbivore - creature.safeLinks; //{kR_danger}
if (excessLinks > 0) {
//{kS_danger}
creaturesInDanger.push(creature); //{kT_danger}
} //{kU_danger}
} //{kV_danger}
// Check carnivores for insufficient links
else if (creature.creatureData.dietType === 'carnivore') {
//{kW_danger}
if (creature.activeLinks.length < creature.linkRequirement) {
//{kX_danger}
creaturesInDanger.push(creature); //{kY_danger}
} //{kZ_danger}
// Check if this carnivore is poisoned (feeding on Poisonous creature)
for (var p = 0; p < creature.activeLinks.length; p++) {
if (creature.activeLinks[p].target.creatureData.poisonous) {
if (creaturesInDanger.indexOf(creature) === -1) creaturesInDanger.push(creature);
break;
}
}
} //{l0_danger}
} //{l1_danger}
} //{l2_danger}
} //{l3_danger}
} //{l4_danger}
return creaturesInDanger; //{l5_danger}
} //{l6_danger}
function showNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{l7_danger}
return; // Warning already showing
} //{l8_danger}
// Create warning box
nextPhaseWarningBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{l9_danger}
anchorY: 0.5 //{la_danger}
}); //{lb_danger}
nextPhaseWarningBox.tint = 0xFF8C00; // Orange background
nextPhaseWarningBox.alpha = 0.95;
nextPhaseWarningBox.scaleX = 2.0;
nextPhaseWarningBox.scaleY = 1.2;
nextPhaseWarningBox.x = 500;
nextPhaseWarningBox.y = 2100;
game.addChild(nextPhaseWarningBox);
// Create warning text
var warningText = new Text2("Warning! These creatures are in danger!\nAre you sure you want to end the turn?", {
size: 20,
//{lc_danger}
fill: 0xFFFFFF //{ld_danger}
}); //{le_danger}
warningText.anchor.set(0.5, 0.5);
warningText.x = 0;
warningText.y = 0;
nextPhaseWarningBox.addChild(warningText);
} //{lf_danger}
function hideNextPhaseWarning() {
if (nextPhaseWarningBox && nextPhaseWarningBox.parent) {
//{lg_danger}
nextPhaseWarningBox.parent.removeChild(nextPhaseWarningBox); //{lh_danger}
nextPhaseWarningBox = null;
} //{li_danger}
} //{lj_danger}
function highlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lk_danger}
var creature = creaturesInDanger[i]; //{ll_danger}
tween.stop(creature, {
//{lm_danger}
tint: true //{ln_danger}
}); //{lo_danger}
tween(creature, {
//{lp_danger}
tint: 0xFFFF00 //{lq_danger}
}, {
//{lr_danger}
duration: 200 //{ls_danger}
}); //{lt_danger}
} //{lu_danger}
} //{lv_danger}
function unhighlightCreaturesInDanger(creaturesInDanger) {
for (var i = 0; i < creaturesInDanger.length; i++) {
//{lw_danger}
var creature = creaturesInDanger[i]; //{lx_danger}
tween.stop(creature, {
//{ly_danger}
tint: true //{lz_danger}
}); //{mA_danger}
tween(creature, {
//{mB_danger}
tint: 0xFFFFFF //{mC_danger}
}, {
//{mD_danger}
duration: 200 //{mE_danger}
}); //{mF_danger}
} //{mG_danger}
} //{mH_danger}
nextPhaseButton.down = function () {
// Exit link adjustment mode if active and hide adjust links button
if (linkAdjustmentMode) {
deactivateLinkAdjustmentMode();
}
// Always hide adjust links button when next phase is clicked
hideAdjustLinksButton();
// Hide warning box when button is pressed
hideNextPhaseWarning(); //{mI_danger}
// Disable button completely if events must be played
if (gamePhase === 'noon' && hasEventCardsInHand) {
return;
}
// Prevent skipping card play if the player holds valid normal cards
if (gamePhase === 'draw' || gamePhase === 'noon' && playerHand.length > 0 && hasValidPlacements(playerHand)) {
// Show a safe, harmless fading text warning instead of the destructive discard loop
if (!game.safeSkipWarning) {
var safeWarning = new Text2("You have playable cards! You must play one.", {
size: 40,
fill: 0xFF0000
});
safeWarning.anchor.set(0.5, 0.5);
safeWarning.x = 1024;
safeWarning.y = 1366;
game.addChild(safeWarning);
game.safeSkipWarning = safeWarning;
tween(safeWarning, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (safeWarning.parent) safeWarning.parent.removeChild(safeWarning);
game.safeSkipWarning = null;
}
});
}
return;
}
if (gamePhase === 'dawn') {
processDawnPhase();
} else if (gamePhase === 'draw') {
processDrawPhase();
} else if (gamePhase === 'noon') {
// Check if player has event cards that must be played first
if (eventCardsToPlay.length > 0 || mustPlayCard) {
// Must play event cards or mandatory card first
return;
}
gamePhase = 'dusk';
processDuskPhase();
} else if (gamePhase === 'dusk') {
// Apply consequences for over-hunted herbivores
processLinkConsequences();
clearLinkHighlights();
gamePhase = 'night';
processNightPhase();
} else if (gamePhase === 'night') {
processNightPhase();
} else if (gamePhase === 'end') {
processEndTurnPhase();
}
};
function updateDangerHighlights() {
var inDangerList = [];
if (gamePhase === 'noon' || gamePhase === 'dusk') {
inDangerList = getCreaturesInDanger();
}
for (var gridY = 0; gridY < planetHeight; gridY++) {
var _loop = function _loop() {
terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
topCreature = terrain.creatureStack[terrain.creatureStack.length - 1];
isInDanger = false;
for (i = 0; i < inDangerList.length; i++) {
if (inDangerList[i] === topCreature) {
isInDanger = true;
break;
}
}
if (isInDanger) {
// Only trigger the highlight creation once
if (!topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = true;
// Create halo AS A CHILD so it inherits placement movement and scaling
if (!topCreature.dangerHalo) {
halo = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
halo.tint = 0xFF0000;
halo.alpha = 0.2;
// Reduced scale to 1.05 so it stays tight to the card and doesn't conflict
halo.scaleX = 1.05;
halo.scaleY = 1.05;
halo.x = 0;
halo.y = 0;
topCreature.addChildAt(halo, 0);
topCreature.dangerHalo = halo;
// Attach the pulse function directly to the halo to avoid closure/scope bugs
halo.pulse = function () {
if (!this.parent) return;
var currentHalo = this;
tween(currentHalo, {
alpha: 0.8
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!currentHalo.parent) return;
tween(currentHalo, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: currentHalo.pulse.bind(currentHalo)
});
}
});
};
halo.pulse();
}
}
} else {
if (topCreature.isDangerFlashing) {
topCreature.isDangerFlashing = false;
if (topCreature.dangerHalo && topCreature.dangerHalo.parent) {
tween.stop(topCreature.dangerHalo);
topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo);
topCreature.dangerHalo = null;
}
}
}
}
},
terrain,
topCreature,
isInDanger,
i,
halo;
for (var gridX = 0; gridX < planetWidth; gridX++) {
_loop();
}
}
// Process global warning text safely
if (gamePhase === 'noon' && inDangerList.length > 0) {
if (!game.dangerWarningText || !game.dangerWarningText.parent) {
var _flashText = function flashText() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!game.dangerWarningText || !game.dangerWarningText.parent) return;
tween(game.dangerWarningText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _flashText
});
}
});
};
var warningText = new Text2("Warning!\nCreature/s in danger!", {
size: 40,
fill: 0xFF0000,
align: 'right'
});
warningText.anchor.set(1, 0);
warningText.x = 2000;
warningText.y = 330;
game.addChild(warningText);
game.dangerWarningText = warningText;
_flashText();
}
} else {
if (game.dangerWarningText && game.dangerWarningText.parent) {
tween.stop(game.dangerWarningText);
game.dangerWarningText.parent.removeChild(game.dangerWarningText);
game.dangerWarningText = null;
}
}
}
var gameStatusText = new Text2("Round: 1 | Phase: DAWN | Turn: 1", {
size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
gameStatusText.x = 1024;
gameStatusText.y = 60;
game.addChild(gameStatusText);
// Link adjustment system variables
var linkAdjustmentMode = false;
var adjustLinkButton = null;
var cardsWithAdjustableLinks = [];
var currentlyAdjustingCard = null;
function createAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
}
adjustLinkButton = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.8
});
adjustLinkButton.tint = 0xFFD700; // Gold for adjust links
var adjustButtonText = new Text2("Adjust Links?", {
size: 20,
fill: 0x000000
});
adjustButtonText.anchor.set(0.5, 0.5);
adjustLinkButton.addChild(adjustButtonText);
adjustLinkButton.x = 250; // Left of next phase button
adjustLinkButton.y = 2400;
game.addChild(adjustLinkButton);
adjustLinkButton.down = function () {
activateLinkAdjustmentMode();
};
}
function showAdjustLinksButton() {
createAdjustLinksButton();
}
function hideAdjustLinksButton() {
if (adjustLinkButton && adjustLinkButton.parent) {
adjustLinkButton.parent.removeChild(adjustLinkButton);
adjustLinkButton = null;
}
}
function activateLinkAdjustmentMode() {
linkAdjustmentMode = true;
currentlyAdjustingCard = null;
cardsWithAdjustableLinks = [];
// Find all carnivores on the board
for (var gridY = 0; gridY < planetHeight; gridY++) {
for (var gridX = 0; gridX < planetWidth; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain && terrain.creatureStack.length > 0) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var creature = terrain.creatureStack[i];
if (creature.creatureData.dietType === 'carnivore') {
cardsWithAdjustableLinks.push(creature);
}
}
}
}
}
// Create and display personal "Alter Links" buttons on all carnivore cards
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.createPersonalAlterLinksButton();
}
}
function deactivateLinkAdjustmentMode() {
linkAdjustmentMode = false;
// Remove personal alter buttons from all cards and restore card colors
for (var i = 0; i < cardsWithAdjustableLinks.length; i++) {
var card = cardsWithAdjustableLinks[i];
card.tint = 0xFFFFFF; // Return to normal color
// Remove personal alter button
if (card.personalAlterButton && card.personalAlterButton.parent) {
card.personalAlterButton.parent.removeChild(card.personalAlterButton);
card.personalAlterButton = null;
}
}
cardsWithAdjustableLinks = [];
currentlyAdjustingCard = null;
}
// Handle card dragging and link highlighting on hold
game.move = function (x, y, obj) {
if (draggedCard) {
draggedCard.x = x;
draggedCard.y = y;
// Ensure dragged card is always on top
game.removeChild(draggedCard);
game.addChild(draggedCard);
// Highlights are already shown from the card's down event
} else if (draggedLinkMarker && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker dragging - highlight valid targets only while holding
var carnivore = draggedLinkMarker.carnivore;
highlightValidLinkTargets(carnivore);
// Create temporary visual line from carnivore to cursor
// Remove any existing temp line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
}
// Create new temp line
var tempLine = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.01,
scaleY: 1
});
tempLine.tint = 0xFFFF00; // Yellow temp line
tempLine.alpha = 0.5;
var dx = x - carnivore.x;
var dy = y - carnivore.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
tempLine.x = carnivore.x + dx / 2;
tempLine.y = carnivore.y + dy / 2;
tempLine.rotation = angle;
tempLine.scaleY = distance / 336;
game.addChild(tempLine);
game.tempLinkLine = tempLine;
}
};
game.up = function (x, y, obj) {
if (draggedCard) {
var placed = false;
// Check if card was dropped on valid terrain
for (var gridY = 0; gridY < planetHeight && !placed; gridY++) {
for (var gridX = 0; gridX < planetWidth && !placed; gridX++) {
var terrain = planetBoard[gridY][gridX];
if (terrain) {
if (draggedCard.creatureData && canPlaceCreatureOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeCreatureOnStack(draggedCard, terrain, gridX, gridY);
}
} else if (draggedCard.terrainData && canPlaceTerrainOnTerrain(draggedCard, terrain)) {
if (Math.abs(terrain.x - x) < 140 && Math.abs(terrain.y - y) < 168) {
placed = placeTerrainOnTerrain(draggedCard, terrain, gridX, gridY);
}
}
}
}
}
// If not placed, return to original position
if (!placed && originalCardPosition) {
tween(draggedCard, originalCardPosition, {
duration: 300
});
}
// Clear all highlights
hidePossibleMoves();
draggedCard = null;
originalCardPosition = null;
} else if (draggedLinkMarker && draggedLinkCarnivore && gamePhase === 'dusk' && linkAdjustmentMode) {
// Handle link marker release with new system
var linkCreated = false;
// Remove temporary line
if (game.tempLinkLine && game.tempLinkLine.parent) {
game.tempLinkLine.parent.removeChild(game.tempLinkLine);
game.tempLinkLine = null;
}
// Check if released over a valid herbivore
for (var i = 0; i < linkHighlights.length; i++) {
var highlight = linkHighlights[i];
if (highlight.targetCreature) {
var creature = highlight.targetCreature;
var distance = Math.sqrt(Math.pow(creature.x - x, 2) + Math.pow(creature.y - y, 2));
if (distance < 100) {
// Within range of the herbivore
// Create the link
if (createLink(draggedLinkCarnivore, creature)) {
linkCreated = true;
break;
}
}
}
}
// Clear highlights and reset drag state
clearLinkHighlights();
draggedLinkMarker = null;
draggedLinkCarnivore = null;
}
};
function processAT18Special() {
// Find all carnivore cards in discard pile
at18CarnivoreList = [];
for (var i = 0; i < discardPile.length; i++) {
//{at18_loop}
var cardData = discardPile[i];
if (cardData.cardType === 'creature' && cardData.dietType === 'carnivore') {
//{at18_check}
at18CarnivoreList.push(cardData);
} //{at18_found}
} //{at18_end_loop}
at18Active = true;
if (at18CarnivoreList.length === 0) {
// No carnivores in discard pile - show message
showAT18NoCarnivoredMessage();
} else {
//{at18_else}
// Display carnivores for selection
displayAT18CarnivoreSelection();
} //{at18_display_end}
} //{at18_process_end}
function showAT18NoCarnivoredMessage() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
//{at18_no_x}
anchorY: 0.5 //{at18_no_y}
}); //{at18_no_asset}
notificationBox.tint = 0x4169E1; // Blue background for info
notificationBox.alpha = 0.9;
notificationBox.scaleX = 2.5;
notificationBox.scaleY = 1.5;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No Carnivore cards in Discard pile - Special does not apply", {
size: 28,
//{at18_no_text_size}
fill: 0xFFFFFF //{at18_no_text_fill}
}); //{at18_no_text}
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable to dismiss
notificationBox.down = function () {
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
} //{at18_no_dismiss}
at18Active = false;
}; //{at18_no_down}
} //{at18_no_function_end}
function displayAT18CarnivoreSelection() {
// Create scry UI header
var scryHeader = new Text2("Pick 1 to add back onto the main deck", {
size: 32,
//{at18_header_size}
fill: 0xFFFFFF //{at18_header_fill}
}); //{at18_header_text}
scryHeader.anchor.set(0.5, 0);
scryHeader.x = 1024;
scryHeader.y = 300;
game.addChild(scryHeader);
scryHeader.at18Header = true;
// Display each carnivore card in a selectable list format
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < at18CarnivoreList.length; i++) {
//{at18_display_loop}
var cardData = at18CarnivoreList[i];
var cardContainer = new CreatureCard(cardData);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.at18Selectable = true;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var selectedIndex = this.cardDataIndex;
selectAT18Carnivore(selectedIndex);
}; //{at18_selectable_down}
game.addChild(cardContainer);
} //{at18_display_loop_end}
} //{at18_display_function_end}
function selectAT18Carnivore(cardIndex) {
if (cardIndex < 0 || cardIndex >= at18CarnivoreList.length) {
return;
}
// Get the selected carnivore card data
var selectedCardData = at18CarnivoreList[cardIndex];
// Remove from discard pile
for (var i = discardPile.length - 1; i >= 0; i--) {
//{at18_remove_loop}
if (discardPile[i] === selectedCardData || discardPile[i].id === selectedCardData.id && discardPile[i].cardType === selectedCardData.cardType && discardPile[i].dietType === selectedCardData.dietType) {
//{at18_remove_check}
discardPile.splice(i, 1);
break;
} //{at18_remove_end}
} //{at18_remove_loop_end}
// Add to top of main deck
mainDeck.push(selectedCardData);
// Clear all AT18 UI elements
clearAT18UI();
// Update deck count
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
at18Active = false;
} //{at18_select_function_end}
function clearAT18UI() {
// Remove all AT18 selectable cards from game
for (var i = game.children.length - 1; i >= 0; i--) {
//{at18_clear_loop}
var child = game.children[i];
if (child.at18Selectable) {
//{at18_clear_check}
game.removeChild(child);
} //{at18_clear_removed}
if (child.at18Header) {
//{at18_clear_header}
game.removeChild(child);
} //{at18_clear_header_removed}
} //{at18_clear_loop_end}
} //{at18_clear_function_end}
function activateScry() {
if (!scryTokenActive || scryMode || mainDeck.length < 3) {
return;
}
scryMode = true;
scryStep = 1;
scryCards = [];
// Pop 3 cards from mainDeck and create them
for (var i = 0; i < 3; i++) {
var cardData = mainDeck.pop();
var card = createCardFromData(cardData);
scryCards.push(card);
// Do NOT push to playerHand - only to scryCards
game.addChild(card);
}
// Position cards in center of screen side-by-side
var scryCardPositions = [512, 1024, 1536];
for (var i = 0; i < scryCards.length; i++) {
scryCards[i].x = scryCardPositions[i];
scryCards[i].y = 1000;
scryCards[i].scaleX = 1.5;
scryCards[i].scaleY = 1.5;
// Assign down function to each card
scryCards[i].down = function (card) {
return function (x, y, obj) {
processScrySelection(card);
};
}(scryCards[i]);
}
// Create header text
scryHeaderText = new Text2("SCRY: Select 1 card to put on the\nBOTTOM of the main deck", {
size: 56,
fill: 0xFFFF00,
align: 'center'
});
scryHeaderText.anchor.set(0.5, 0);
scryHeaderText.x = 1024;
scryHeaderText.y = 120;
game.addChild(scryHeaderText);
}
function processScrySelection(selectedCard) {
// Find the index of selectedCard in scryCards
var cardIndex = -1;
for (var i = 0; i < scryCards.length; i++) {
if (scryCards[i] === selectedCard) {
cardIndex = i;
break;
}
}
if (cardIndex === -1) {
return; // Card not found
}
// Extract clean copy of card data
var cardData = selectedCard.creatureData || selectedCard.terrainData || selectedCard.eventData;
var cleanCardData = {};
cleanCardData.type = cardData.type;
cleanCardData.level = cardData.level;
cleanCardData.cardType = cardData.cardType;
if (cardData.dietType) cleanCardData.dietType = cardData.dietType;
if (cardData.name) cleanCardData.name = cardData.name;
if (cardData.terrainRequirement) cleanCardData.terrainRequirement = cardData.terrainRequirement;
if (cardData.climateRequirement) cleanCardData.climateRequirement = cardData.climateRequirement;
if (cardData.subtype) cleanCardData.subtype = cardData.subtype;
if (cardData.landType) cleanCardData.landType = cardData.landType;
if (cardData.waterType) cleanCardData.waterType = cardData.waterType;
if (cardData.climate) cleanCardData.climate = cardData.climate;
if (cardData.id) cleanCardData.id = cardData.id;
if (cardData.colorBand) cleanCardData.colorBand = cardData.colorBand;
if (scryStep === 1) {
// Put card on BOTTOM of deck
mainDeck.unshift(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Update header text
scryHeaderText.setText("SCRY: Select 1 card to DISCARD.\nThe last card goes on TOP of the deck.");
scryStep = 2;
} else if (scryStep === 2) {
// Put card in discard pile
discardPile.push(cleanCardData);
// Remove from screen
if (selectedCard.parent) {
selectedCard.parent.removeChild(selectedCard);
}
// Remove from scryCards array
scryCards.splice(cardIndex, 1);
// Now process the last remaining card automatically
if (scryCards.length === 1) {
var lastCard = scryCards[0];
var lastCardData = lastCard.creatureData || lastCard.terrainData || lastCard.eventData;
var cleanLastCardData = {};
cleanLastCardData.type = lastCardData.type;
cleanLastCardData.level = lastCardData.level;
cleanLastCardData.cardType = lastCardData.cardType;
if (lastCardData.dietType) cleanLastCardData.dietType = lastCardData.dietType;
if (lastCardData.name) cleanLastCardData.name = lastCardData.name;
if (lastCardData.terrainRequirement) cleanLastCardData.terrainRequirement = lastCardData.terrainRequirement;
if (lastCardData.climateRequirement) cleanLastCardData.climateRequirement = lastCardData.climateRequirement;
if (lastCardData.subtype) cleanLastCardData.subtype = lastCardData.subtype;
if (lastCardData.landType) cleanLastCardData.landType = lastCardData.landType;
if (lastCardData.waterType) cleanLastCardData.waterType = lastCardData.waterType;
if (lastCardData.climate) cleanLastCardData.climate = lastCardData.climate;
if (lastCardData.id) cleanLastCardData.id = lastCardData.id;
if (lastCardData.colorBand) cleanLastCardData.colorBand = lastCardData.colorBand;
// Put on TOP of deck
mainDeck.push(cleanLastCardData);
// Remove from screen
if (lastCard.parent) {
lastCard.parent.removeChild(lastCard);
}
// Clear scryCards array
scryCards = [];
// Remove header text
if (scryHeaderText && scryHeaderText.parent) {
scryHeaderText.parent.removeChild(scryHeaderText);
scryHeaderText = null;
}
// Reset scry state
scryMode = false;
scryTokenActive = false;
scryStep = 0;
// Update UI
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
}
}
}
function showNoPlayableCardsNotification() {
// Create notification box
var notificationBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5
});
notificationBox.tint = 0x8B0000; // Dark red background
notificationBox.alpha = 0.9;
notificationBox.x = 1024;
notificationBox.y = 1366;
game.addChild(notificationBox);
// Create notification text
var notificationText = new Text2("No playable cards! Click to continue", {
size: 36,
fill: 0xFFFFFF
});
notificationText.anchor.set(0.5, 0.5);
notificationText.x = 0;
notificationText.y = 0;
notificationBox.addChild(notificationText);
// Make the box clickable
notificationBox.down = function () {
// Remove the notification
if (notificationBox.parent) {
notificationBox.parent.removeChild(notificationBox);
}
// Discard all cards in hand
discardCards(playerHand.slice());
// Start draw 1, discard 1 cycle
processDrawPhase();
};
// Store reference for cleanup
game.noPlayableNotification = notificationBox;
}
// Initialize terrain-based game
createBasicTerrainPool();
setupPlanet();
createInitialDeck();
createEventDecks();
// Shuffle event decks on game start
shuffleEventDeck(basicEventDeck);
shuffleEventDeck(advancedEventDeck);
// Game starts with turn 1, round 1, in Dawn phase
currentRound = 1;
turnNumber = 1;
gamePhase = 'dawn';
game.update = function () {
// Update UI elements
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
graveyardCountText.setText("Graveyard: " + graveyard.length);
eventDiscardCountText.setText("Event Discards: B:" + basicEventDiscard.length + " | A:" + advancedEventDiscard.length);
// Update scry token display
if (scryTokenActive) {
scryText.setText("Scry ✓");
scryText.tint = 0x00FF00; // Green when available
} else if (scryMode) {
scryText.setText("Scry (Active)");
scryText.tint = 0xFFFF00; // Yellow when in use
} else {
scryText.setText("Scry");
scryText.tint = 0x888888; // Gray when unavailable
}
// Update round and phase indicator
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
// Check if event cards have been removed from hand
hasEventCardsInHand = false;
for (var i = 0; i < playerHand.length; i++) {
//{lV_event1}
if (playerHand[i].eventData) {
hasEventCardsInHand = true;
break;
}
}
// Update next phase button appearance based on event cards
if (gamePhase === 'noon') {
//{lV_event2}
if (hasEventCardsInHand) {
nextPhaseButton.tint = 0xFF0000; // Red when events must be played
nextPhaseButtonText.setText("Play events 1st!");
} else {
nextPhaseButton.tint = 0x32CD32; // Green when normal phase progression
nextPhaseButtonText.setText("NEXT PHASE");
}
}
// Update link marker states during dusk phase
if (gamePhase === 'dusk') {
updateLinkMarkerStates();
}
// Update danger highlights for creatures at risk of extinction
updateDangerHighlights();
};
function resetCardLinks(carnivore) {
if (!carnivore || carnivore.creatureData.dietType !== 'carnivore') {
return;
}
// Remove all active links from this carnivore with fall-off animation
for (var i = carnivore.activeLinks.length - 1; i >= 0; i--) {
var link = carnivore.activeLinks[i];
animateLinkFallOff(carnivore, link.target);
}
// Reset all link markers to unlinked state
for (var j = 0; j < carnivore.linkMarkers.length; j++) {
var marker = carnivore.linkMarkers[j];
marker.isLinked = false;
marker.targetHerbivore = null;
marker.tint = 0xFF0000; // Red for unlinked
}
// Clear active links array
carnivore.activeLinks = [];
}
var simplifyActive = false;
var simplifyHeaderText = null;
function processSimplifySpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
if (targets.length > 0) {
simplifyActive = true;
simplifyHeaderText = new Text2("Select a terrain for devolution!", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
simplifyHeaderText.x = 1024;
simplifyHeaderText.y = 120;
game.addChild(simplifyHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0x0000FF;
highlight.alpha = 0.5;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isSimplifyHighlight = true;
highlight.down = function () {
if (!simplifyActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isSimplifyHighlight) game.removeChild(game.children[j]);
}
if (simplifyHeaderText && simplifyHeaderText.parent) simplifyHeaderText.parent.removeChild(simplifyHeaderText);
simplifyActive = false;
};
game.addChild(highlight);
}
}
}
var watcherActive = false;
var watcherList = [];
function processWatcherSpecial() {
watcherList = [];
for (var i = 0; i < discardPile.length; i++) {
if (discardPile[i].cardType === 'event') watcherList.push(discardPile[i]);
}
watcherActive = true;
if (watcherList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("No Event cards in Discard - Watcher does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
watcherActive = false;
};
} else {
var header = new Text2("Choose an event to return to the event deck", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.watcherHeader = true;
game.addChild(header);
for (var i = 0; i < watcherList.length; i++) {
var cardContainer = createCardFromData(watcherList[i]);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = 500 + Math.floor(i / 2) * 280;
cardContainer.cardDataIndex = i;
cardContainer.watcherSelectable = true;
cardContainer.down = function () {
var selectedData = watcherList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].name === selectedData.name) {
discardPile.splice(d, 1);
break;
}
}
if (selectedData.level === 'Advanced') advancedEventDeck.push(selectedData);else basicEventDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.watcherSelectable || c.watcherHeader) game.removeChild(c);
}
basicEventCountText.setText("Basic Events: " + basicEventDeck.length);
advancedEventCountText.setText("Advanced Events: " + advancedEventDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
watcherActive = false;
};
game.addChild(cardContainer);
}
}
}
var forerunnerActive = false;
var forerunnerList = [];
function processForerunnerSpecial() {
forerunnerList = discardPile.slice(); // Copy entire discard pile
forerunnerActive = true;
if (forerunnerList.length === 0) {
var notifyBox = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
tint: 0x4169E1,
alpha: 0.9,
x: 1024,
y: 1366
});
var notifyText = new Text2("Discard pile is empty - Forerunner does not apply", {
size: 28,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
notifyBox.addChild(notifyText);
game.addChild(notifyBox);
notifyBox.down = function () {
if (notifyBox.parent) notifyBox.parent.removeChild(notifyBox);
forerunnerActive = false;
};
} else {
var header = new Text2("Choose a card to place on top of the main deck,\nall others will be returned to the discard pile", {
size: 48,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
header.forerunnerHeader = true;
game.addChild(header);
var cardYOffset = 500;
var cardYSpacing = 280;
for (var i = 0; i < forerunnerList.length; i++) {
var cardContainer = createCardFromData(forerunnerList[i]);
cardContainer.scaleX = 0.6;
cardContainer.scaleY = 0.6;
cardContainer.x = 512 + i % 2 * 600;
cardContainer.y = cardYOffset + Math.floor(i / 2) * cardYSpacing;
cardContainer.cardDataIndex = i;
cardContainer.forerunnerSelectable = true;
cardContainer.down = function () {
var selectedData = forerunnerList[this.cardDataIndex];
for (var d = discardPile.length - 1; d >= 0; d--) {
if (discardPile[d] === selectedData || discardPile[d].id === selectedData.id) {
discardPile.splice(d, 1);
break;
}
}
mainDeck.push(selectedData);
for (var k = game.children.length - 1; k >= 0; k--) {
var c = game.children[k];
if (c.forerunnerSelectable || c.forerunnerHeader) game.removeChild(c);
}
deckCountText.setText("Deck: " + mainDeck.length);
discardCountText.setText("Discard: " + discardPile.length);
forerunnerActive = false;
};
game.addChild(cardContainer);
}
}
}
var devolutionActive = false;
var devolutionHeaderText = null;
function processDevolutionEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack && terrain.creatureStack.length > 0) {
var topC = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topC.creatureData.dietType === 'carnivore' && topC.creatureData.level === 'Advanced') targets.push(topC);
}
}
}
if (targets.length === 0) {
onComplete(); // No targets, event fizzles instantly
} else {
devolutionActive = true;
devolutionHeaderText = new Text2("Select an advanced carnivore to go back in time!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
devolutionHeaderText.x = 1024;
devolutionHeaderText.y = 120;
game.addChild(devolutionHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6; // Light purple
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDevolutionHighlight = true;
highlight.down = function () {
if (!devolutionActive) return;
var tC = this.targetCreature;
for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC);
var stack = planetBoard[tC.gridY][tC.gridX].creatureStack;
var idx = stack.indexOf(tC);
if (idx !== -1) stack.splice(idx, 1);
discardPile.push(tC.creatureData);
discardCountText.setText("Discard: " + discardPile.length);
if (tC.parent) tC.parent.removeChild(tC);
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDevolutionHighlight) game.removeChild(game.children[j]);
}
if (devolutionHeaderText && devolutionHeaderText.parent) devolutionHeaderText.parent.removeChild(devolutionHeaderText);
devolutionActive = false;
onComplete(); // Finish event resolution
};
game.addChild(highlight);
}
}
}
var terraformerActive = false;
var terraformerHeaderText = null;
function processTerraformerSpecial(originX, originY) {
var targets = [];
var neighbors = getAdjacentTerrains(originX, originY, true);
var targetLevel = 'Advanced';
// First pass: Check for empty Advanced terrains
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Advanced' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
// Second pass: If no Advanced terrains found, check for empty Basic terrains
if (targets.length === 0) {
targetLevel = 'Basic';
for (var i = 0; i < neighbors.length; i++) {
var adj = neighbors[i];
if (adj && adj.terrainData && adj.terrainData.level === 'Basic' && (!adj.creatureStack || adj.creatureStack.length === 0)) {
targets.push(adj);
}
}
}
if (targets.length > 0) {
terraformerActive = true;
terraformerHeaderText = new Text2("Select 1 terrain card to be removed,\nthe basic terrain will then shift", {
size: 56,
fill: 0xFFFF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
terraformerHeaderText.x = 1024;
terraformerHeaderText.y = 120;
game.addChild(terraformerHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xFFFF00;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.1;
highlight.scaleY = target.scaleY * 1.1;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isTerraformerHighlight = true;
highlight.targetLevel = targetLevel;
highlight.down = function () {
if (!terraformerActive) return;
var tTerrain = this.targetTerrain;
var tX = tTerrain.gridX;
var tY = tTerrain.gridY;
var basicUnder = tTerrain.basicTerrainUnderneath;
// 1. If Advanced, discard the Advanced card
if (this.targetLevel === 'Advanced') {
discardPile.push(tTerrain.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
}
// 2. Grab a fresh basic terrain from the unused pool if available
var targetBasicToSwap = this.targetLevel === 'Advanced' ? basicUnder : tTerrain;
if (basicTerrainPool.length > planetLayout.reduce(function (a, b) {
return a + b;
}, 0)) {
// Pop a truly unused card from the pool
var newBasicData = basicTerrainPool.pop();
// Inherit the climate of the grid slot
newBasicData.climate = targetBasicToSwap.terrainData.climate;
var newBasicCard = new TerrainCard(newBasicData);
newBasicCard.gridX = tX;
newBasicCard.gridY = tY;
newBasicCard.isInPlay = true;
newBasicCard.x = targetBasicToSwap.x;
newBasicCard.y = targetBasicToSwap.y;
newBasicCard.scaleX = targetBasicToSwap.scaleX;
newBasicCard.scaleY = targetBasicToSwap.scaleY;
planetBoard[tY][tX] = newBasicCard;
planetSlots[tY][tX].terrainCard = newBasicCard;
var basicIndex = game.getChildIndex(targetBasicToSwap);
if (targetBasicToSwap.parent) targetBasicToSwap.parent.removeChild(targetBasicToSwap);
game.addChildAt(newBasicCard, basicIndex);
} else {
// If pool is empty, just revert Advanced to Basic without swapping
if (this.targetLevel === 'Advanced') {
planetBoard[tY][tX] = basicUnder;
planetSlots[tY][tX].terrainCard = basicUnder;
}
}
// Cleanup UI
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isTerraformerHighlight) game.removeChild(game.children[j]);
}
if (terraformerHeaderText && terraformerHeaderText.parent) terraformerHeaderText.parent.removeChild(terraformerHeaderText);
terraformerActive = false;
};
game.addChild(highlight);
}
}
}
;
var diseaseActive = false;
var diseaseHeaderText = null;
function processDiseaseEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
if (terrain.creatureStack[i].creatureData.level === 'Basic') targets.push(terrain.creatureStack[i]);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
}
diseaseActive = true;
var picks = 0;
diseaseHeaderText = new Text2("Select a creature to become sick!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
diseaseHeaderText.x = 1024;
diseaseHeaderText.y = 120;
game.addChild(diseaseHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isDiseaseHighlight = true;
highlight.down = function () {
if (!diseaseActive) return;
var tC = this.targetCreature;
tC.extinctionMarkers += 1;
tC.updateExtinctionMarkers();
picks++;
if (picks === 1) {
diseaseHeaderText.setText("And now another sickens....");
} else if (picks === 2) {
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isDiseaseHighlight) game.removeChild(game.children[j]);
}
if (diseaseHeaderText && diseaseHeaderText.parent) diseaseHeaderText.parent.removeChild(diseaseHeaderText);
diseaseActive = false;
onComplete();
}
};
game.addChild(highlight);
}
}
var volcanoActive = false;
var volcanoHeaderText = null;
function processVolcanoEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
// Priority: Only mountains with creatures, OR any mountain if none have creatures
if (terrain.creatureStack && terrain.creatureStack.length > 0) {
targets.push(terrain);
}
}
}
}
// If no occupied mountains exist, grab all empty mountains
if (targets.length === 0) {
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'mountain') {
targets.push(terrain);
}
}
}
}
if (targets.length === 0) {
onComplete();
return;
} // No mountains at all
volcanoActive = true;
volcanoHeaderText = new Text2("Select a Mountain to become a raging volcano!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
volcanoHeaderText.x = 1024;
volcanoHeaderText.y = 120;
game.addChild(volcanoHeaderText);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isVolcanoHighlight = true;
highlight.down = function () {
if (!volcanoActive) return;
var selectedMountain = this.targetTerrain;
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isVolcanoHighlight) game.removeChild(game.children[j]);
}
// 1. Transform into Volcano Terrain instantly
var volcanoData = {
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'land',
landType: 'mountain',
id: 'VOLCANO',
colorBand: 'hot',
climateRequirement: 'any',
name: 'Volcano',
climate: 'hot',
special: true
};
var volcanoCard = new TerrainCard(volcanoData);
var mX = selectedMountain.gridX;
var mY = selectedMountain.gridY;
volcanoCard.creatureStack = selectedMountain.creatureStack || [];
selectedMountain.terrainData.climate = 'hot';
volcanoCard.gridX = mX;
volcanoCard.gridY = mY;
volcanoCard.isInPlay = true;
volcanoCard.x = selectedMountain.x;
volcanoCard.y = selectedMountain.y;
volcanoCard.scaleX = selectedMountain.scaleX;
volcanoCard.scaleY = selectedMountain.scaleY;
planetBoard[mY][mX] = volcanoCard;
planetSlots[mY][mX].terrainCard = volcanoCard;
volcanoCard.basicTerrainUnderneath = selectedMountain.basicTerrainUnderneath || selectedMountain;
var mIndex = game.getChildIndex(selectedMountain);
game.addChildAt(volcanoCard, mIndex + 1);
for (var i = 0; i < volcanoCard.creatureStack.length; i++) {
var c = volcanoCard.creatureStack[i];
game.removeChild(c);
game.addChild(c);
}
for (var l = 0; l < linkLines.length; l++) {
game.removeChild(linkLines[l]);
game.addChild(linkLines[l]);
}
// 2. Start fleeing checks
processVolcanoFlee(volcanoCard, onComplete);
};
game.addChild(highlight);
}
}
function processVolcanoFlee(mountain, onComplete) {
var finishVolcano = function finishVolcano() {
if (volcanoHeaderText && volcanoHeaderText.parent) volcanoHeaderText.parent.removeChild(volcanoHeaderText);
volcanoActive = false;
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
onComplete();
};
if (!mountain.creatureStack || mountain.creatureStack.length === 0) {
finishVolcano();
return;
}
var topC = mountain.creatureStack[mountain.creatureStack.length - 1];
if (topC.creatureData.flying || topC.creatureData.swimming || topC.creatureData.fly || topC.creatureData.swim) {
volcanoHeaderText.setText("Lucky creatures!");
setTimeout(function () {
finishVolcano();
}, 1500);
return;
}
volcanoHeaderText.setText("Unlucky creature - Flee to survive!");
setTimeout(function () {
var validEscapes = [];
var neighbors = getAdjacentTerrains(mountain.gridX, mountain.gridY, true);
mountain.creatureStack.pop();
for (var n = 0; n < neighbors.length; n++) {
if (neighbors[n] && canPlaceCreatureOnTerrain(topC, neighbors[n])) validEscapes.push(neighbors[n]);
}
mountain.creatureStack.push(topC);
if (validEscapes.length === 0) {
volcanoHeaderText.setText("This creature cannot escape and will be destroyed!\nClick it to say goodbye");
var doomHlt = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
doomHlt.tint = 0xFF0000;
doomHlt.alpha = 0.7;
doomHlt.scaleX = topC.scaleX * 1.15;
doomHlt.scaleY = topC.scaleY * 1.15;
doomHlt.x = topC.x;
doomHlt.y = topC.y;
doomHlt.down = function () {
if (doomHlt.parent) doomHlt.parent.removeChild(doomHlt);
var stack = mountain.creatureStack;
stack.pop();
graveyard.push(topC.creatureData);
scryDeniedNextRound = true;
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
if (topC.parent) topC.parent.removeChild(topC);
processVolcanoFlee(mountain, onComplete);
};
game.addChild(doomHlt);
} else {
volcanoHeaderText.setText("Choose a new home!");
var fleeHighlights = [];
for (var e = 0; e < validEscapes.length; e++) {
var esc = validEscapes[e];
var hlt = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
hlt.tint = 0xFFFF00;
hlt.alpha = 0.6;
hlt.scaleX = esc.scaleX * 1.15;
hlt.scaleY = esc.scaleY * 1.15;
hlt.x = esc.x;
hlt.y = esc.y;
hlt.targetEscape = esc;
hlt.isFleeHighlight = true;
hlt.down = function () {
var chosenEsc = this.targetEscape;
for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]);
var movingC = mountain.creatureStack.pop();
placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY);
setTimeout(function () {
processVolcanoFlee(mountain, onComplete);
}, 400);
};
game.addChild(hlt);
fleeHighlights.push(hlt);
}
}
}, 1500);
}
function processRisingSeasEvent(onComplete) {
var targets = [];
var emptyTargets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.terrainData && terrain.terrainData.landType === 'flat') {
if (terrain.creatureStack && terrain.creatureStack.length > 0) targets.push(terrain);else emptyTargets.push(terrain);
}
}
}
var finalTargets = targets.length > 0 ? targets : emptyTargets;
if (finalTargets.length === 0) {
var msg = new Text2("Event has no effect.", {
size: 56,
fill: 0xFFFFFF,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
msg.x = 1024;
msg.y = 120;
game.addChild(msg);
setTimeout(function () {
if (msg.parent) msg.parent.removeChild(msg);
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
}, 1500);
return;
}
risingSeasActive = true;
risingSeasHeaderText = new Text2("Choose a land to raise the sea level!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
risingSeasHeaderText.x = 1024;
risingSeasHeaderText.y = 120;
game.addChild(risingSeasHeaderText);
for (var t = 0; t < finalTargets.length; t++) {
var target = finalTargets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isRisingSeasHighlight = true;
highlight.down = function () {
if (!risingSeasActive) return;
var selectedFlat = this.targetTerrain;
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isRisingSeasHighlight) game.removeChild(game.children[j]);
}
// Advanced Terrain replacement logic
if (selectedFlat.terrainData.level === 'Advanced') {
discardPile.push(selectedFlat.terrainData);
discardCountText.setText("Discard: " + discardPile.length);
}
var seaData = {
type: 'advanced',
level: 'Advanced',
cardType: 'terrain',
subtype: 'water',
waterType: 'sea',
id: 'BE05_TERRAIN',
colorBand: 'darkblue',
climateRequirement: 'any',
name: 'Rising Seas',
special: true
};
var seaCard = new TerrainCard(seaData);
var mX = selectedFlat.gridX;
var mY = selectedFlat.gridY;
seaCard.creatureStack = selectedFlat.creatureStack || [];
seaCard.gridX = mX;
seaCard.gridY = mY;
seaCard.isInPlay = true;
seaCard.x = selectedFlat.x;
seaCard.y = selectedFlat.y;
seaCard.scaleX = selectedFlat.scaleX;
seaCard.scaleY = selectedFlat.scaleY;
planetBoard[mY][mX] = seaCard;
planetSlots[mY][mX].terrainCard = seaCard;
seaCard.basicTerrainUnderneath = selectedFlat.terrainData.level === 'Advanced' ? selectedFlat.basicTerrainUnderneath : selectedFlat;
var mIndex = game.getChildIndex(selectedFlat);
if (selectedFlat.terrainData.level === 'Advanced' && selectedFlat.parent) selectedFlat.parent.removeChild(selectedFlat);
game.addChildAt(seaCard, mIndex + 1);
for (var i = 0; i < seaCard.creatureStack.length; i++) {
var c = seaCard.creatureStack[i];
game.removeChild(c);
game.addChild(c);
}
for (var l = 0; l < linkLines.length; l++) {
game.removeChild(linkLines[l]);
game.addChild(linkLines[l]);
}
processRisingSeasFlee(seaCard);
};
game.addChild(highlight);
}
}
function processRisingSeasFlee(seaTerrain) {
var finishSeas = function finishSeas() {
if (risingSeasHeaderText && risingSeasHeaderText.parent) risingSeasHeaderText.parent.removeChild(risingSeasHeaderText);
risingSeasActive = false;
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
};
if (!seaTerrain.creatureStack || seaTerrain.creatureStack.length === 0) {
finishSeas();
return;
}
var topC = seaTerrain.creatureStack[seaTerrain.creatureStack.length - 1];
if (topC.creatureData.flying || topC.creatureData.swimming || topC.creatureData.fly || topC.creatureData.swim) {
risingSeasHeaderText.setText("The creatures are happy here!");
setTimeout(function () {
finishSeas();
}, 1500);
return;
}
risingSeasHeaderText.setText("This creature is in danger and must relocate!");
setTimeout(function () {
var validEscapes = [];
var neighbors = getAdjacentTerrains(seaTerrain.gridX, seaTerrain.gridY, true);
seaTerrain.creatureStack.pop();
for (var n = 0; n < neighbors.length; n++) {
if (neighbors[n] && canPlaceCreatureOnTerrain(topC, neighbors[n])) validEscapes.push(neighbors[n]);
}
seaTerrain.creatureStack.push(topC);
if (validEscapes.length === 0) {
risingSeasHeaderText.setText("The creature could not relocate and has died...");
setTimeout(function () {
var stack = seaTerrain.creatureStack;
stack.pop();
graveyard.push(topC.creatureData);
scryDeniedNextRound = true;
for (var j = topC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(topC, topC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === topC) animateLinkFallOff(linkLines[j].carnivore, topC);
if (topC.parent) topC.parent.removeChild(topC);
processRisingSeasFlee(seaTerrain);
}, 2000);
} else {
risingSeasHeaderText.setText("Relocate the creature quick!");
var fleeHighlights = [];
for (var e = 0; e < validEscapes.length; e++) {
var esc = validEscapes[e];
var hlt = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
hlt.tint = 0xFFFF00;
hlt.alpha = 0.6;
hlt.scaleX = esc.scaleX * 1.15;
hlt.scaleY = esc.scaleY * 1.15;
hlt.x = esc.x;
hlt.y = esc.y;
hlt.targetEscape = esc;
hlt.isFleeHighlight = true;
hlt.down = function () {
var chosenEsc = this.targetEscape;
for (var k = game.children.length - 1; k >= 0; k--) if (game.children[k].isFleeHighlight) game.removeChild(game.children[k]);
var movingC = seaTerrain.creatureStack.pop();
placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY);
setTimeout(function () {
processRisingSeasFlee(seaTerrain);
}, 400);
};
game.addChild(hlt);
fleeHighlights.push(hlt);
}
}
}, 1500);
}
var risingSeasActive = false;
var risingSeasHeaderText = null;
;
var virusActive = false;
function processVirusEvent(onComplete) {
var targets = [];
var backupTargets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var t = planetBoard[y][x];
if (t && t.creatureStack && t.creatureStack.length > 0) {
var c = t.creatureStack[t.creatureStack.length - 1];
backupTargets.push(c);
if (c.creatureData.level === 'Advanced' && c.creatureData.dietType === 'herbivore') {
targets.push(c);
}
}
}
}
var finalTargets = targets.length > 0 ? targets : backupTargets;
if (finalTargets.length === 0) {
onComplete();
return;
}
virusActive = true;
var header = new Text2("Choose a creature to be the host...", {
size: 56,
fill: 0x39FF14,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
game.addChild(header);
for (var i = 0; i < finalTargets.length; i++) {
var target = finalTargets[i];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0x39FF14;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isVirusHighlight = true;
highlight.down = function () {
if (!virusActive) return;
this.targetCreature.virusMarkers += 1;
this.targetCreature.updateVirusMarkers();
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isVirusHighlight) game.removeChild(game.children[j]);
}
header.setText("Lets hope it's not too bad...\ntry to prevent links to or from that creature,\nclick anywhere to continue");
var clickCatcher = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 100,
scaleY: 100
});
clickCatcher.alpha = 0.01;
clickCatcher.x = 1024;
clickCatcher.y = 1366;
game.addChild(clickCatcher);
clickCatcher.down = function () {
if (header.parent) header.parent.removeChild(header);
if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
virusActive = false;
onComplete();
};
};
game.addChild(highlight);
}
}
var smiteActive = false;
function processSmiteEvent(onComplete) {
smiteActive = true;
var header = new Text2("Select a place to utterly destroy!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
game.addChild(header);
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var target = planetBoard[y][x];
if (target) {
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xFF0000;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isSmiteHighlight = true;
highlight.down = function () {
if (!smiteActive) return;
var tTerrain = this.targetTerrain;
// Kill all creatures WITHOUT triggering new events
while (tTerrain.creatureStack.length > 0) {
var c = tTerrain.creatureStack.pop();
for (var j = c.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(c, c.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === c) animateLinkFallOff(linkLines[j].carnivore, c);
graveyard.push(c.creatureData);
scryDeniedNextRound = true;
if (c.parent) c.parent.removeChild(c);
}
// Destroy all terrain on this spot (Advanced and Basic)
if (tTerrain.basicTerrainUnderneath && tTerrain.basicTerrainUnderneath.parent) tTerrain.basicTerrainUnderneath.parent.removeChild(tTerrain.basicTerrainUnderneath);
if (tTerrain.parent) tTerrain.parent.removeChild(tTerrain);
// Wipe the board data clean so it can be rebuilt
planetBoard[tTerrain.gridY][tTerrain.gridX] = null;
planetSlots[tTerrain.gridY][tTerrain.gridX].terrainCard = null;
graveyardCountText.setText("Graveyard: " + graveyard.length);
for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isSmiteHighlight) game.removeChild(game.children[j]);
if (header.parent) header.parent.removeChild(header);
smiteActive = false;
onComplete();
};
game.addChild(highlight);
}
}
}
}
var harshEvolutionActive = false;
function processHarshEvolutionEvent(onComplete) {
var targets = [];
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain && terrain.creatureStack) {
for (var i = 0; i < terrain.creatureStack.length; i++) {
var c = terrain.creatureStack[i];
if (c.creatureData.dietType === 'herbivore' && c.creatureData.level === 'Advanced') targets.push(c);
}
}
}
}
if (targets.length === 0) {
var msg = new Text2("No creatures to target", {
size: 56,
fill: 0xFFFFFF,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
msg.x = 1024;
msg.y = 120;
game.addChild(msg);
setTimeout(function () {
if (msg.parent) msg.parent.removeChild(msg);
onComplete();
}, 1500);
return;
}
harshEvolutionActive = true;
var header = new Text2("Select an advanced creature to bite the dust!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
game.addChild(header);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xDDA0DD;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetCreature = target;
highlight.isHarshHighlight = true;
highlight.down = function () {
if (!harshEvolutionActive) return;
var tC = this.targetCreature;
// Remove creature from stack and trigger normal death (which spawns the AE)
var stack = planetBoard[tC.gridY][tC.gridX].creatureStack;
stack.splice(stack.indexOf(tC), 1);
for (var j = tC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(tC, tC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === tC) animateLinkFallOff(linkLines[j].carnivore, tC);
tC.die();
graveyardCountText.setText("Graveyard: " + graveyard.length);
for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isHarshHighlight) game.removeChild(game.children[j]);
if (header.parent) header.parent.removeChild(header);
harshEvolutionActive = false;
onComplete();
};
game.addChild(highlight);
}
}
var meteorActive = false;
function processMeteorEvent(onComplete) {
var targets = [];
// Find all terrains with 4 orthogonal neighbors
for (var y = 0; y < planetHeight; y++) {
for (var x = 0; x < planetWidth; x++) {
var terrain = planetBoard[y][x];
if (terrain) {
var neighbors = getAdjacentTerrains(x, y, false);
if (neighbors.length === 4) targets.push(terrain);
}
}
}
if (targets.length === 0) {
var msg = new Text2("No suitable impact zones", {
size: 56,
fill: 0xFFFFFF,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
msg.x = 1024;
msg.y = 120;
game.addChild(msg);
setTimeout(function () {
if (msg.parent) msg.parent.removeChild(msg);
onComplete();
}, 1500);
return;
}
meteorActive = true;
var header = new Text2("Choose a terrain for the meteor to strike!", {
size: 56,
fill: 0xFF0000,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 120;
game.addChild(header);
for (var t = 0; t < targets.length; t++) {
var target = targets[t];
var highlight = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xFF4500;
highlight.alpha = 0.6;
highlight.scaleX = target.scaleX * 1.15;
highlight.scaleY = target.scaleY * 1.15;
highlight.x = target.x;
highlight.y = target.y;
highlight.targetTerrain = target;
highlight.isMeteorHighlight = true;
highlight.down = function () {
if (!meteorActive) return;
var epicenter = this.targetTerrain;
for (var j = game.children.length - 1; j >= 0; j--) {
if (game.children[j].isMeteorHighlight) game.removeChild(game.children[j]);
}
header.setText("The central impact hits for 2 extinction markers\nclick anywhere to confirm");
// Create massive invisible button to intercept clicks anywhere on screen
var clickCatcher = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 100,
scaleY: 100
});
clickCatcher.alpha = 0.01;
clickCatcher.x = 1024;
clickCatcher.y = 1366;
game.addChild(clickCatcher);
clickCatcher.down = function () {
// Phase 1: Apply 2 markers to Epicenter
if (epicenter.creatureStack && epicenter.creatureStack.length > 0) {
var topC = epicenter.creatureStack[epicenter.creatureStack.length - 1];
topC.extinctionMarkers += 2;
topC.updateExtinctionMarkers();
}
header.setText("Now the blast wave hits all around\nclick anywhere to confirm");
// Advance click catcher to Phase 2
clickCatcher.down = function () {
// Phase 2: Apply 1 marker to orthogonal neighbors
var blastZone = getAdjacentTerrains(epicenter.gridX, epicenter.gridY, false);
for (var b = 0; b < blastZone.length; b++) {
var bT = blastZone[b];
if (bT && bT.creatureStack && bT.creatureStack.length > 0) {
var bC = bT.creatureStack[bT.creatureStack.length - 1];
bC.extinctionMarkers += 1;
bC.updateExtinctionMarkers();
}
}
// Cleanup and finish
if (header.parent) header.parent.removeChild(header);
if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
meteorActive = false;
onComplete();
};
};
};
game.addChild(highlight);
}
} ===================================================================
--- original.js
+++ change.js
@@ -90,8 +90,10 @@
self.isInPlay = false;
self.linkMarkers = [];
self.activeLinks = [];
self.extinctionMarkers = 0;
+ self.virusMarkers = 0;
+ self.virusVisuals = [];
self.linkRequirement = 0;
self.safeLinks = 0;
// Set link requirements and safe link levels based on creature type
if (self.creatureData.dietType === 'carnivore') {
@@ -193,10 +195,40 @@
self.addChild(marker);
self.extinctionMarkerVisuals.push(marker);
}
};
+ self.updateVirusMarkers = function () {
+ for (var i = 0; i < self.virusVisuals.length; i++) {
+ if (self.virusVisuals[i].parent) {
+ self.virusVisuals[i].parent.removeChild(self.virusVisuals[i]);
+ }
+ }
+ self.virusVisuals = [];
+ for (var i = 0; i < self.virusMarkers; i++) {
+ var vMark = LK.getAsset('terrainLandFlat', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.12,
+ scaleY: 0.12
+ });
+ vMark.tint = 0x39FF14; // Neon Green
+ vMark.x = -40 + i * 20;
+ vMark.y = -80;
+ var vText = new Text2("V", {
+ size: 60,
+ fill: 0x000000,
+ font: "'Arial Black', 'Impact', sans-serif"
+ });
+ vText.anchor.set(0.5, 0.5);
+ vMark.addChild(vText);
+ self.addChild(vMark);
+ self.virusVisuals.push(vMark);
+ }
+ };
self.die = function () {
if (self.isInPlay) {
+ self.virusMarkers = 0;
+ self.updateVirusMarkers();
// Grateful interceptor
if (self.creatureData.grateful) {
// Grateful bypasses the graveyard, events, and scry penalty
bonusPile.push(self.creatureData);
@@ -696,8 +728,10 @@
} else if (self.eventData.id === 'AE02') {
processHarshEvolutionEvent(finalizeEvent);
} else if (self.eventData.id === 'AE03') {
processMeteorEvent(finalizeEvent);
+ } else if (self.eventData.id === 'AE04') {
+ processVirusEvent(finalizeEvent);
} else {
finalizeEvent(); // Fallback for unimplemented events
}
}
@@ -972,15 +1006,15 @@
/****
* Game Code
****/
-// Zoom container for magnified card view
-// Terrain card assets - Land types with green strip
-// Terrain card assets - Water types with blue/grey strip
-// Climate indicator strips
-// Creature card assets
-// Event card asset
// UI elements
+// Event card asset
+// Creature card assets
+// Climate indicator strips
+// Terrain card assets - Water types with blue/grey strip
+// Terrain card assets - Land types with green strip
+// Zoom container for magnified card view
var zoomContainer = new Container();
zoomContainer.x = 0;
zoomContainer.y = 0;
LK.gui.center.addChild(zoomContainer);
@@ -2327,8 +2361,9 @@
if (evt.id === 'BE05') infoString += " | Rising Seas: This event changes a flat land into an advanced sea terrain card, and all the creatures there that don't swim or fly, must move or die.";
if (evt.id === 'AE01') infoString += " | Smite! Destroys a stack completely! All creatures there are killed, all land there are removed from play, nothing remains!";
if (evt.id === 'AE02') infoString += " | Harsh Evolution! One of your advanced herbivores will be destroyed!";
if (evt.id === 'AE03') infoString += " | Meteor! 1 terrain will be hit by a powerful AOE damage effect spreading extinction markers far and wide!";
+ if (evt.id === 'AE04') infoString += " | Virus! A creature breaks out in a deadly illness that spreads via links to or from it!";
} else if (card.terrainData) {
// Terrain card information
var terrain = card.terrainData;
if (terrain) {
@@ -4142,9 +4177,17 @@
id: 'AE03',
name: 'Meteor',
effect: 'catastrophic'
});
- for (var i = 4; i <= 5; i++) {
+ advancedEventDeck.push({
+ type: 'advanced',
+ cardType: 'event',
+ level: 'Advanced',
+ id: 'AE04',
+ name: 'Virus',
+ effect: 'catastrophic'
+ });
+ for (var i = 5; i <= 5; i++) {
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
@@ -6111,8 +6154,82 @@
}
var risingSeasActive = false;
var risingSeasHeaderText = null;
;
+var virusActive = false;
+function processVirusEvent(onComplete) {
+ var targets = [];
+ var backupTargets = [];
+ for (var y = 0; y < planetHeight; y++) {
+ for (var x = 0; x < planetWidth; x++) {
+ var t = planetBoard[y][x];
+ if (t && t.creatureStack && t.creatureStack.length > 0) {
+ var c = t.creatureStack[t.creatureStack.length - 1];
+ backupTargets.push(c);
+ if (c.creatureData.level === 'Advanced' && c.creatureData.dietType === 'herbivore') {
+ targets.push(c);
+ }
+ }
+ }
+ }
+ var finalTargets = targets.length > 0 ? targets : backupTargets;
+ if (finalTargets.length === 0) {
+ onComplete();
+ return;
+ }
+ virusActive = true;
+ var header = new Text2("Choose a creature to be the host...", {
+ size: 56,
+ fill: 0x39FF14,
+ align: 'center',
+ anchorX: 0.5,
+ anchorY: 0
+ });
+ header.x = 1024;
+ header.y = 120;
+ game.addChild(header);
+ for (var i = 0; i < finalTargets.length; i++) {
+ var target = finalTargets[i];
+ var highlight = LK.getAsset('terrainLandFlat', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ highlight.tint = 0x39FF14;
+ highlight.alpha = 0.6;
+ highlight.scaleX = target.scaleX * 1.15;
+ highlight.scaleY = target.scaleY * 1.15;
+ highlight.x = target.x;
+ highlight.y = target.y;
+ highlight.targetCreature = target;
+ highlight.isVirusHighlight = true;
+ highlight.down = function () {
+ if (!virusActive) return;
+ this.targetCreature.virusMarkers += 1;
+ this.targetCreature.updateVirusMarkers();
+ for (var j = game.children.length - 1; j >= 0; j--) {
+ if (game.children[j].isVirusHighlight) game.removeChild(game.children[j]);
+ }
+ header.setText("Lets hope it's not too bad...\ntry to prevent links to or from that creature,\nclick anywhere to continue");
+ var clickCatcher = LK.getAsset('terrainLandFlat', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 100,
+ scaleY: 100
+ });
+ clickCatcher.alpha = 0.01;
+ clickCatcher.x = 1024;
+ clickCatcher.y = 1366;
+ game.addChild(clickCatcher);
+ clickCatcher.down = function () {
+ if (header.parent) header.parent.removeChild(header);
+ if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
+ virusActive = false;
+ onComplete();
+ };
+ };
+ game.addChild(highlight);
+ }
+}
var smiteActive = false;
function processSmiteEvent(onComplete) {
smiteActive = true;
var header = new Text2("Select a place to utterly destroy!", {