User prompt
Position the text , (hold for 2 seconds) lower by 1.5 lines
User prompt
Position the words: ‘Next’ and ‘Phase’ in the next phase button on separate lines, 1 above the other
User prompt
Double the size of the text in the ‘Next phase’ button last placed
User prompt
Please add text ‘(hold for 2 seconds)’ to the ‘Next phase’ button, under the words ‘Next phase’
User prompt
Make the game draw an initial hand of 3 cards after board setup and then generate a timed (3 second) message in the middle of the UI that says ‘Here is your 1st hand- play 1! Good luck!’ ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add sound effects and background music
User prompt
BYPASS PHASE CHANGES DURING FLEEING: - Locate `function placeCreatureOnStack(creatureCard, terrainCard, gridX, gridY)`. - Scroll near the bottom of the function and find this exact code: ```javascript // 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) { ``` - REPLACE it with: ```javascript // Reset draw phase after successful placement if (creatureCard.mustPlay) { drawPhase = 'complete'; } if (game.isFleeingEvent) { LK.getSound('cardPlace').play(); return true; } // Check if this was the last event card or must play card if (eventCardsToPlay.length > 0) { ``` 4. INJECT FLEEING FLAG INTO EVENTS: - Locate `processVolcanoFlee`. Inside the `hlt.down = function () {` block, find this exact code: ```javascript var movingC = mountain.creatureStack.pop(); placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); LK.setTimeout(function () { ``` - REPLACE it with: ```javascript var movingC = mountain.creatureStack.pop(); game.isFleeingEvent = true; placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); game.isFleeingEvent = false; LK.setTimeout(function () { ``` - Locate `processRisingSeasFlee`. Inside its `hlt.down = function () {` block, find this exact code: ```javascript var movingC = seaTerrain.creatureStack.pop(); placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); LK.setTimeout(function () { ``` - REPLACE it with: ```javascript var movingC = seaTerrain.creatureStack.pop(); game.isFleeingEvent = true; placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY); game.isFleeingEvent = false; LK.setTimeout(function () { ``` 5. REPLACE DANGER HIGHLIGHTS WITH MEMORY-SAFE VECTOR BORDER: - Locate `function updateDangerHighlights() {`. - Replace the ENTIRE function from top to bottom with this exact code: ```javascript function updateDangerHighlights() { var inDangerList = []; if (gamePhase === 'noon' || gamePhase === 'dusk') { inDangerList = getCreaturesInDanger(); } // 1. Sweep old root-level halos that were causing the memory leak for (var j = game.children.length - 1; j >= 0; j--) { if (game.children[j].isDangerHalo) { var oldHalo = game.children[j]; game.removeChild(oldHalo); if (oldHalo.destroy) oldHalo.destroy(); } } // 2. Draw memory-free hollow vector borders on cards 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 topC = t.creatureStack[t.creatureStack.length - 1]; var isEndangered = (inDangerList.indexOf(topC) !== -1); if (isEndangered) { if (!topC.dangerHalo) { var halo = new Graphics(); halo.lineStyle(5, 0xFF0000, 0.7); halo.drawRect(-80, -110, 160, 220); // 5px hollow border matching card bounds topC.addChild(halo); // Attach directly to the card topC.dangerHalo = halo; } } else { if (topC.dangerHalo) { if (topC.dangerHalo.parent) topC.dangerHalo.parent.removeChild(topC.dangerHalo); if (topC.dangerHalo.destroy) topC.dangerHalo.destroy(); topC.dangerHalo = null; } } } } } // 3. Process global warning text safely if (gamePhase === 'noon' && inDangerList.length > 0) { if (!game.dangerWarningText) { 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; } } else { if (game.dangerWarningText) { if (game.dangerWarningText.parent) game.dangerWarningText.parent.removeChild(game.dangerWarningText); if (game.dangerWarningText.destroy) game.dangerWarningText.destroy(); game.dangerWarningText = null; } } } ```
User prompt
1. STOP ADVANCED CARNIVORES without "voracious" FROM AUTO-LINKING: - Locate `function autoCreateLinks()`. - Find this exact code: ```javascript var carnivore = terrain.creatureStack[i]; if (carnivore.creatureData.dietType === 'carnivore') { // Try to create links for this carnivore ``` - REPLACE it with: ```javascript var carnivore = terrain.creatureStack[i]; if (carnivore.creatureData.dietType === 'carnivore') { // Only auto-link Voracious creatures if (carnivore.creatureData.level !== 'Basic' && !carnivore.creatureData.voracious) continue; // Try to create links for this carnivore ``` 2. FIX SEA-BOUND TERRAIN CHECK: - Locate `function isValidLinkTarget(carnivore, herbivore)`. - Find this exact code: ```javascript if (carnivore.creatureData.seaBound) { if (herbivore.creatureData.subtype !== 'water') { return false; // Sea-bound creatures can only link to water creatures } } ``` - REPLACE it with: ```javascript if (carnivore.creatureData.seaBound) { var targetTerrain = planetBoard[herbivore.gridY][herbivore.gridX]; if (targetTerrain && targetTerrain.terrainData && targetTerrain.terrainData.waterType !== 'sea') { return false; // Sea-bound must link to creatures on SEA terrain } } ```
User prompt
Please fix the bug: 'Timeout.tick error: setTimeout is not a function' in or related to this line: 'setTimeout(function () {' Line Number: 6895
User prompt
reduced the size of the danger highlight by 5.0, also highlight the 'adjust links' buttons of all creatures 'in danger' during the noon phase
User prompt
Please fix the bug: 'Uncaught TypeError: setTimeout is not a function' in or related to this line: 'setTimeout(function () {' Line Number: 6895
User prompt
Please fix the bug: 'TypeError: Graphics is not a constructor' in or related to this line: 'var halo = new Graphics();' Line Number: 5158
User prompt
The danger highlight is still causing memory/rendering issues because it creates a scaled terrain asset and pushes it behind the creature card. Please completely rewrite the visual generation of the danger highlight to use a hollow vector border instead. Please make this exact change: 1. REWRITE THE VISUALS IN UPDATE DANGER HIGHLIGHTS: - Locate the `updateDangerHighlights()` function. - Look inside the second loop (`for (var i = 0; i < inDangerList.length; i++)`) where `halo` is created. - Completely delete the lines that use `LK.getAsset('terrainLandFlat'...)` and delete the lines setting its `tint`, `alpha`, `scaleX`, and `scaleY`. - Instead, create `halo` as a `new Graphics()` object. - Instruct the Graphics object to set a line style of `5` pixel thickness, color `0xFF0000` (Red), and `0.7` opacity. - Instruct it to draw a hollow rectangle. The rectangle should be exactly the size of a creature card (160 width, 220 height). Because it is centered, its starting X and Y coordinates must be `-80` and `-110`. - Set `halo.x` and `halo.y` to match the `topCreature.x` and `topCreature.y`. - Because it is a hollow line, do NOT use `getChildIndex` or `addChildAt` to push it behind the creature. Simply use `game.addChild(halo);` so the red border draws perfectly on top of the card's edges. - Ensure you keep the `halo.isDangerHalo = true;` tag and the `topCreature.dangerHalo = halo;` assignments so the cleanup loop at the top of the function still works.
User prompt
with reference to the warning/danger highlight around creatures that will get an extinction marker: make it a border only - not over the card itself and reduce the scale a little and give the edges a toothed effect ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Timeout.tick error: Cannot set properties of null (setting 'terrainCard')' in or related to this line: 'planetSlots[tY][tX].terrainCard = finalCard;' Line Number: 6377
User prompt
the danger highlight that appears around cards in danger needs to be altered - it is too big, please reduce its size by 20%, it also sometimes causes memory issues so make sure that it does not flash or use excessive memory for any reason
User prompt
'BE04 and 'BE05' have text that displays at the top of the UI but it needs to be moved to the left. At the moment it is on the right half of the UI so move all the text that gets displayed during these events to the very centre of the screen, aligned centrally (use the "round"/"phase"/ "turn" text tine for the x coordinate reference and set the y to be 1 text line below the round/pgase/turn text line.
User prompt
Card 'BH33' has a special called "Shell" that is not functioning properly - if the shell marker is on 'BH33' when 1 or more event cards are drawn then it should trigger like this: 1st - a check to count the number of event cards drawn to the hand - if there is only 1 event card then text should appear in the UI just below the "round/phase/turn" text that reads: "Event defended! Shell token removed", and that event card should be moved to the appropriate event discard pile and the turn should continue as if no event was drawn at all. If however, 2 or more event cards were drawn then text should appear in the UI (in the same place as above stated) displaying: "The shell is ready to defend! Choose the event you wish to deflect", then the event cards in hand should be highlighted and wait for 1 of them to be double-clicked, where upon it is moved to the appropriate event discard pile and play continues as if the hand were just drawn (player must play the other even/s)
User prompt
Please fix the bug: 'Error: The supplied DisplayObject must be a child of the caller' in or related to this line: 'var basicIndex = game.getChildIndex(oldBasicCard);' Line Number: 6303
User prompt
Card: 'BH32' leaves a blue rectangle behind permanently in the top left of the UI where the animation takes place as the cards are shuffled for its special rule (terra-former) effect, this must be removed please, after the animation has finished and the new Basic Terrain card has been placed.
User prompt
Please make these exact 5 changes using precise string replacements: 1. SPEED UP THE ANIMATION: - Search for exactly: `{ duration: 2000, easing: tween.linear }` - Replace with: `{ duration: 1000, easing: tween.linear }` 2. SPEED UP THE TIMEOUT: - Search for exactly: ```javascript triggerNextPhase(); }, 2000); ``` - Replace with: ```javascript triggerNextPhase(); }, 1000); ``` 3. ROUNDED CORNERS FOR CREATURES: - Search for exactly: ```javascript self.isInPlay = false; self.linkMarkers = []; ``` - Replace with: ```javascript self.isInPlay = false; var maskGraphics = new Graphics(); maskGraphics.beginFill(0xFFFFFF); maskGraphics.drawRoundedRect(-80, -110, 160, 220, 15); maskGraphics.endFill(); self.addChild(maskGraphics); self.mask = maskGraphics; self.linkMarkers = []; ``` 4. ROUNDED CORNERS FOR EVENTS: - Search for exactly: ```javascript self.addChild(levelText); self.isInPlay = false; self.move = function (x, y, obj) { ``` - Replace with: ```javascript self.addChild(levelText); self.isInPlay = false; var eMask = new Graphics(); eMask.beginFill(0xFFFFFF); eMask.drawRoundedRect(-80, -110, 160, 220, 15); eMask.endFill(); self.addChild(eMask); self.mask = eMask; self.move = function (x, y, obj) { ``` 5. ROUNDED CORNERS FOR TERRAIN: - Search for exactly: ```javascript self.gridX = -1; self.gridY = -1; self.creatureStack = []; ``` - Replace with: ```javascript var tMask = new Graphics(); tMask.beginFill(0xFFFFFF); tMask.drawRoundedRect(-126, -168, 252, 336, 20); tMask.endFill(); self.addChild(tMask); self.mask = tMask; self.gridX = -1; self.gridY = -1; self.creatureStack = []; ```
User prompt
I need to change the "Next Phase" button into a "Hold to Confirm" button to prevent accidental clicks. It should require holding for 2 seconds, displaying an orange loading bar, and cancel if the player lets go early. Please make these exact 3 changes: 1. SETUP THE HOLD BAR GRAPHICS: - Locate the `nextPhaseButton` creation block (around line 1400, where `var nextPhaseButtonText` is). - Right BELOW the `game.addChild(nextPhaseButton);` line, insert this new loading bar graphic: ```javascript var holdBar = LK.getAsset('terrainLandFlat', { anchorX: 0, anchorY: 0.5 }); holdBar.tint = 0xFF8C00; // Orange holdBar.alpha = 0.8; holdBar.scaleX = 0; // Starts empty holdBar.scaleY = 0.1; // Thin bar holdBar.x = -126; // Starts at the left edge of the button holdBar.y = 150; // Sits at the bottom edge nextPhaseButton.addChild(holdBar); nextPhaseButton.holdBar = holdBar; nextPhaseButton.holdTimer = null; ``` 2. REWRITE THE NEXT PHASE LOGIC INTO A FUNCTION: - Find the `nextPhaseButton.down = function () {` line. - Rename that function to `function triggerNextPhase() {`. It should look exactly like this: ```javascript function triggerNextPhase() { // Exit link adjustment mode if active and hide adjust links button ``` - (Keep all the code inside it exactly the same). 3. ADD THE DOWN, UP, AND MOVE EVENTS: - Scroll down to the very end of the new `triggerNextPhase()` function (right below the `}` that closes it). - Insert these three mouse event handlers to manage the holding logic: ```javascript nextPhaseButton.down = function () { if (gamePhase === 'noon' && hasEventCardsInHand) return; // Blocked by events // Start the loading bar animation (2000ms = 2 seconds) this.holdBar.scaleX = 0; tween(this.holdBar, { scaleX: 1 }, { duration: 2000, easing: tween.linear }); // Start the 2 second timer this.holdTimer = LK.setTimeout(function() { // If this finishes, trigger the phase! nextPhaseButton.holdBar.scaleX = 0; triggerNextPhase(); }, 2000); }; nextPhaseButton.up = function () { // Cancel everything if they let go early if (this.holdTimer) { LK.clearTimeout(this.holdTimer); this.holdTimer = null; } tween.stop(this.holdBar); this.holdBar.scaleX = 0; }; // We also use .move to cancel if they drag their finger OFF the button var originalMove = nextPhaseButton.move; nextPhaseButton.move = function (x, y, obj) { if (originalMove) originalMove.call(this, x, y, obj); // If mouse leaves the button boundaries, cancel the hold var hw = 126 * this.scaleX; var hh = 168 * this.scaleY; if (Math.abs(this.x - x) > hw || Math.abs(this.y - y) > hh) { if (this.holdTimer) { LK.clearTimeout(this.holdTimer); this.holdTimer = null; } tween.stop(this.holdBar); this.holdBar.scaleX = 0; } }; ``` ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught TypeError: setTimeout is not a function' in or related to this line: 'setTimeout(function () {' Line Number: 6800
User prompt
I need to add a "Click to continue" pause to the Devolution event (BE02) when there are no valid targets, and completely remove the "Draw" button from the UI. Please make these exact 2 changes using precise string replacements: 1. REMOVE THE DRAW BUTTON: - Search the global variables section (around line 1000-1100) for the Draw button setup. Find this entire block of code: ```javascript // 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(); } }; ``` - COMPLETELY DELETE that entire block of code. 2. FIX DEVOLUTION EMPTY TARGETS PAUSE: - Locate the `processDevolutionEvent(onComplete)` function at the bottom of the script. - Find this exact block of code near the top of that function: ```javascript if (targets.length === 0) { onComplete(); // No targets, event fizzles instantly } else { ``` - REPLACE that block with this cinematic pause logic: ```javascript if (targets.length === 0) { var fizzleText = new Text2("No viable creatures - lucky you!\nClick to continue", { size: 56, fill: 0x00FF00, align: 'center', anchorX: 0.5, anchorY: 0 }); fizzleText.x = 1024; fizzleText.y = 90; game.addChild(fizzleText); 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 (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher); if (clickCatcher.destroy) clickCatcher.destroy(); if (fizzleText.parent) fizzleText.parent.removeChild(fizzleText); if (fizzleText.destroy) fizzleText.destroy(); onComplete(); }; } else { ```
User prompt
I need to automate the "Shell" (BH33) special rule so it intercepts Event cards exactly as they are drawn, and move its visual token to the bottom right of the card to prevent overlap. Please make these exact 3 changes using precise string replacements: 1. MOVE THE SHELL TOKEN TO THE BOTTOM RIGHT: - Locate the `applyCreatureEffect(creatureCard)` function. - Find these exact lines inside the `if (creature && creature.shell)` block: ```javascript shellMarker.x = 0; shellMarker.y = -100; ``` - REPLACE them with this to move it to the bottom right: ```javascript shellMarker.x = 50; shellMarker.y = 80; ``` 2. ADD AUTOMATIC EVENT INTERCEPTION ON DRAW: - Locate the `drawCard()` function. - Find this exact block of code (around line 1250): ```javascript var cardData = mainDeck.pop(); var card; // Create appropriate card type based on cardType ``` - REPLACE that block with this updated logic that catches Events before they enter the hand: ```javascript var cardData = mainDeck.pop(); // Automatic Shell Interceptor! if (cardData.cardType === 'event' && game.activeShellCreature) { // 1. Discard the event if (cardData.level === 'Advanced') advancedEventDiscard.push(cardData); else basicEventDiscard.push(cardData); // 2. Break the Shell var sc = game.activeShellCreature; if (sc.shellMarker) { if (sc.shellMarker.parent) sc.shellMarker.parent.removeChild(sc.shellMarker); if (sc.shellMarker.destroy) sc.shellMarker.destroy(); sc.shellMarker = null; } game.activeShellCreature = null; // 3. Show UI Alert var shellMsg = new Text2("Event card blocked by shell!", { size: 50, fill: 0x00FF00, align: 'center' }); shellMsg.anchor.set(0.5, 0.5); shellMsg.x = 1024; shellMsg.y = 1366; game.addChild(shellMsg); LK.setTimeout(function() { if (shellMsg.parent) shellMsg.parent.removeChild(shellMsg); if (shellMsg.destroy) shellMsg.destroy(); }, 2500); // 4. Automatically draw a replacement card! return drawCard(); } var card; // Create appropriate card type based on cardType ``` 3. REMOVE THE OLD MANUAL SHELL UI: - Locate the `function showEventCardDebugBox()` function. - REPLACE the entire function from top to bottom with this clean, default version (deleting the entire `if (game.activeShellCreature)` wrapper): ```javascript function showEventCardDebugBox() { 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; } ```
/****
* 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.populationMarkers = 0;
self.populationVisuals = [];
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.updatePopulationMarkers = function () {
for (var i = 0; i < self.populationVisuals.length; i++) {
if (self.populationVisuals[i].parent) self.populationVisuals[i].parent.removeChild(self.populationVisuals[i]);
}
self.populationVisuals = [];
self.linkRequirement = (self.creatureData.level === 'Basic' ? 1 : 2) + self.populationMarkers;
for (var i = 0; i < self.populationMarkers; i++) {
var pMark = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.12,
scaleY: 0.12
});
pMark.tint = 0xFFA500;
pMark.x = 40 - i * 20;
pMark.y = -80;
var pText = new Text2("P", {
size: 60,
fill: 0x000000,
font: "'Arial Black', 'Impact', sans-serif"
});
pText.anchor.set(0.5, 0.5);
pMark.addChild(pText);
self.addChild(pMark);
self.populationVisuals.push(pMark);
}
for (var i = 0; i < self.linkMarkers.length; i++) {
if (self.linkMarkers[i].parent) self.linkMarkers[i].parent.removeChild(self.linkMarkers[i]);
}
self.linkMarkers = [];
self.createLinkMarkers();
};
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
if (self.creatureData && self.creatureData.id === 'ACE05') nextYOffset = -65;
// Add "Tough" text if present
if (self.creatureData && self.creatureData.tough) {
self.toughText = new Text2("Tough", {
size: 21,
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 + 26;
}
// 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: 21,
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 += 26;
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
if (self.creatureData && self.creatureData.bully) {
self.bullyText = new Text2("Bully", {
size: 21,
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 + 26;
} //{2U_bully6}
// Add "Hydrophobic" text if present
if (self.creatureData && self.creatureData.hydrophobic) {
self.hydrophobicText = new Text2("Hydrophobic", {
size: 24,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.hydrophobicText.anchor.set(0.5, 0.5);
self.hydrophobicText.x = 0;
self.hydrophobicText.y = nextYOffset;
self.addChild(self.hydrophobicText);
nextYOffset += 28;
}
if (self.creatureData && self.creatureData.vigorous) {
self.vigorousText = new Text2("Vigorous", {
size: 21,
fill: 0xFF4500,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.vigorousText.anchor.set(0.5, 0.5);
self.vigorousText.x = 0;
self.vigorousText.y = nextYOffset;
self.addChild(self.vigorousText);
nextYOffset += 26;
}
if (self.creatureData && self.creatureData.voracious) {
self.voraciousText = new Text2("Voracious", {
size: 21,
fill: 0x8B0000,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.voraciousText.anchor.set(0.5, 0.5);
self.voraciousText.x = 0;
self.voraciousText.y = nextYOffset;
self.addChild(self.voraciousText);
nextYOffset += 26;
}
if (self.creatureData && self.creatureData.settlers) {
self.settlersText = new Text2("Settlers", {
size: 21,
fill: 0x8B4513,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.settlersText.anchor.set(0.5, 0.5);
self.settlersText.x = 0;
self.settlersText.y = nextYOffset;
self.addChild(self.settlersText);
nextYOffset += 26;
}
if (self.creatureData && self.creatureData.many) {
self.manyText = new Text2("Many", {
size: 21,
fill: 0xFF0000,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
self.manyText.anchor.set(0.5, 0.5);
self.manyText.x = 0;
self.manyText.y = nextYOffset;
self.addChild(self.manyText);
nextYOffset += 26;
}
// Add "Hunter-Hunter" text if present (for BC04 - Tuna)
if (self.creatureData && self.creatureData.hunterHunter) {
self.hunterHunterText = new Text2("Hunter-Hunter", {
size: 21,
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\nEater", {
size: 20,
fill: 0x00BFFF,
align: 'center',
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: 21,
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 + 26;
}
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);
}
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(function () {});
} 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 if (self.eventData.id === 'AE05') {
processHumansEvent(function () {});
} 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;
if (self.pinkBorder) {
if (self.pinkBorder.parent) self.pinkBorder.parent.removeChild(self.pinkBorder);
if (self.pinkBorder.destroy) self.pinkBorder.destroy();
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.terrainData.displacer && self.terrainData.id !== 'AT15' && self.terrainData.id !== 'AT18') {
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.id === 'AT15') {
var ancientText = new Text2("Ancient", {
size: 32,
fill: 0xFFD700,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
ancientText.anchor.set(0.5, 0.5);
ancientText.x = 0;
ancientText.y = 0;
self.addChild(ancientText);
}
if (self.terrainData && self.terrainData.id === 'AT18') {
var hostileText = new Text2("Hostile", {
size: 32,
fill: 0xFF4500,
font: "'Arial Black', 'Impact', sans-serif",
fontWeight: "bold"
});
hostileText.anchor.set(0.5, 0.5);
hostileText.x = 0;
hostileText.y = 0;
self.addChild(hostileText);
}
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
****/
// 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
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,
shell: config.id === 'BH33' ? 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' || config.id === 'AC04' ? true : false,
hydrophobic: config.id === 'AC04' ? 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.hydrophobic) infoString += " | Hydrophobic: Cannot link to creatures on water terrain.";
if (creature && creature.vigorous) infoString += " | Vigorous: Every dusk phase this creature will grow and require +1 link to survive!";
if (creature && creature.voracious) infoString += " | Voracious: You cannot control this creature's links! It feeds automatically!";
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.";
if (creature && creature.settlers) infoString += " | Settlers: Humans can go anywhere! (Can be placed on empty Advanced terrain or follow normal placement rules).";
if (creature && creature.many) infoString += " | Many: Max 3 links per creature, and a maximum of 24 total links!";
}
} 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!";
if (evt.id === 'AE05') infoString += " | Humans! Becomes an advanced carnivore requiring land that grows each turn demanding more resources and you cannot control how it feeds!";
} 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;
}
if (card.isVolcanoHotspot) {
infoString += " | This terrain is considered a hot climate!";
}
// 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) {
if (cardInfoText.parent) cardInfoText.parent.removeChild(cardInfoText);
if (cardInfoText.destroy) cardInfoText.destroy();
cardInfoText = null;
}
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) {
if (zoomedCard.parent) zoomedCard.parent.removeChild(zoomedCard);
if (zoomedCard.destroy) zoomedCard.destroy();
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();
}
}
}
} else if (card.eventData && card.eventData.id === 'AE05') {
// Highlight all Advanced Land Terrains
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.level === 'Advanced' && t.terrainData.subtype === 'land') {
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();
// Automatic Shell Interceptor!
if (cardData.cardType === 'event' && game.activeShellCreature) {
// 1. Discard the event
if (cardData.level === 'Advanced') advancedEventDiscard.push(cardData);else basicEventDiscard.push(cardData);
// 2. Break the Shell
var sc = game.activeShellCreature;
if (sc.shellMarker) {
if (sc.shellMarker.parent) sc.shellMarker.parent.removeChild(sc.shellMarker);
if (sc.shellMarker.destroy) sc.shellMarker.destroy();
sc.shellMarker = null;
}
game.activeShellCreature = null;
// 3. Show UI Alert
var shellMsg = new Text2("Event card blocked by shell!", {
size: 50,
fill: 0x00FF00,
align: 'center'
});
shellMsg.anchor.set(0.5, 0.5);
shellMsg.x = 1024;
shellMsg.y = 1366;
game.addChild(shellMsg);
LK.setTimeout(function () {
if (shellMsg.parent) shellMsg.parent.removeChild(shellMsg);
if (shellMsg.destroy) shellMsg.destroy();
}, 2500);
// 4. Automatically draw a replacement card!
return drawCard();
}
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() {
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 targetClimate = basicTerrainCard.isVolcanoHotspot ? 'hot' : basicTerrain.climate;
if (advancedTerrain.climateRequirement && advancedTerrain.climateRequirement !== 'any') {
var climateMatches = false;
if (advancedTerrain.climateRequirement.indexOf('/') !== -1) {
var allowedClimates = advancedTerrain.climateRequirement.split('/');
for (var k = 0; k < allowedClimates.length; k++) {
if (allowedClimates[k] === targetClimate) {
climateMatches = true;
break;
}
}
} else {
climateMatches = advancedTerrain.climateRequirement === targetClimate;
}
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 a Volcano hotspot, it is ALWAYS hot
if (terrainCard.isVolcanoHotspot) {
terrainClimate = 'hot';
}
// Otherwise, if it's an advanced terrain on basic terrain, use the basic terrain's row climate
else 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 || creature.settlers) {
return true;
} 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 = 50;
shellMarker.y = 80;
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.hydrophobic) {
var targetTerrain = planetBoard[herbivore.gridY][herbivore.gridX];
if (targetTerrain && targetTerrain.terrainData && targetTerrain.terrainData.subtype === 'water') {
return false; // Hydrophobic cannot link to water terrain
}
}
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 && (!carnivore.creatureData.many || carnivore.activeLinks.length < 24)) {
var linkCreated = false;
if (carnivore.creatureData.voracious) {
// Voracious creatures link randomly without player control
var possibleTargets = [];
var isEfficient = carnivore.creatureData && carnivore.creatureData.efficient;
var adjacentTerrains = getAdjacentTerrains(gridX, gridY, isEfficient);
for (var j = 0; j < adjacentTerrains.length; j++) {
var adjT = adjacentTerrains[j];
if (adjT && adjT.creatureStack.length > 0) {
var topH = adjT.creatureStack[adjT.creatureStack.length - 1];
if (isValidLinkTarget(carnivore, topH)) {
var currentLinksToTarget = 0;
for (var L = 0; L < carnivore.activeLinks.length; L++) {
if (carnivore.activeLinks[L].target === topH) currentLinksToTarget++;
}
if (!carnivore.creatureData.many || currentLinksToTarget < 3) {
possibleTargets.push(topH);
}
}
}
}
if (possibleTargets.length > 0) {
var randH = possibleTargets[Math.floor(Math.random() * possibleTargets.length)];
if (createLink(carnivore, randH)) linkCreated = true;
}
} else {
// Keep existing autoCreateLinks adjacent neighbor logic here...
// 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() {
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 () {
// 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.virusMarkers > 0) {
creature.extinctionMarkers += 1;
creature.updateExtinctionMarkers();
}
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();
break; // Cap poison damage at 1 marker per turn
}
}
} 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);
}
}
// Remove ALL links pointing TO this creature (fixes Hunter-Hunter pointing to carnivores)
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'
});
advancedEventDeck.push({
type: 'advanced',
cardType: 'event',
level: 'Advanced',
id: 'AE05',
name: 'Humans',
effect: 'catastrophic'
});
for (var i = 6; 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 {
// Basic Creature
if (basicEventDeck.length === 0 && basicEventDiscard.length > 0) {
basicEventDeck = basicEventDiscard.slice();
basicEventDiscard = [];
shuffleEventDeck(basicEventDeck);
}
if (basicEventDeck.length > 0) {
eventCard = basicEventDeck.pop();
} else {
// No fallback to advanced! Event fizzles safely.
var luckyMsg = new Text2("Lucky you - no events available!", {
size: 40,
fill: 0x00FF00,
align: 'center'
});
luckyMsg.anchor.set(0.5, 0.5);
luckyMsg.x = 1024;
luckyMsg.y = 1366;
game.addChild(luckyMsg);
LK.setTimeout(function () {
if (luckyMsg.parent) luckyMsg.parent.removeChild(luckyMsg);
}, 2000);
}
}
if (eventCard) {
discardPile.push(eventCard);
}
}
function processDawnPhase() {
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 proceed() {
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();
}
}
LK.setTimeout(proceed, 1000);
});
} else {
showPhaseTransition("Dawn Phase - Processing...", proceed);
}
}
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
if (!scryDeniedNextRound && currentRound > 1) {
scryTokenActive = true;
} else {
scryTokenActive = false; // Strictly enforce penalty
}
// Now that the round has started, clear the flags for the new round
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() {
showPhaseTransition("Dusk Phase - Arrange links...", function () {
// 1. Trigger Vigorous Growth
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 topC = t.creatureStack[t.creatureStack.length - 1];
if (topC.creatureData.vigorous) {
topC.populationMarkers++;
topC.updatePopulationMarkers();
}
}
}
}
// 2. Trigger Voracious Auto-Linking
autoCreateLinks();
showAdjustLinksButton();
updateLinkMarkerStates();
});
}
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();
});
}
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);
}
// 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();
}
// 1. Manage Creature Halos efficiently (Do not recreate every frame!)
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 topCreature = t.creatureStack[t.creatureStack.length - 1];
var isEndangered = inDangerList.indexOf(topCreature) !== -1;
if (isEndangered) {
if (!topCreature.dangerHalo) {
var halo = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
halo.tint = 0xFF0000;
halo.alpha = 0.5;
halo.scaleX = topCreature.scaleX * 1.15;
halo.scaleY = topCreature.scaleY * 1.15;
halo.x = topCreature.x;
halo.y = topCreature.y;
var tIndex = game.getChildIndex(topCreature);
if (tIndex > 0) game.addChildAt(halo, tIndex);else game.addChild(halo);
topCreature.dangerHalo = halo;
}
} else {
if (topCreature.dangerHalo) {
if (topCreature.dangerHalo.parent) topCreature.dangerHalo.parent.removeChild(topCreature.dangerHalo);
if (topCreature.dangerHalo.destroy) topCreature.dangerHalo.destroy();
topCreature.dangerHalo = null;
}
}
}
}
}
// 3. Process global warning text safely
if (gamePhase === 'noon' && inDangerList.length > 0) {
if (!game.dangerWarningText || !game.dangerWarningText.parent) {
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;
}
} else {
if (game.dangerWarningText) {
if (game.dangerWarningText.parent) game.dangerWarningText.parent.removeChild(game.dangerWarningText);
if (game.dangerWarningText.destroy) game.dangerWarningText.destroy();
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 = 40;
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;
cardContainer.lastClickTime = 0;
// Add click handler to select this card
cardContainer.down = function (x, y, obj) {
var currentTime = new Date().getTime();
var selectedIndex = this.cardDataIndex;
var selectedData = at18CarnivoreList[selectedIndex];
// First click: Read info
if (currentTime - this.lastClickTime > 500) {
this.lastClickTime = currentTime;
var dummyCard = {
creatureData: selectedData.cardType === 'creature' ? selectedData : null,
terrainData: selectedData.cardType === 'terrain' ? selectedData : null,
eventData: selectedData.cardType === 'event' ? selectedData : null
};
showCardInfo(dummyCard);
return;
}
// Second click: Confirm selection
hideCardInfo();
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();
// Scry Button Hover Warning
// The Scry button is at topRight (-300, 190). On a 2048 screen, this is roughly X: 1748, Y: 190.
var isHoveringScry = currentMouseX > 1700 && currentMouseX < 1950 && currentMouseY > 150 && currentMouseY < 260;
if (isHoveringScry && mainDeck.length < 3) {
if (!game.scryWarningText) {
game.scryWarningText = new Text2("Cannot scry due to low deck size", {
size: 24,
fill: 0xFF0000,
align: 'right'
});
game.scryWarningText.anchor.set(1, 0);
game.scryWarningText.x = -50;
game.scryWarningText.y = 240;
LK.gui.topRight.addChild(game.scryWarningText);
}
} else {
if (game.scryWarningText && game.scryWarningText.parent) {
game.scryWarningText.parent.removeChild(game.scryWarningText);
game.scryWarningText = null;
}
}
};
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 = 90;
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 = 90;
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.lastClickTime = 0;
cardContainer.down = function () {
var currentTime = new Date().getTime();
var selectedData = watcherList[this.cardDataIndex];
// First click: Read info
if (currentTime - this.lastClickTime > 500) {
this.lastClickTime = currentTime;
var dummyCard = {
creatureData: selectedData.cardType === 'creature' ? selectedData : null,
terrainData: selectedData.cardType === 'terrain' ? selectedData : null,
eventData: selectedData.cardType === 'event' ? selectedData : null
};
showCardInfo(dummyCard);
return;
}
// Second click: Confirm selection
hideCardInfo();
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 = 90;
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.lastClickTime = 0;
cardContainer.down = function () {
var currentTime = new Date().getTime();
var selectedData = forerunnerList[this.cardDataIndex];
// First click: Read info
if (currentTime - this.lastClickTime > 500) {
this.lastClickTime = currentTime;
// Create temporary dummy card to feed into showCardInfo
var dummyCard = {
creatureData: selectedData.cardType === 'creature' ? selectedData : null,
terrainData: selectedData.cardType === 'terrain' ? selectedData : null,
eventData: selectedData.cardType === 'event' ? selectedData : null
};
showCardInfo(dummyCard);
return;
}
// Second click (within 500ms): Confirm selection!
hideCardInfo();
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) {
var fizzleText = new Text2("No viable creatures - lucky you!\nClick to continue", {
size: 56,
fill: 0x00FF00,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
fizzleText.x = 1024;
fizzleText.y = 90;
game.addChild(fizzleText);
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 (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
if (clickCatcher.destroy) clickCatcher.destroy();
if (fizzleText.parent) fizzleText.parent.removeChild(fizzleText);
if (fizzleText.destroy) fizzleText.destroy();
onComplete();
};
} 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 = 90;
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 = 90;
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;
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 onFinish() {
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(function (a, b) {
return 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;
}
}
}
});
};
game.addChild(highlight);
}
}
}
;
function animateTerraformerSlotMachine(oldBasicCard, tX, tY) {
// Create a temporary visual card to flicker through the pool
var slotCard = new TerrainCard(basicTerrainPool[0]);
slotCard.gridX = tX;
slotCard.gridY = tY;
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);
// The constructor resets transforms, so we must reapply them!
slotCard.x = oldBasicCard.x;
slotCard.y = oldBasicCard.y;
slotCard.scaleX = oldBasicCard.scaleX;
slotCard.scaleY = oldBasicCard.scaleY;
speed += 15; // Slow down over time (friction)
LK.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);
// Perfectly match the old card's position
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;
// Update the physical board arrays
planetBoard[tY][tX] = finalCard;
planetSlots[tY][tX].terrainCard = finalCard;
if (slotCard.parent) slotCard.parent.removeChild(slotCard);
if (slotCard.destroy) {
slotCard.destroy(); // Completely wipe the temporary visual card from memory
}
game.addChildAt(finalCard, basicIndex);
// Flash yellow to signify the lock-in!
finalCard.tint = 0xFFFF00;
tween(finalCard, {
tint: 0xFFFFFF
}, {
duration: 500
});
}
}
flashNext();
}
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) {
// ONLY check the top creature on the stack
var topC = terrain.creatureStack[terrain.creatureStack.length - 1];
if (topC && topC.creatureData && topC.creatureData.level === 'Basic') {
targets.push(topC);
}
}
}
}
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 = 90;
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 = 90;
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 || [];
// Hardcode this tile to permanently act as 'hot' regardless of row
volcanoCard.terrainData.climate = 'hot';
volcanoCard.isVolcanoHotspot = true; // Tag for info text
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;
// If this is NOT the hot row (row 2), draw a red border
if (mY !== 2) {
var hotBorder = LK.getAsset('terrainLandFlat', {
anchorX: 0.5,
anchorY: 0.5
});
hotBorder.tint = 0xFF6347; // Hot climate color
hotBorder.scaleX = volcanoCard.scaleX * 1.05;
hotBorder.scaleY = volcanoCard.scaleY * 1.05;
hotBorder.x = volcanoCard.x;
hotBorder.y = volcanoCard.y;
var mIndex = game.getChildIndex(selectedMountain);
game.addChildAt(hotBorder, mIndex); // Put behind the terrain
volcanoCard.hotBorder = hotBorder;
}
planetBoard[mY][mX] = volcanoCard;
if (planetSlots[mY] && planetSlots[mY][mX]) {
//{OA_fix}
planetSlots[mY][mX].terrainCard = volcanoCard;
} //{OB_fix}
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.setText("The new terrain is very hot!\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 (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
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 creature can escape! Confirm");
LK.setTimeout(function () {
finishVolcano();
}, 1500);
return;
}
volcanoHeaderText.setText("Unlucky creature - Flee to survive!");
LK.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] && neighbors[n] !== mountain && canPlaceCreatureOnTerrain(topC, neighbors[n])) {
validEscapes.push(neighbors[n]);
}
}
mountain.creatureStack.push(topC);
if (validEscapes.length === 0) {
volcanoHeaderText.setText("The unlucky creature has nowhere to go, confirm");
// Create a 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 () {
if (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
// Kill the creature
var stack = mountain.creatureStack;
stack.pop();
graveyard.push(topC.creatureData);
scryDeniedNextRound = true; // Deny scry, but NO event card generated
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);
// Loop to check the 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]);
}
var movingC = mountain.creatureStack.pop();
placeCreatureOnStack(movingC, chosenEsc, chosenEsc.gridX, chosenEsc.gridY);
LK.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);
LK.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 = 90;
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.setText("The new terrain is a vast sea!\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 (clickCatcher.parent) clickCatcher.parent.removeChild(clickCatcher);
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!");
LK.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...");
LK.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);
LK.setTimeout(function () {
processRisingSeasFlee(seaTerrain);
}, 400);
};
game.addChild(hlt);
fleeHighlights.push(hlt);
}
}
}, 1500);
}
var risingSeasActive = false;
var risingSeasHeaderText = null;
;
var humansActive = false;
function processHumansEvent(onComplete) {
var targets = [];
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.level === 'Advanced' && t.terrainData.subtype === 'land') targets.push(t);
}
}
if (targets.length === 0) {
onComplete();
return;
}
humansActive = true;
var header = new Text2("Choose a site for the humans village!", {
size: 56,
fill: 0xFF4500,
align: 'center',
anchorX: 0.5,
anchorY: 0
});
header.x = 1024;
header.y = 90;
game.addChild(header);
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
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.isHumansHighlight = true;
highlight.down = function () {
if (!humansActive) return;
var tTerrain = this.targetTerrain;
for (var j = game.children.length - 1; j >= 0; j--) if (game.children[j].isHumansHighlight) game.removeChild(game.children[j]);
if (header.parent) header.parent.removeChild(header);
var humanData = {
type: 'advanced',
level: 'Advanced',
cardType: 'creature',
dietType: 'carnivore',
name: 'Humans',
id: 'ACE05',
subtype: 'land',
landType: 'any',
colorBand: 'brown',
climateRequirement: 'any',
efficient: true,
tough: true,
bully: true,
dominantDNA: true,
settlers: true,
vigorous: true,
voracious: true,
many: true
};
var humanCard = new CreatureCard(humanData);
if (tTerrain.creatureStack.length > 0) {
var coveredC = tTerrain.creatureStack[tTerrain.creatureStack.length - 1];
if (coveredC.shellMarker) {
coveredC.removeChild(coveredC.shellMarker);
coveredC.shellMarker = null;
if (game.activeShellCreature === coveredC) game.activeShellCreature = null;
}
for (var j = coveredC.activeLinks.length - 1; j >= 0; j--) animateLinkFallOff(coveredC, coveredC.activeLinks[j].target);
for (var j = linkLines.length - 1; j >= 0; j--) if (linkLines[j].herbivore === coveredC) animateLinkFallOff(linkLines[j].carnivore, coveredC);
}
for (var k = 0; k < tTerrain.creatureStack.length; k++) {
var exC = tTerrain.creatureStack[k];
exC.extinctionMarkers = 0;
exC.updateExtinctionMarkers();
tween(exC, {
y: exC.y + 20
}, {
duration: 200
});
}
tTerrain.creatureStack.push(humanCard);
humanCard.gridX = tTerrain.gridX;
humanCard.gridY = tTerrain.gridY;
humanCard.isInPlay = true;
var colorBarHeight = 42;
humanCard.x = tTerrain.x;
humanCard.y = tTerrain.y - tTerrain.height * tTerrain.scaleY / 2 + colorBarHeight + 150;
humanCard.scaleX = 252 / 160 * tTerrain.scaleX;
humanCard.scaleY = (336 - colorBarHeight / 2) / 220 * tTerrain.scaleY * 0.95;
game.addChild(humanCard);
humanCard.createLinkMarkers();
humansActive = false;
hasEventCardsInHand = false;
gamePhase = 'noon';
gameStatusText.setText("Round: " + currentRound + " | Phase: " + gamePhase.toUpperCase() + " | Turn: " + turnNumber);
var remaining = playerHand.slice();
if (remaining.length > 0) discardCards(remaining);
};
game.addChild(highlight);
}
}
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 = 90;
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;
if (planetSlots[tTerrain.gridY] && planetSlots[tTerrain.gridY][tTerrain.gridX]) {
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);
LK.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 = 90;
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);
LK.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 = 90;
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