Promo 3 card.
[heroik.git] / scenarios.js
index 25baf66..12bfa3e 100644 (file)
@@ -27,6 +27,10 @@ var EVENTS = [
       effect: "Abunakkashii and his Offspring gain a bonus of +2 Strength.",
       requires: ["abunakkashii"],
       unique: true },
+    { name: "The start of a legend.",
+      effect: "Abunakkashii and his Offspring gain a bonus of +1 Strength.",
+      requires: ["abunakkashii"],
+      unique: true },
     { name: "Technological prowess.",
       effect: "Traps gain a bonus of +1 Strength.",
       requires: ["traps"] },
@@ -35,6 +39,8 @@ var EVENTS = [
       effect: "Psi monsters gain a bonus of +1 Strength.", },
     { name: "A cry in the night.",
       action: "An adventurer of your choice loses 1 Mana." },
+    { name: "Vague psi.",
+      action: "Lose all your Mana tokens." },
     { name: "It's an ambush!",
       action: "Place a Tenacity token on each monster that does not have one." },
     { name: "The alarm is sounded.",
@@ -55,14 +61,48 @@ var EVENTS = [
     { name: "Epic combat.",
       effect: "The Final Monster gains a bonus of +3 Strength.",
       unique: true },
-    
+    { name: "Red herring.",
+      action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game." },
+    { name: "The torch has gone out.",
+      action: "The next dungeon card is placed face-down.", },
+
+    { name: "Anti-psi zone.",
+      effect: "You do not gain Mana tokens for the next three creatures you defeat, even if they have a Mana icon.",
+      unique: true },
+      
+    { name: "Necromancy.",
+      action: "Return a random defeated dungeon card to the lowest corridor and shuffle it." },
+    { name: "Reanimation.",
+      action: "Return a random defeated dungeon card to the highest corridor and shuffle it." },
+
+    { name: "Malediction.",
+      action: "Discard a quest or a magic item." },
+
+    { name: "False brethren.",
+      effect: "All Demons gain Immunity 5 and Immunity 7.",
+      requires: ["demons"],
+      unique: true },
+
+    { name: "Psi assault.",
+      effect: "You cannot use ultimate powers.",
+      lock: ["ultimates"],
+      unique: true },
+    { name: "Entering an anti-magic zone.",
+      effect: "You cannot use ultimate powers.",
+      lock: ["ultimates"],
+      duration: 3,
+      later: { name: "Leaving the anti-magic zone.",
+               effect: "You may use ultimate powers again.",
+               clear: ["ultimates"] }
+    },
+
     // Events from Sean Allen's random scenario generator.
     // http://boardgamegeek.com/filepage/57107/random-scenario-generator
     { name: "Fire from above.",
       effect: "Dragons gain a bonus of +1 Strength.",
       requires: ["dragons", "noncanonical"],
       unique: true },
-    { name: "Bad dreams.",
+    { name: "Overwhelming fear.",
       action: "Lose 1 extra Courage token. (The dungeon does not gain another Fear token.)",
       requires: ["noncanonical"],
       unique: true },
@@ -70,30 +110,17 @@ var EVENTS = [
       effect: "Any monster with a Tenacity token is also <strong>Fierce</strong>.",
       requires: ["noncanonical"],
       unique: true },
-    { name: "Dead end.",
-      action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.",
-      requires: ["noncanonical"] },
-    { name: "Surrounded.",
+    { name: "The tide has turned.",
       effect: "All monsters have <strong>Supremacy</strong>.",
       requires: ["noncanonical"],
       unique: true },
-
-    // Events from Stephane Renard's scenario.
-    // http://docfox.free.fr/spip.php?article129
-    { name: "What was that?",
-      action: "The next dungeon card is placed face-down.",
-      requires: ["noncanonical"] },
-    { name: "Malediction.",
-      action: "Discard all quests.",
-      requires: ["noncanonical"],
-      unique: true },
-    { name: "Reanimation.",
-      action: "Return a random spent dungeon card to the smallest corridor and shuffle it. (If you haven't spent any cards, nothing happens.)",
+    { name: "Lost.",
+      action: "Shuffle the remaining corridors together and redistribute the cards as if you were setting up the game.",
       requires: ["noncanonical"] },
-    { name: "Zone of silence.",
-      effect: "Temporary and Ultimate Powers cannot be used until you reveal a new card.",
+    { name: "Into the darkness.",
+      effect: "Choose one corridor. Its cards are now always placed face-down.",
       requires: ["noncanonical"] },
-
+    
     // Events of my own devising.
     { name: "Unwanted attention.",
       effect: "Unique monsters gain a bonus of +1 Strength.",
@@ -103,7 +130,7 @@ var EVENTS = [
       effect: "Greenskins have 1d8 Strength.",
       requires: ["noncanonical"],
       unique: true },
-    { name: "Dropped the torch.",
+    { name: "Twisty passages.",
       effect: "After defeating a card, roll a die. On an odd number its replacement is placed face-down.",
       requires: ["noncanonical"],
       unique: true },
@@ -124,11 +151,60 @@ var EVENTS = [
       unique: true },
     { name: "Infighting.",
       effect: "Greenskins have <strong>Undead&nbsp;+1</strong>, <strong>Demons&nbsp;-1</strong>. Demons have <strong>Greenskins&nbsp;+1</strong>, <strong>Undead&nbsp;-1</strong>. Undead have <strong>Demons&nbsp;+1</strong>, <strong>Greenskins&nbsp;-1</strong>.",
+      requires: ["noncanonical", "demons", "undead"],
+      unique: true },
+    { name: "New moon.",
+      effect: "Demons have <strong>Veil of Shadow</strong>.",
+      requires: ["noncanonical", "demons"],
+      unique: true },
+    { name: "Camouflage.",
+      effect: "Greenskins have <strong>Veil of Shadow</strong>.",
       requires: ["noncanonical"],
       unique: true },
+    { name: "Cryptic shades.",
+      effect: "Undead have <strong>Veil of Shadow</strong>.",
+      requires: ["noncanonical", "undead"],
+      unique: true },
+    { name: "Lingering smoke.",
+      effect: "Dragons have <strong>Veil of Shadow</strong>.",
+      requires: ["noncanonical", "dragons"],
+      unique: true },
+    { name: "Hypnotizing gaze.",
+      effect: "Bubbleyes have <strong>Veil of Shadow</strong>.",
+      requires: ["noncanonical", "bubbleyes"],
+      unique: true },
+    { name: "Leeched power.",
+      action: "A random adventurer loses a Mana token.",
+      requires: ["noncanonical"] },
+
+];
+
+var NOP = [
+    { name: "Nothing happens." },
+    { name: "A draft blows down the hallway.",
+      unique: true, requires: ["noncanonical"] },
+    { name: "You sneeze.",
+      unique: true, requires: ["noncanonical"] },
+    { name: "There's a skittering in the distance.",
+      unique: true,  requires: ["noncanonical"] },
+    { name: "The torch flickers.",
+      unique: true, requires: ["noncanonical"] },
+    { name: "Shadows dance across the walls.",
+      unique: true, requires: ["noncanonical"] },
 ];
 
-var NOTHING = { name: "Nothing happens." };
+var HELPFUL = [
+    { name: "It cuts both ways.",
+      action: "Next time you roll a 1, put a Tenacity token on all face-up monsters and roll again.",
+      requires: ["helpful"] },
+    { name: "Breached their defense.",
+      action: "Gain 2 Courage tokens and the dungeon gains 1 Fear token and the Fate Chart advances; <em>or</em> gain 1 Mana token.",
+      requires: ["helpful"], unique: true },
+    { name: "Mana ritual.",
+      action: "Discard up to 2 Mana tokens. For each one discarded, gain 1 Courage token.",
+      requires: ["helpful"] }
+];
+    
 var LOSE = { name: "Your adventuring party is defeated!" };
 
 function randrange (a, b) {
@@ -139,16 +215,49 @@ function generate (flags, events, nop) {
     var chosen = [];
     var i;
 
+    var pending = [];
+    var locks = [];
+    var event;
+
+    function pend (event, i) {
+        while (pending[i]) ++i
+        pending[i] = event;
+    }
+
     function canStillHappen (event) {
         return issubset(event.requires || [], flags)
-            && !(event.unique && contains.call(chosen, event));
+            && !(event.unique && contains.call(chosen, event))
+            && !(event.lock && intersects(event.lock || [], locks));
     }
 
-    for (i = 0; i < events; ++i)
-        chosen.push(choice(EVENTS.filter(canStillHappen)));
+    for (i = 0; i < events; ++i) {
+        event = pending.shift()
+            || choice(EVENTS.filter(canStillHappen));
+        chosen.push(event);
+        if (event.later)
+            pend(event.later, (Math.random() * chosen[i].duration) | 0);
+        locks = locks.concat(event.lock || [])
+            .filter(not(contains), event.clear || []);
+    }
+
+    for (i = 0; i < pending.length; ++i)
+        if (pending[i])
+            chosen.push(pending[i]);
 
-    for (i = 0; i < nop; ++i)
-        chosen.splice(randrange(0, chosen.length), 0, NOTHING);
+    
+    for (i = 0; i < nop / 2; ++i) {
+        var helpful = HELPFUL.filter(canStillHappen);
+        var neutral = NOP.filter(canStillHappen);
+        chosen.splice(
+            randrange(0, chosen.length), 0,
+            choice(helpful.length ? helpful : neutral));
+                           
+    }
+    for (;i < nop; ++i) {
+        var neutral = NOP.filter(canStillHappen);
+        chosen.splice(randrange(0, chosen.length), 0, choice(neutral));
+                           
+    }
 
     chosen.push(LOSE);
     return chosen;