X-Git-Url: https://git.yukkurigames.com/?p=heroik.git;a=blobdiff_plain;f=scenarios.js;h=12bfa3e857c16bd2559200fbb1d6357711478ad8;hp=25baf66889cd6ff431c2fe18050b10e54bf5f6ed;hb=2288d6ec7230056c1c4da48ca54185bc0193ea8f;hpb=12e70756fab6e3745aa153e7e2a9230e08df278f diff --git a/scenarios.js b/scenarios.js index 25baf66..12bfa3e 100644 --- a/scenarios.js +++ b/scenarios.js @@ -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 Fierce.", 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 Supremacy.", 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 Undead +1, Demons -1. Demons have Greenskins +1, Undead -1. Undead have Demons +1, Greenskins -1.", + requires: ["noncanonical", "demons", "undead"], + unique: true }, + { name: "New moon.", + effect: "Demons have Veil of Shadow.", + requires: ["noncanonical", "demons"], + unique: true }, + { name: "Camouflage.", + effect: "Greenskins have Veil of Shadow.", requires: ["noncanonical"], unique: true }, + { name: "Cryptic shades.", + effect: "Undead have Veil of Shadow.", + requires: ["noncanonical", "undead"], + unique: true }, + { name: "Lingering smoke.", + effect: "Dragons have Veil of Shadow.", + requires: ["noncanonical", "dragons"], + unique: true }, + { name: "Hypnotizing gaze.", + effect: "Bubbleyes have Veil of Shadow.", + 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; or 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;