WOLF GAME
xax
<<nobr>>\n<h1>WEREWOLF GAME</h1>\n<ul class="title">\n<li>[[new game|new_game]]</li>\n</ul>\n<<endnobr>>
<<display "init">>\n<<display "character creation">>\n\n[[→|game_intro]]
...\n\n[[←|Start]]
<<set $pc to\n { eye: either ("brown", "blue", "black", "hazel")\n , eyefeat: "none"\n , haircolor: either ("black", "brown", "blond", "ginger")\n , hair: "military cut"\n , facialhair: "none"\n , bodyhair: "minimal"\n , skin: either ("pale, freckled", "pale", "tanned", "light-skinned", "dark-skinned")\n , scars: "none"\n , tattoos: "none"\n }>>\\n<<set $ccdisplay to null>>\\neye color: <<block pceye>><<print $pc.eye>> <<block customize>><<if $ccdisplay !== "eye">>\\n<<click "⭷">><<set $ccdisplay to "eye"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.eye "pceye" "brown" "brown">>\n<<toggle $pc.eye "pceye" "blue" "blue">>\n<<toggle $pc.eye "pceye" "green" "green">>\n<<toggle $pc.eye "pceye" "hazel" "hazel">>\n<<toggle $pc.eye "pceye" "red" "red">>\n<<toggle $pc.eye "pceye" "gleaming red-green" "gleaming red-green">>\n<<endif>><<endblock>><<endblock>>\neye features: <<block pceyefeat>><<print $pc.eyefeat>> <<block customize>><<if $ccdisplay !== "eyefeat">>\\n<<click "⭷">><<set $ccdisplay to "eyefeat"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.eyefeat "pceyefeat" "none" "none">>\n<<toggle $pc.eyefeat "pceyefeat" "eyepatch (left)" "eyepatch (left)">>\n<<toggle $pc.eyefeat "pceyefeat" "glasses" "glasses">>\n<<toggle $pc.eyefeat "pceyefeat" "bandage-wrapped" "bandage-wrapped">>\n<<endif>><<endblock>><<endblock>>\nhair color: <<block pchaircolor>><<print $pc.haircolor>> <<block customize>><<if $ccdisplay !== "haircolor">>\\n<<click "⭷">><<set $ccdisplay to "haircolor"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.haircolor "pchaircolor" "black" "black">>\n<<toggle $pc.haircolor "pchaircolor" "brown" "brown">>\n<<toggle $pc.haircolor "pchaircolor" "blond" "blond">>\n<<toggle $pc.haircolor "pchaircolor" "ginger" "ginger">>\n<<toggle $pc.haircolor "pchaircolor" "grey" "grey">>\n<<endif>><<endblock>><<endblock>>\nhair style: <<block pchair>><<print $pc.hair>> <<block customize>><<if $ccdisplay !== "hair">>\\n<<click "⭷">><<set $ccdisplay to "hair"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.hair "pchair" "bald" "bald">>\n<<toggle $pc.hair "pchair" "shaved to stubble" "shaved to stubble">>\n<<toggle $pc.hair "pchair" "widow's peak" "widow's peak">>\n<<toggle $pc.hair "pchair" "military cut" "military cut">>\n<<toggle $pc.hair "pchair" "sides shaved; top slicked back" "sides shaved; top slicked back">>\n<<toggle $pc.hair "pchair" "shaggy curls" "shaggy curls">>\n<<toggle $pc.hair "pchair" "mid-length ponytail" "mid-length ponytail">>\n<<toggle $pc.hair "pchair" "long braid" "long braid">>\n<<toggle $pc.hair "pchair" "bowl cut" "bowl cut">>\n<<toggle $pc.hair "pchair" "pulled-back small braids" "pulled-back small braids">>\n<<toggle $pc.hair "pchair" "dreadlocks" "dreadlocks">>\n<<endif>><<endblock>><<endblock>>\nfacial hair: <<block pcfacialhair>><<print $pc.facialhair>> <<block customize>><<if $ccdisplay !== "facialhair">>\\n<<click "⭷">><<set $ccdisplay to "facialhair"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.facialhair "pcfacialhair" "none" "none">>\n<<toggle $pc.facialhair "pcfacialhair" "stubble" "stubble">>\n<<toggle $pc.facialhair "pcfacialhair" "mustache" "mustache">>\n<<toggle $pc.facialhair "pcfacialhair" "goatee" "goatee">>\n<<toggle $pc.facialhair "pcfacialhair" "chinstrap" "chinstrap">>\n<<toggle $pc.facialhair "pcfacialhair" "full beard" "full beard">>\n<<endif>><<endblock>><<endblock>>\nbody hair: <<block pcbodyhair>><<print $pc.bodyhair>> <<block customize>><<if $ccdisplay !== "bodyhair">>\\n<<click "⭷">><<set $ccdisplay to "bodyhair"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.bodyhair "pcbodyhair" "shaved" "shaved">>\n<<toggle $pc.bodyhair "pcbodyhair" "minimal" "minimal">>\n<<toggle $pc.bodyhair "pcbodyhair" "hairy chest" "hairy chest">>\n<<toggle $pc.bodyhair "pcbodyhair" "hairy forearms & calves" "hairy forearms & calves">>\n<<toggle $pc.bodyhair "pcbodyhair" "coarsely-haired" "coarsely-haired">>\n<<endif>><<endblock>><<endblock>>\nskin color: <<block pcskin>><<print $pc.skin>> <<block customize>><<if $ccdisplay !== "skin">>\\n<<click "⭷">><<set $ccdisplay to "skin"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.skin "pcskin" "pale, freckled" "pale, freckled">>\n<<toggle $pc.skin "pcskin" "pale" "pale">>\n<<toggle $pc.skin "pcskin" "tanned" "tanned">>\n<<toggle $pc.skin "pcskin" "light-skinned" "light-skinned">>\n<<toggle $pc.skin "pcskin" "dark-skinned" "dark-skinned">>\n<<endif>><<endblock>><<endblock>>\nscars: <<block pcscars>><<print $pc.scars>> <<block customize>><<if $ccdisplay !== "scars">>\\n<<click "⭷">><<set $ccdisplay to "scars"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.scars "pcscars" "none" "none">>\n<<toggle $pc.scars "pcscars" "fang marks; across neck and chest" "fang marks; across neck and chest">>\n<<toggle $pc.scars "pcscars" "claw marks; across ribs" "claw marks; across ribs">>\n<<toggle $pc.scars "pcscars" "burns; flecks across left side of body" "burns; flecks across left side of body">>\n<<toggle $pc.scars "pcscars" "cuts; across back of hands and both forearms" "cuts; across back of hands and both forearms">>\n<<toggle $pc.scars "pcscars" "cuts; across ribs and side" "cuts; across ribs and side">>\n<<toggle $pc.scars "pcscars" "cratered bullet mark; right side of stomach and back" "cratered bullet mark; right side of stomach and back">>\n<<toggle $pc.scars "pcscars" "cratered bullet mark; left shoulder" "cratered bullet mark; left shoulder">>\n<<toggle $pc.scars "pcscars" "buckshot mark; chest and right shoulder" "buckshot mark; chest and right shoulder">>\n<<toggle $pc.scars "pcscars" "facial scar; over cheek and through eyebrow" "facial scar; over cheek and through eyebrow">>\n<<toggle $pc.scars "pcscars" "facial scar; across bridge of nose" "facial scar; across bridge of nose">>\n<<toggle $pc.scars "pcscars" "facial scar; notched left ear" "facial scar; notched left ear">>\n<<toggle $pc.scars "pcscars" "prisoner brand; left deltoid" "prisoner brand; left deltoid">>\n<<endif>><<endblock>><<endblock>>\ntattoos: <<block pctattoos>><<print $pc.tattoos>> <<block customize>><<if $ccdisplay !== "tattoos">>\\n<<click "⭷">><<set $ccdisplay to "tattoos"; reshow ("customize")>><<endclick>>\\n<<else>><<click "⭸">><<set $ccdisplay to null; reshow ("customize")>><<endclick>>\n<<toggle $pc.tattoos "pctattoos" "none" "none">>\n<<toggle $pc.tattoos "pctattoos" "disordered mess of sailor tattoos covering most of both arms, chest, and back" "disordered mess of sailor tattoos covering most of both arms, chest, and back">>\n<<toggle $pc.tattoos "pctattoos" "knuckle tattoos; lettering in coils up wrists and forearms" "knuckle tattoos; lettering in coils up wrists and forearms">>\n<<toggle $pc.tattoos "pctattoos" "chest piece; deer skull and antlers surrounded by thorn vines" "chest piece; deer skull and antlers surrounded by thorn vines">>\n<<toggle $pc.tattoos "pctattoos" "chest piece; bloody heart pierced with crossed daggers" "chest piece; bloody heart pierced with crossed daggers">>\n<<toggle $pc.tattoos "pctattoos" "ornate lemniscate double-barred cross across back from lower neck to waist" "ornate lemniscate double-barred cross across back from lower neck to waist">>\n<<toggle $pc.tattoos "pctattoos" "sharp-angled geometric knotwork in rings around both biceps" "sharp-angled geometric knotwork in rings around both biceps">>\n<<toggle $pc.tattoos "pctattoos" "zig-zagging lines down spine" "zig-zagging lines down spine">>\n<<toggle $pc.tattoos "pctattoos" "symmetrical curving lines and dots across hips and thighs" "symmetrical curving lines and dots across hips and thighs">>\n<<toggle $pc.tattoos "pctattoos" "dots and lines across eyebrows, cheeks, jawline" "dots and lines across eyebrows, cheeks, jawline">>\n<<toggle $pc.tattoos "pctattoos" "palms and soles of feet inked solid black" "palms and soles of feet inked solid black">>\n<<endif>><<endblock>><<endblock>>
<<block mapblock>><<citymap $_pos>><<set markVisited ($_pos)>>\n<<endblock>>\\n/%\n{{{TODO}}}\n* {{{write room descriptions}}}\n* {{{write npc dialog}}}\n* {{{get follow-up questions based on clues working}}}\n* {{{figure out what room investigation would even look like}}}\n* {{{hover/click controls for map movement?}}}\n* {{{add secret & inaccessible exits; split riverside exits in two so you actually have to cross the river}}}\n* {{{place more npcs(?)}}}\n%/
window.replicate = function (val, length) {\n var o = [];\n for (var i = 0; i < length; i++) {\n o.push (val);\n }\n return o;\n};\n\nwindow.hasNonEmptyChildNodes = function (e) {\n for(let i = 0; i < e.childNodes.length; i++) {\n let ch = e.childNodes[i];\n if (ch.tagName || (ch.data && ch.data.trim() !== "")) {\n return true;\n }\n }\n return false;\n}\n\nfunction ixes (pos) {\n let nth = Math.floor (pos / 32);\n let ix = pos % 32;\n return {nth: nth, ix: ix};\n};\n\nwindow.markVisited = function (pos) {\n let ox = ixes (pos);\n let visited = state.history[0].variables["_visited"];\n visited[ox.nth] = visited[ox.nth] | (1 << ox.ix);\n state.history[0].variables["_visited"] = visited;\n}\n\nwindow.wasVisited = function (pos) {\n let ox = ixes (pos);\n let visited = state.history[0].variables["_visited"];\n return !!(visited[ox.nth] & (1 << ox.ix));\n}
function Point2d (x, y) {\n this.x = x;\n this.y = y;\n};\nPoint2d.prototype.minus = function (pt) {\n return new Point2d (this.x - pt.x, this.y - pt.y);\n};\nPoint2d.prototype.timesScalar = function (s) {\n return new Point2d (this.x * s, this.y * s);\n};\nPoint2d.prototype.magnitude = function () {\n return Math.sqrt (this.x * this.x + this.y * this.y);\n};\nPoint2d.prototype.normalize = function () {\n let d = this.magnitude();\n return new Point2d (this.x / d, this.y / d);\n};\nPoint2d.prototype.clamp = function (mag) {\n let d = this.magnitude();\n if (d <= mag) {\n return this;\n }\n return this.normalize().timesScalar(mag);\n};\n\nfunction Point3d (x, y, z) {\n this.x = x;\n this.y = y;\n this.z = z;\n}\nPoint3d.prototype.plus = function (pt) {\n return new Point3d (pt.x + this.x, pt.y + this.y, pt.z + this.z);\n}\nPoint3d.prototype.timesScalar = function (s) {\n return new Point3d (this.x * s, this.y * s, this.z * s);\n}\nPoint3d.prototype.rotateY = function (rad) {\n return new Point3d\n ( this.x * Math.cos (rad) + this.z * Math.sin (rad)\n , this.y\n ,-this.x * Math.sin (rad) + this.z * Math.cos (rad)\n );\n}\nfunction Matrix44 (vs) {\n var i = 0;\n this.vs =\n [ 1, 0, 0, 0\n , 0, 1, 0, 0\n , 0, 0, 1, 0\n , 0, 0, 0, 1\n ];\n if (vs) {\n for (i = 0; i < 16; i++) {\n this.vs[i] = vs[i];\n }\n }\n}\nMatrix44.prototype.scale = function (s) {\n [0,1,2,4,5,6,8,9,10].map (i => this.vs[i] *= s);\n return this;\n};\nMatrix44.prototype.rotateX = function (rad) {\n this.vs[ 5] = Math.cos (rad);\n this.vs[ 6] = -Math.sin (rad);\n this.vs[ 9] = Math.sin (rad);\n this.vs[10] = Math.cos (rad);\n return this;\n};\nMatrix44.prototype.rotateY = function (rad) {\n this.vs[ 0] = Math.cos (rad);\n this.vs[ 2] = -Math.sin (rad);\n this.vs[ 8] = Math.sin (rad);\n this.vs[10] = Math.cos (rad);\n return this;\n};\nMatrix44.prototype.rotateZ = function (rad) {\n this.vs[ 0] = Math.cos (rad);\n this.vs[ 1] = -Math.sin (rad);\n this.vs[ 4] = Math.sin (rad);\n this.vs[ 5] = Math.cos (rad);\n return this;\n};\nMatrix44.prototype.multiplyVector = function (vec3) {\n var x\n = this.vs[0] * vec3.x\n + this.vs[1] * vec3.y\n + this.vs[2] * vec3.z\n + this.vs[3] * 0;\n var y\n = this.vs[4] * vec3.x\n + this.vs[5] * vec3.y\n + this.vs[6] * vec3.z\n + this.vs[7] * 0;\n var z\n = this.vs[8] * vec3.x\n + this.vs[9] * vec3.y\n + this.vs[10] * vec3.z\n + this.vs[11] * 0;\n return new Point3d (x, y, z);\n};\nMatrix44.prototype.tvec3 = Matrix44.prototype.multiplyVector;\n\n\nfunction identity () {\n return new Matrix44 (\n [ 1, 0, 0, 0\n , 0, 1, 0, 0\n , 0, 0, 1, 0\n , 0, 0, 0, 1\n ]);\n}\nfunction rotationX (rad) {\n return identity().rotateX(rad);\n}\nfunction rotationY (rad) {\n return identity().rotateY(rad);\n}\nfunction rotationZ (rad) {\n return identity().rotateZ(rad);\n}\nfunction euler (x, y, z) {\n let a = Math.cos (x);\n let b = Math.sin (x);\n let c = Math.cos (y);\n let d = Math.sin (y);\n let e = Math.cos (z);\n let f = Math.sin (z);\n return new Matrix44 (\n [ c * e, -c * f, -d, 0\n , -b * d * e + a * f, b * d * f + a * e, -b * c, 0\n , a * d * e + b * f, -a * d * f + b * e, a * c, 0\n , 0, 0, 0, 1\n ]);\n};\n\nfunction t (p, a, b) {\n return a + (b - a) * p;\n}\n\nfunction RoomBox (offset, floor, outlines) {\n this.offset = offset;\n this.floor = floor;\n this.floorOutline = null;\n this.outlines = outlines;\n}\nRoomBox.prototype.pitchCeiling = function (pitch, axis) {\n if (this.outlines[0].length !== 4) {\n return; /* not sure how to do this aside from this way */\n }\n let p1 = null;\n let p2 = null;\n if (axis === "X") {\n p1 = new Point3d\n ( this.outlines[0][0].x\n , this.outlines[0][0].y - pitch\n , t (0.5, this.outlines[0][0].z, this.outlines[0][1].z)\n );\n p2 = new Point3d\n ( this.outlines[0][2].x\n , this.outlines[0][2].y - pitch\n , t (0.5, this.outlines[0][2].z, this.outlines[0][3].z)\n );\n this.outlines[0] =\n [ this.outlines[0][0]\n , p1\n , this.outlines[0][1]\n , this.outlines[0][2]\n , p2\n , this.outlines[0][3]\n ];\n } else {\n p1 = new Point3d\n ( t (0.5, this.outlines[0][1].x, this.outlines[0][2].x)\n , this.outlines[0][0].y - pitch\n , this.outlines[0][0].z\n );\n p2 = new Point3d\n ( t (0.5, this.outlines[0][3].x, this.outlines[0][0].x)\n , this.outlines[0][2].y - pitch\n , this.outlines[0][2].z\n );\n this.outlines[0] =\n [ this.outlines[0][0]\n , this.outlines[0][1]\n , p2\n , this.outlines[0][2]\n , this.outlines[0][3]\n , p1\n ];\n }\n this.outlines.push ([p1, p2]);\n return this;\n}\nRoomBox.prototype.addFloor = function (floorLoop) {\n this.floor.push (floorLoop);\n return this;\n};\nRoomBox.prototype.withFloorOutline = function (floorOutline) {\n this.floorOutline = floorOutline;\n return this;\n};\nRoomBox.prototype.rotate = function (rad) {\n this.floor = this.floor.map (loop => loop.map (pt => pt.rotateY (rad)));\n this.outlines = this.outlines.map (loop => loop.map (pt => pt.rotateY (rad)));\n return this;\n}\nRoomBox.prototype.render = function (camera) {\n let floor = null;\n let outline = null;\n let floorOutline = null;\n let o = {};\n if (this.floor.length === 1) {\n floor = document.createElementNS("http://www.w3.org/2000/svg", "path");\n floor.setAttributeNS(null, 'd', this.floorPath (camera));\n } else {\n floor = document.createElementNS("http://www.w3.org/2000/svg", "g");\n this.floor\n .map (lines => {\n let elem = document.createElementNS("http://www.w3.org/2000/svg", "path");\n elem.setAttributeNS(null, 'd', this.simplePath (camera, lines));\n floor.appendChild (elem);\n });\n }\n outline = document.createElementNS("http://www.w3.org/2000/svg", "path");\n outline.setAttributeNS(null, 'd', this.outlinesPath (camera));\n o.floor = floor;\n o.outlines = outline;\n if (this.floorOutline) {\n floorOutline = document.createElementNS("http://www.w3.org/2000/svg", "path");\n floorOutline.setAttributeNS(null, 'd', this.simplePath (camera, this.floorOutline));\n o.floorOutline = floorOutline;\n }\n return o;\n}\nRoomBox.prototype.floorPath = function (camera) {\n return this.path (camera, this.floor);\n}\nRoomBox.prototype.outlinesPath = function (camera) {\n return this.path (camera, this.outlines);\n}\n\nRoomBox.prototype.path = function (camera, points) {\n return points\n .map (line => {\n let o = "";\n o = line\n .map (pt => camera.tvec3 (pt.plus (this.offset)))\n .map ((pt, i) => (i == 1 ? "L " : "") + pt.x + "," + pt.z)\n .reduce ((a, b) => a + " " + b);\n if (line.length > 2) {\n return "M " + o + " Z";\n } else {\n return "M " + o;\n }\n }).reduce ((a, b) => a + " " + b);\n};\nRoomBox.prototype.simplePath = function (camera, line) {\n let o = line\n .map (pt => camera.tvec3 (pt.plus (this.offset)))\n .map ((pt, i) => (i == 1 ? "L " : "") + pt.x + "," + pt.z)\n .reduce ((a, b) => a + " " + b);\n if (line.length > 2) {\n return "M " + o + " Z";\n } else {\n return "M " + o;\n }\n return o;\n};\n\n\nwindow.worldDir = function (pt) {\n if (pt.y < -(Math.abs (pt.x) + Math.abs (pt.z)) - 0.20) {\n return "up";\n } else if (pt.y > Math.abs (pt.x) + Math.abs (pt.z)) {\n return "down";\n }\n var dirs = ["northwest", "west", "southwest", "south", "southeast", "east", "northeast", "north"];\n if (pt.x == 0 && pt.z == 0) {\n return "inside";\n }\n return dirs[(Math.round (Math.atan2 (pt.x, pt.z) * (4 / Math.PI) + 3) + 8) % 8];\n}\n\n\nwindow.cuboid = function (offset, dims) {\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( dims.x, 0, dims.z)\n , new Point3d ( dims.x, 0,-dims.z)\n , new Point3d (-dims.x, 0,-dims.z)\n , new Point3d (-dims.x, 0, dims.z)\n ]\n ]\n ,/* invert the y axis b/c this is svg where +y is further down */\n [ [ new Point3d ( dims.x,-dims.y, dims.z)\n , new Point3d ( dims.x,-dims.y,-dims.z)\n , new Point3d (-dims.x,-dims.y,-dims.z)\n , new Point3d (-dims.x,-dims.y, dims.z)\n ]\n , [ new Point3d ( dims.x, 0 , dims.z)\n , new Point3d ( dims.x,-dims.y, dims.z)\n ]\n , [ new Point3d ( dims.x, 0 ,-dims.z)\n , new Point3d ( dims.x,-dims.y,-dims.z)\n ]\n , [ new Point3d (-dims.x, 0 ,-dims.z)\n , new Point3d (-dims.x,-dims.y,-dims.z)\n ]\n , [ new Point3d (-dims.x, 0 , dims.z)\n , new Point3d (-dims.x,-dims.y, dims.z)\n ]\n ]\n );\n};\nwindow.cube = function (offset) {\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( 0.45, 0, 0.45)\n , new Point3d ( 0.45, 0,-0.45)\n , new Point3d (-0.45, 0,-0.45)\n , new Point3d (-0.45, 0, 0.45)\n ]\n ]\n ,/* invert the y axis b/c this is svg where +y is further down */\n [ [ new Point3d ( 0.45,-0.90, 0.45)\n , new Point3d ( 0.45,-0.90,-0.45)\n , new Point3d (-0.45,-0.90,-0.45)\n , new Point3d (-0.45,-0.90, 0.45)\n ]\n , [ new Point3d (0.45, 0 , 0.45)\n , new Point3d (0.45,-0.90, 0.45)\n ]\n , [ new Point3d (0.45, 0 , -0.45)\n , new Point3d (0.45,-0.90, -0.45)\n ]\n , [ new Point3d (-0.45, 0 , -0.45)\n , new Point3d (-0.45,-0.90, -0.45)\n ]\n , [ new Point3d (-0.45, 0 , 0.45)\n , new Point3d (-0.45,-0.90, 0.45)\n ]\n ]\n );\n};\nwindow.octogon = function (offset, height) {\n let h = height ? height : 0.90;\n let basePts =\n [ new Point3d (-0.20, 0, 0.45)\n , new Point3d ( 0.20, 0, 0.45)\n , new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, 0,-0.20)\n , new Point3d ( 0.20, 0,-0.45)\n , new Point3d (-0.20, 0,-0.45)\n , new Point3d (-0.45, 0,-0.20)\n , new Point3d (-0.45, 0, 0.20)\n ];\n return new RoomBox\n ( offset\n ,\n [ basePts\n ]\n ,\n [ basePts.map (pt => new Point3d (pt.x, -h, pt.z))\n ].concat (basePts.map (pt =>\n [ new Point3d (pt.x, 0, pt.z)\n , new Point3d (pt.x, -h, pt.z)\n ]))\n );\n};\nwindow.split = function (offset, p1, gap, p2) {\n let width = p1 + gap + p2;\n let mid = width / 2;\n /* */\n let stops = [-mid, -mid + p1, -mid + p1 + gap, mid];\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( 0.45, 0, stops[0]) /* far edge */\n , new Point3d (-0.45, 0, stops[0])\n , new Point3d (-0.45, 0, stops[1]) /* inner edge of shape 1 */\n , new Point3d ( 0.45, 0, stops[1])\n ]\n , [ new Point3d ( 0.45, 0, stops[2]) /* inner edge of shape 2 */\n , new Point3d (-0.45, 0, stops[2])\n , new Point3d (-0.45, 0, stops[3]) /* far edge */\n , new Point3d ( 0.45, 0, stops[3])\n ]\n ]\n ,\n [ [ new Point3d ( 0.45,-0.60, stops[3])\n , new Point3d ( 0.45,-0.60, stops[0])\n , new Point3d (-0.45,-0.60, stops[0])\n , new Point3d (-0.45,-0.60, stops[3])\n ]\n , [ new Point3d ( 0.45, 0 , stops[3])\n , new Point3d ( 0.45,-0.60, stops[3])\n ]\n , [ new Point3d ( 0.45, 0 , stops[0])\n , new Point3d ( 0.45,-0.60, stops[0])\n ]\n , [ new Point3d (-0.45, 0 , stops[0])\n , new Point3d (-0.45,-0.60, stops[0])\n ]\n , [ new Point3d (-0.45, 0 , stops[3])\n , new Point3d (-0.45,-0.60, stops[3])\n ]\n , [ new Point3d ( 0.45, 0.45, stops[2])\n , new Point3d ( 0.45, 0.45, stops[1])\n , new Point3d (-0.45, 0.45, stops[1])\n , new Point3d (-0.45, 0.45, stops[2])\n ]\n , [ new Point3d ( 0.45, 0 , stops[2])\n , new Point3d ( 0.45, 0.45, stops[2])\n ]\n , [ new Point3d ( 0.45, 0 , stops[1])\n , new Point3d ( 0.45, 0.45, stops[1])\n ]\n , [ new Point3d (-0.45, 0 , stops[1])\n , new Point3d (-0.45, 0.45, stops[1])\n ]\n , [ new Point3d (-0.45, 0 , stops[2])\n , new Point3d (-0.45, 0.45, stops[2])\n ]\n ]\n );\n};\nwindow.multipath = function (offset, dirs) {\n let adirs = dirs.toLowerCase().split("").sort().join("");\n switch (adirs) {\n case "ns":\n return hall (offset, "Z");\n case "ew":\n return hall (offset, "X");\n case "es":\n return corner (offset, 0);\n case "en":\n return corner (offset, 1);\n case "nw":\n return corner (offset, 2);\n case "sw":\n return corner (offset, 3);\n case "esw":\n return tjunction (offset, 0);\n case "ens":\n return tjunction (offset, 1);\n case "enw":\n return tjunction (offset, 2);\n case "nsw":\n return tjunction (offset, 3);\n case "ensw":\n return crossing (offset);\n default:\n return cube (offset);\n }\n};\nwindow.hall = function (offset, dim, longLength, shortLength) {\n let length = longLength ? longLength : 0.45;\n let width = shortLength ? shortLength : 0.20;\n let xDim = dim === "X" ? length : width;\n let zDim = dim === "Z" ? length : width;\n let h = -0.40;\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( xDim, 0, zDim)\n , new Point3d ( xDim, 0,-zDim)\n , new Point3d (-xDim, 0,-zDim)\n , new Point3d (-xDim, 0, zDim)\n ]\n ]\n ,\n [ [ new Point3d ( xDim, h, zDim)\n , new Point3d ( xDim, h,-zDim)\n , new Point3d (-xDim, h,-zDim)\n , new Point3d (-xDim, h, zDim)\n ]\n , [ new Point3d (xDim, 0, zDim)\n , new Point3d (xDim, h, zDim)\n ]\n , [ new Point3d (xDim, 0, -zDim)\n , new Point3d (xDim, h, -zDim)\n ]\n , [ new Point3d (-xDim, 0, -zDim)\n , new Point3d (-xDim, h, -zDim)\n ]\n , [ new Point3d (-xDim, 0, zDim)\n , new Point3d (-xDim, h, zDim)\n ]\n ]\n );\n};\nwindow.stairs = function (offset, dim, slope) {\n let xDim = dim === "X" ? 0.45 : 0.20;\n let zDim = dim === "Z" ? 0.45 : 0.20;\n let xHeight = dim === "X" ? -slope : 0;\n let zHeight = dim === "Z" ? -slope : 0;\n let h = -0.40;\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( xDim, 0, zDim)\n , new Point3d ( xDim, zHeight,-zDim)\n , new Point3d (-xDim, xHeight + zHeight,-zDim)\n , new Point3d (-xDim, xHeight , zDim)\n ]\n ]\n ,\n [ [ new Point3d ( xDim, h , zDim)\n , new Point3d ( xDim, h + zHeight ,-zDim)\n , new Point3d (-xDim, h + xHeight + zHeight,-zDim)\n , new Point3d (-xDim, h + xHeight , zDim)\n ]\n , [ new Point3d (xDim, 0, zDim)\n , new Point3d (xDim, h, zDim)\n ]\n , [ new Point3d (xDim, zHeight, -zDim)\n , new Point3d (xDim, h + zHeight, -zDim)\n ]\n , [ new Point3d (-xDim, xHeight + zHeight, -zDim)\n , new Point3d (-xDim, h + xHeight + zHeight, -zDim)\n ]\n , [ new Point3d (-xDim, xHeight, zDim)\n , new Point3d (-xDim, h + xHeight, zDim)\n ]\n ]\n );\n};\nwindow.crossing = function (offset) {\n let h = -0.40;\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( 0.20, 0, 0.20) /* c */\n , new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, 0,-0.20)\n , new Point3d ( 0.20, 0,-0.20) /* c */\n , new Point3d ( 0.20, 0,-0.45)\n , new Point3d (-0.20, 0,-0.45)\n , new Point3d (-0.20, 0,-0.20) /* c */\n , new Point3d (-0.45, 0,-0.20)\n , new Point3d (-0.45, 0, 0.20)\n , new Point3d (-0.20, 0, 0.20) /* c */\n , new Point3d (-0.20, 0, 0.45)\n , new Point3d ( 0.20, 0, 0.45)\n ]\n ]\n ,\n [ [ new Point3d ( 0.20, h, 0.20) /* c */\n , new Point3d ( 0.45, h, 0.20)\n , new Point3d ( 0.45, h,-0.20)\n , new Point3d ( 0.20, h,-0.20) /* c */\n , new Point3d ( 0.20, h,-0.45)\n , new Point3d (-0.20, h,-0.45)\n , new Point3d (-0.20, h,-0.20) /* c */\n , new Point3d (-0.45, h,-0.20)\n , new Point3d (-0.45, h, 0.20)\n , new Point3d (-0.20, h, 0.20) /* c */\n , new Point3d (-0.20, h, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.20)\n , new Point3d ( 0.20, h, 0.20)\n ]\n , [ new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, h, 0.20)\n ]\n , [ new Point3d ( 0.45, 0,-0.20)\n , new Point3d ( 0.45, h,-0.20)\n ]\n , [ new Point3d ( 0.20, 0,-0.20)\n , new Point3d ( 0.20, h,-0.20)\n ]\n , [ new Point3d ( 0.20, 0,-0.45)\n , new Point3d ( 0.20, h,-0.45)\n ]\n , [ new Point3d (-0.20, 0,-0.45)\n , new Point3d (-0.20, h,-0.45)\n ]\n , [ new Point3d (-0.20, 0,-0.20)\n , new Point3d (-0.20, h,-0.20)\n ]\n , [ new Point3d (-0.45, 0,-0.20)\n , new Point3d (-0.45, h,-0.20)\n ]\n , [ new Point3d (-0.45, 0, 0.20)\n , new Point3d (-0.45, h, 0.20)\n ]\n , [ new Point3d (-0.20, 0, 0.20)\n , new Point3d (-0.20, h, 0.20)\n ]\n , [ new Point3d (-0.20, 0, 0.45)\n , new Point3d (-0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n ]\n );\n};\n\nwindow.tjunction = function (offset, rot) {\n let h = -0.40;\n let rad = rot * Math.PI / 2; /* as in, `rot` is 0-3 */\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( 0.20, 0, 0.20) /* c */\n , new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, 0,-0.20)\n , new Point3d (-0.45, 0,-0.20)\n , new Point3d (-0.45, 0, 0.20)\n , new Point3d (-0.20, 0, 0.20) /* c */\n , new Point3d (-0.20, 0, 0.45)\n , new Point3d ( 0.20, 0, 0.45)\n ]\n ]\n , [ [ new Point3d ( 0.20, h, 0.20) /* c */\n , new Point3d ( 0.45, h, 0.20)\n , new Point3d ( 0.45, h,-0.20)\n , new Point3d (-0.45, h,-0.20)\n , new Point3d (-0.45, h, 0.20)\n , new Point3d (-0.20, h, 0.20) /* c */\n , new Point3d (-0.20, h, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.20)\n , new Point3d ( 0.20, h, 0.20) /* c */\n ]\n , [ new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, h, 0.20)\n ]\n , [ new Point3d ( 0.45, 0,-0.20)\n , new Point3d ( 0.45, h,-0.20)\n ]\n , [ new Point3d (-0.45, 0,-0.20)\n , new Point3d (-0.45, h,-0.20)\n ]\n , [ new Point3d (-0.45, 0, 0.20)\n , new Point3d (-0.45, h, 0.20)\n ]\n , [ new Point3d (-0.20, 0, 0.20)\n , new Point3d (-0.20, h, 0.20) /* c */\n ]\n , [ new Point3d (-0.20, 0, 0.45)\n , new Point3d (-0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n ]\n ).rotate(rad);\n};\n\nwindow.corner = function (offset, rot) {\n let h = -0.40;\n let rad = rot * Math.PI / 2; /* as in, `rot` is 0-3 */\n return new RoomBox\n ( offset\n ,\n [ [ new Point3d ( 0.20, 0, 0.20) /* c */\n , new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, 0,-0.20)\n , new Point3d (-0.20, 0,-0.20) /* c */\n , new Point3d (-0.20, 0, 0.45)\n , new Point3d ( 0.20, 0, 0.45)\n ]\n ]\n ,\n [ [ new Point3d ( 0.20, h, 0.20)\n , new Point3d ( 0.45, h, 0.20)\n , new Point3d ( 0.45, h,-0.20)\n , new Point3d (-0.20, h,-0.20)\n , new Point3d (-0.20, h, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.20)\n , new Point3d ( 0.20, h, 0.20)\n ]\n , [ new Point3d ( 0.45, 0, 0.20)\n , new Point3d ( 0.45, h, 0.20)\n ]\n , [ new Point3d ( 0.45, 0,-0.20)\n , new Point3d ( 0.45, h,-0.20)\n ]\n , [ new Point3d (-0.20, 0,-0.20)\n , new Point3d (-0.20, h,-0.20)\n ]\n , [ new Point3d (-0.20, 0, 0.45)\n , new Point3d (-0.20, h, 0.45)\n ]\n , [ new Point3d ( 0.20, 0, 0.45)\n , new Point3d ( 0.20, h, 0.45)\n ]\n ]\n ).rotate(rad);\n};\n\nfunction rgba (r,g,b,a) {\n this.r = r;\n this.g = g;\n this.b = b;\n this.a = a;\n}\nrgba.prototype.asString = function () {\n return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";\n}\nrgba.prototype.multAlpha = function (a) {\n return new rgba(this.r, this.g, this.b, this.a * a);\n}\n\nwindow.zoneData =\n [ { name: "Old Chapel Hill"\n , colors:\n { floor: new rgba(0,255,125,0.25)\n , outline: new rgba(0,127,255,0.50)\n , walls: new rgba(0,255,125,0.18)\n }\n }\n , { name: "Market Street"\n , colors:\n { floor: new rgba(255,255,125,0.25)\n , outline: new rgba(255,127,255,0.50)\n , walls: new rgba(255,255,127,0.18)\n }\n }\n , { name: "Shores of the Underground River"\n , colors:\n { floor: new rgba(0,127,127,0.25)\n , outline: new rgba(0,63,255,0.50)\n , walls: new rgba(0,127,127,0.18)\n }\n }\n , { name: "Escapeway" }\n , { name: "Lower Market" }\n ];\n\n\nwindow.mapData =\n [\n { id: 0\n , room: crossing (new Point3d (0, 0, 0))\n , name: "Saint's Walk"\n , exits:\n [ { to: 1, toExit: 0, pos: new Point3d ( 0.45, -0.10, 0 ) }\n , { to: 3, toExit: 0, pos: new Point3d ( 0 , -0.10, 0.45) }\n , { to: 5, toExit: 0, pos: new Point3d (-0.45, -0.10, 0 ) }\n , { to: 7, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45) }\n ]\n , zone: 0\n }\n , { id: 1\n , room: hall (new Point3d ( 1, 0, 0), "X")\n , name: "Hunger for the Flesh"\n , npcs:\n [ { twee: "A [[beast in churchclothes|dialog][$_conv = $maskedChurchman]] slouches down the boulevard, skulking behind the pillars." }\n ]\n , exits:\n [ { to: 0, toExit: 0, pos: new Point3d (-0.45, -0.10, 0) }\n , { to: 2, toExit: 0, pos: new Point3d (0.45, -0.10, 0) }\n ]\n , zone: 0\n }\n , { id: 2\n , room: octogon (new Point3d ( 2, 0, 0), 0.6)\n , name: "Hall of Sleeping Dead"\n , exits:\n [ { to: 1, toExit: 1, pos: new Point3d (-0.45, -0.10, 0) }\n ]\n , zone: 0\n }\n , { id: 3\n , room: hall (new Point3d ( 0, 0, 1), "Z")\n , name: "Golden Serpents Intertwined"\n , exits:\n [ { to: 0, toExit: 1, pos: new Point3d (0, -0.10, -0.45) }\n , { to: 4, toExit: 0, pos: new Point3d (0, -0.10, 0.45) }\n ]\n , zone: 0\n }\n , { id: 4\n , room: octogon (new Point3d ( 0, 0, 2), 0.6)\n , name: "Hall of Lost Martyrs"\n , exits:\n [ { to: 3, toExit: 1, pos: new Point3d (0, -0.10, -0.45) }\n ]\n , zone: 0\n }\n , { id: 5\n , room: stairs (new Point3d (-1, 0, 0), "X", 0.5)\n , name: "Before the Fall"\n , exits:\n [ { to: 0, toExit: 2, pos: new Point3d (0.45, -0.10, 0) }\n , { to: 6, toExit: 0, pos: new Point3d (-0.45, -0.60, 0) }\n ]\n , zone: 0\n }\n , { id: 6\n , room: cuboid (new Point3d (-2.5,-0.5, 0), new Point3d (0.95, 0.90, 0.45)).pitchCeiling (0.45, "X")\n , name: "Chapel of the Lionheart"\n , exits:\n [ { to: 5, toExit: 1, pos: new Point3d (0.95, -0.1, 0) }\n ]\n , zone: 0\n }\n , { id: 7\n , room: hall (new Point3d ( 0, 0,-1), "Z")\n , name: "White Rose Street"\n , exits:\n [ { to: 0, toExit: 3, pos: new Point3d (0, -0.10, 0.45) }\n , { to: 8, toExit: 0, pos: new Point3d (0, -0.10, -0.45) }\n ]\n , zone: 0\n }\n , { id: 8\n , room: corner (new Point3d ( 0, 0,-2), 0)\n , name: "Prayer Corner"\n , exits:\n [ { to: 7, toExit: 1, pos: new Point3d (0, -0.10, 0.45) }\n , { to: 9, toExit: 0, pos: new Point3d (0.45, -0.10, 0) }\n ]\n , zone: 0\n }\n\n , { id: 9\n , room: new RoomBox (new Point3d ( 1.25, 0.5, -2)\n ,\n [ [ new Point3d ( 0.20, 0 , 0.20)\n , new Point3d ( 0.20, 0 ,-0.20)\n , new Point3d (-0.70, -0.5,-0.20)\n , new Point3d (-0.70, -0.5, 0.20)\n ]\n , [ new Point3d ( 0.20, 0 , 0.20)\n , new Point3d ( 0.20, 0 ,-0.20)\n , new Point3d ( 0.05, 0 ,-0.20)\n , new Point3d ( 0.05, 0 ,-0.45)\n , new Point3d ( 0.65, 0 ,-0.45)\n , new Point3d ( 0.65, 0 , 0.20)\n ]\n ]\n ,\n [ [ new Point3d ( 0.20, -0.9, 0.20)\n , new Point3d (-0.70, -0.9, 0.20)\n , new Point3d (-0.70, -0.9,-0.20)\n , new Point3d ( 0.05, -0.9,-0.20)\n , new Point3d ( 0.05, -0.9,-0.45)\n , new Point3d ( 0.65, -0.9,-0.45)\n , new Point3d ( 0.65, -0.9, 0.20)\n ]\n , [ new Point3d (-0.70, -0.5, -0.20)\n , new Point3d (-0.70, -0.9, -0.20)\n ]\n , [ new Point3d (-0.70, -0.5, 0.20)\n , new Point3d (-0.70, -0.9, 0.20)\n ]\n , [ new Point3d ( 0.05, 0 ,-0.20)\n , new Point3d ( 0.05, -0.9,-0.20)\n ]\n , [ new Point3d ( 0.05, 0 ,-0.45)\n , new Point3d ( 0.05, -0.9,-0.45)\n ]\n , [ new Point3d ( 0.65, 0 ,-0.45)\n , new Point3d ( 0.65, -0.9,-0.45)\n ]\n , [ new Point3d ( 0.65, 0 , 0.20)\n , new Point3d ( 0.65, -0.9, 0.20)\n ]\n ]\n ).withFloorOutline (\n [ new Point3d ( 0.20, 0 , 0.20)\n , new Point3d (-0.70, -0.5, 0.20)\n , new Point3d (-0.70, -0.5,-0.20)\n , new Point3d ( 0.20, 0 ,-0.20)\n , new Point3d ( 0.05, 0 ,-0.20)\n , new Point3d ( 0.05, 0 ,-0.45)\n , new Point3d ( 0.65, 0 ,-0.45)\n , new Point3d ( 0.65, 0 , 0.20)\n ])\n , name: "Mason's Rise"\n , desc: ["The street here begins a zig-zagging ascent up the old chapel hill, and in a sharp line of slightly-uneven masonry near the top abruptly changes from the cobblestone and stone the typifies the market street neighborhood to the much older, weathered marble of the old churchbuildings."\n ]\n , exits:\n [ { to: 8, toExit: 1, pos: new Point3d (-0.70, -0.60, 0)}\n , { to: 10, toExit: 0, pos: new Point3d (0.25, -0.10, -0.45)}\n ]\n , zone: 1\n }\n , { id: 10\n , room: multipath (new Point3d ( 1.5, 0.5,-3), "SE")\n , name: "Road of Bones"\n , exits:\n [ { to: 9, toExit: 1, pos: new Point3d (0, -0.10, 0.45)}\n , { to: 11, toExit: 0, pos: new Point3d (0.45, -0.10, 0)}\n , { to: 18, toExit: 1, pos: new Point3d (0, -0.10,-0.20)}\n ]\n , zone: 1\n }\n , { id: 11\n , room: multipath (new Point3d ( 2.5, 0.5,-3), "EW")\n , name: "The Executioner's Alley"\n , desc: ["A narrow alley on the edge of Market Street that is now, after construction some sixty years ago, the only access through to the old chapel hill. Moss grows between the old flagstones, and ivy covers the walls, with only a sliver of sky visible between the buildings on both sides."\n , "The alley's name predates one of the Royal's calling himself \"The Executioner\". It's assumed he named himself after the alley; it's definitely not the other way around."]\n , exits:\n [ { to: 10, toExit: 1, pos: new Point3d (-0.45, -0.10, 0)}\n , { to: 12, toExit: 0, pos: new Point3d (0.45, -0.10, 0)}\n ]\n , zone: 1\n }\n , { id: 12\n , room: multipath (new Point3d ( 3.5, 0.5,-3), "NSEW")\n , name: "Calm Street Crossing"\n , desc: ["There are rut marks worn into the pavingstones underfoot from decades of wagons. Market stalls edge into the main thoroughfare. Buildings loom on all sides."]\n , exits:\n [ { to: 11, toExit: 1, pos: new Point3d (-0.45, -0.10, 0)}\n , { to: 13, toExit: 0, pos: new Point3d (0, -0.10, -0.45)}\n , { to: 14, toExit: 0, pos: new Point3d (0, -0.10, 0.45)}\n , { to: 15, toExit: 0, pos: new Point3d (0.45, -0.10, 0)}\n ]\n , zone: 1\n }\n , { id: 13\n , room: multipath (new Point3d ( 3.5, 0.5,-4), "SE")\n , name: "Baker's Road"\n , desc: ["The street wraps around a tiny elevated park; there's room for little more than two trees and a bench. The walls up are thick with ivy; moss has grown in all the cracks of the tiles. The park is just high enough to see over the roof of the next building, and from there you can see the city's shape unfold around you, sunlight running through streets like channels of molten gold."]\n , exits:\n [ { to: 12, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 16, toExit: 0, pos: new Point3d ( 0.45, -0.10, 0)}\n , { to: 17, toExit: 0, pos: new Point3d (-0.20, -0.10, 0.10)}\n ]\n , zone: 1\n }\n , { id: 14\n , room: multipath (new Point3d ( 3.5, 0.5,-2), "NS")\n , name: "Lamplighter's Street"\n , desc: ["The street is a bridge over a lower road: that one is broken and ruined, strewn with wreckage. Patches of the highroad have slumped down below, exposing the gravel infill; where the breaks meet the edge they've been shored up with old wood."]\n , exits:\n [ { to: 12, toExit: 2, pos: new Point3d (0, -0.10, -0.45)}\n , { to: 41, toExit: 0, pos: new Point3d (0, -0.10, 0.45)}\n ]\n , zone: 1\n }\n , { id: 15\n , room: cuboid (new Point3d ( 4.5, 0.5,-3), new Point3d (0.45, 0.45, 0.45)).pitchCeiling (0.30, "Z")\n , name: "The House of Knives"\n , desc: ["The market flows into an old house with thrown-open doors. Its tile floor has been shattered; its second story half-removed. There are stacks of cloth shining from the upper stories. The dim candlelight gives everything a warm glow."]\n , exits:\n [ { to: 12, toExit: 3, pos: new Point3d (-0.45, -0.10, 0)}\n , { to: 16, toExit: 1, pos: new Point3d (0, -0.10, -0.45)}\n ]\n , zone: 1\n }\n , { id: 16\n , room: multipath (new Point3d ( 4.5, 0.5,-4), "NSW")\n , name: "The Shaded Way"\n , desc: ["The street twists around itself, forming a small lull, with wide, shallow stairs bracketing the street path, half in shadow from overhanging awnings. The main street passes under a tree, and then an arch, drawn into the echoing darkness of the undercity."]\n , exits:\n [ { to: 13, toExit: 1, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 15, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 20, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n ]\n , zone: 1\n }\n\n , { id: 17\n , room: cuboid (new Point3d ( 2.7, 0.5,-3.75), new Point3d (0.45, 0.35, 0.35))\n , name: "Last Hope Hall"\n , desc:\n [ "The main room of a beerhall. It's lit with dimly-burning lanterns, and the air is suffused with the scent of lamp-oil. Great beams of light cast across the floor and walls from windows on the southern wall, lighting motes of dust as they drift through the air."\n , "It's busy: there are people at tables and the bar, and the doors to the kitchens are always swinging."\n , "<<grantTopic \"Last Hope Hall\" \"locations\">>"\n ]\n , npcs:\n [ { twee: "There's a half-transformed [[young factoryworker|dialog][$_conv to $youngFactoryworker]] at a table in the corner, talking with his more-human companions." }\n ]\n , exits:\n [ { to: 13, toExit: 2, pos: new Point3d ( 0.45, -0.10,-0.10)}\n , { to: 18, toExit: 0, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 19, toExit: 0, pos: new Point3d ( 0 , -0.35, 0 )}\n ]\n , zone: 1\n }\n , { id: 18\n , room: cuboid (new Point3d ( 1.7, 0.5,-3.75), new Point3d (0.45, 0.35, 0.35)).pitchCeiling (0.30, "X")\n , name: "Furnace of Gluttony"\n , desc: ["The kitchens are hot and bustling. Giant ovens line one wall, glowing red. One wall is entirely dominated by a huge arched fireplace with a fat black iron cauldron sitting in the embers, bubbling sluggishly. There are a mess of workspaces jigsawed across the stone floor. Bundles of drying herbs and meats hang from the rafters."]\n , exits:\n [ { to: 17, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n , { to: 10, toExit: 2, pos: new Point3d (-0.20, -0.10, 0.35)}\n ]\n , zone: 1\n }\n , { id: 19\n , room: cuboid (new Point3d ( 2.7, 0.1,-3.75), new Point3d (0.45, 0.35, 0.35)).pitchCeiling (0.30, "X")\n , name: "Cheap Rooms for Rent"\n , exits:\n [ { to: 17, toExit: 1, pos: new Point3d (0, 0.10, 0)}\n ]\n , zone: 1\n }\n\n\n , { id: 20\n , room: stairs (new Point3d ( 4.5, 0.5,-5), "Z", -0.5)\n , name: "Road to Darkness"\n , desc: ["The way descends. A vast staircase spills out into the depths, each step a small plaza by itself. Above, an arch of fluted granite decorates the entrance to the underground. All the good stalls are in the undercity proper."]\n , exits:\n [ { to: 16, toExit: 2, pos: new Point3d (0, -0.10, 0.45)}\n , { to: 21, toExit: 0, pos: new Point3d (0, 0.40,-0.45)}\n ]\n , zone: 2\n }\n , { id: 21\n , room: multipath (new Point3d ( 4.5, 1,-6), "NSEW")\n , name: "Bring Forth the New World"\n , exits:\n [ { to: 20, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 22, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n , { to: 25, toExit: 0, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 30, toExit: 0, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 22\n , room: split (new Point3d ( 4.5, 1,-7.15), 0.3, 0.6, 0.3)\n , name: "Where Rats Drowned"\n , exits:\n [ { to: 21, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.60)}\n , { to: 23, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.60)}\n , { to: 26, toExit: 3, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 31, toExit: 0, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 23\n , room: multipath (new Point3d ( 4.5, 1,-8.3), "NSE")\n , name: "Hunger for the Dark"\n , npcs:\n [ { twee: "A [[lady of the night|dialog][$_conv = $ladyOfTheNight]] waits beneath a bright lamppost." }\n ]\n , exits:\n [ { to: 22, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 24, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n , { to: 32, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 24\n , room: hall (new Point3d ( 4.5, 1,-9.3), "Z")\n , name: "Sinners' Repentance"\n , exits:\n [ { to: 23, toExit: 1, pos: new Point3d (0, -0.10, 0.45)}\n ]\n , zone: 2\n }\n , { id: 25\n , room: multipath (new Point3d ( 3.5, 1,-6), "NE")\n , name: "Greymold Garden"\n , exits:\n [ { to: 21, toExit: 2, pos: new Point3d (0.45, -0.10, 0)}\n , { to: 26, toExit: 0, pos: new Point3d (0 , -0.10,-0.45)}\n ]\n , zone: 2\n }\n , { id: 26\n , room: split (new Point3d ( 3.5, 1,-7.15), 0.3, 0.6, 0.3)\n .addFloor (\n [ new Point3d ( 0.15, 0, 0.30)\n , new Point3d ( 0.15, 0,-0.30)\n , new Point3d (-0.15, 0,-0.30)\n , new Point3d (-0.15, 0, 0.30)\n ])\n .withFloorOutline (\n [ new Point3d ( 0.45, 0, 0.60)\n , new Point3d ( 0.45, 0, 0.30)\n , new Point3d ( 0.15, 0, 0.30)\n , new Point3d ( 0.15, 0,-0.30)\n , new Point3d ( 0.45, 0,-0.30)\n , new Point3d ( 0.45, 0,-0.60)\n , new Point3d (-0.45, 0,-0.60)\n , new Point3d (-0.45, 0,-0.30)\n , new Point3d (-0.15, 0,-0.30)\n , new Point3d (-0.15, 0, 0.30)\n , new Point3d (-0.45, 0, 0.30)\n , new Point3d (-0.45, 0, 0.60)\n ])\n , name: "Where the Water Runs Black"\n , exits:\n [ { to: 25, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.60)}\n , { to: 27, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.60)}\n , { to: 28, toExit: 0, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 22, toExit: 2, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 27\n , room: multipath (new Point3d ( 3.5, 1,-8.3), "WS")\n , name: "Twisted as Sin"\n , exits:\n [ { to: 26, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 29, toExit: 1, pos: new Point3d (-0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 28\n , room: split (new Point3d ( 2.5, 1,-7.15), 0.3, 0.6, 0.3)\n , name: "The River of Garbage"\n , npcs:\n [ { twee: "There's a [[bestial dockworker|dialog][$_conv = $bestialDockworker]] unloading barrels from a docked boat, one on each of his giant shoulders." }\n ]\n , exits:\n [ { to: 26, toExit: 2, pos: new Point3d ( 0.45, -0.10, 0 )}\n , { to: 29, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.60)}\n ]\n , zone: 2\n }\n , { id: 29\n , room: multipath (new Point3d ( 2.5, 1,-8.3), "SE")\n , name: "Lovers' Corner"\n , exits:\n [ { to: 28, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 27, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0)}\n ]\n , zone: 2\n }\n , { id: 30\n , room: multipath (new Point3d ( 5.5, 1,-6), "EW")\n , name: "Route of Veins"\n , exits:\n [ { to: 21, toExit: 3, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 35, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 31\n , room: split (new Point3d ( 5.5, 1,-7.15), 0.30, 0.60, 0.30)\n , name: "Where Floodwaters Flowed"\n , exits:\n [ { to: 22, toExit: 3, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 32, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.60)}\n , { to: 36, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 32\n , room: multipath (new Point3d ( 5.5, 1,-8.3), "NSW")\n , name: "Nameless Street"\n , exits:\n [ { to: 31, toExit: 1, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 23, toExit: 1, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 33, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n ]\n , zone: 2\n }\n , { id: 33\n , room: cube (new Point3d ( 5.5, 1,-9.3))\n , name: "The Dry Well"\n , exits:\n [ { to: 32, toExit: 2, pos: new Point3d ( 0 , -0.10, 0.45)}\n ]\n , zone: 2\n }\n\n , { id: 34\n , room: cube (new Point3d ( 6.5, 1,-5))\n , name: "Market Plaza"\n , exits:\n [ { to: 35, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n ]\n , zone: 2\n }\n , { id: 35\n , room: multipath (new Point3d ( 6.5, 1,-6), "NSEW")\n , name: "Where Lovers Part Ways"\n , exits:\n [ { to: 34, toExit: 0, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 30, toExit: 1, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 36, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n , { to: 38, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 36\n , room: split (new Point3d ( 6.5, 1,-7.15), 0.3, 0.6, 0.3)\n , name: "The Dark Fountain"\n , exits:\n [ { to: 35, toExit: 2, pos: new Point3d ( 0 , -0.10, 0.60)}\n , { to: 31, toExit: 2, pos: new Point3d (-0.45, -0.10, 0 )}\n , { to: 37, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.60)}\n ]\n , zone: 2\n }\n , { id: 37\n , room: multipath (new Point3d ( 6.5, 1,-8.3), "SE")\n , name: "Street of Sinful Souls"\n , exits:\n [ { to: 36, toExit: 2, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 40, toExit: 1, pos: new Point3d ( 0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n\n , { id: 38\n , room: multipath (new Point3d ( 7.5, 1,-6), "NEW")\n , name: "Revelation of the Deep"\n , exits:\n [ { to: 39, toExit: 1, pos: new Point3d ( 0 , -0.10,-0.45)}\n , { to: 35, toExit: 3, pos: new Point3d (-0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n , { id: 39\n , room: hall (new Point3d ( 7.5, 1,-7.15), "Z", 0.60, 0.20)\n , name: "Liar's Way"\n , npcs:\n [ { twee: "A [[night watchman|dialog][$_conv = $nightWatchman]] walks through the passage, lantern held aloft." }\n ]\n , exits:\n [ { to: 40, toExit: 0, pos: new Point3d ( 0 , -0.10,-0.45)}\n , { to: 38, toExit: 0, pos: new Point3d ( 0 , -0.10, 0.45)}\n ]\n , zone: 2\n }\n , { id: 40\n , room: multipath (new Point3d ( 7.5, 1,-8.3), "SEW")\n , name: "Drink the Wine of Death"\n , exits:\n [ { to: 39, toExit: 0, pos: new Point3d ( 0 , -0.10, 0.45)}\n , { to: 37, toExit: 1, pos: new Point3d (-0.45, -0.10, 0 )}\n ]\n , zone: 2\n }\n\n\n , { id: 41\n , room: multipath (new Point3d ( 3.5, 0.5,-1), "NEW")\n , name: "Kane Street Crossing"\n , desc: [""]\n , exits:\n [ { to: 14, toExit: 0, pos: new Point3d (0, -0.10,-0.45)}\n ]\n , zone: 1\n }\n ];\n\nlet isRotating = false;\nlet tilt = 60 / 180.0 * 3.1415926;\nlet angle = -45 / 180.0 * 3.1415926;\n\nwindow.addEventListener ('mouseup', e => {\n isRotating = false;\n});\n\nwindow.renderMap = function (ix, full) {\n let currentRoom = mapData[ix];\n let adjRooms = currentRoom.exits.map (exit => exit.to);\n state.history[0].variables["_currentRoom"] = currentRoom;\n let visibleRooms = mapData.slice(0, ix).concat(mapData.slice(ix+1, mapData.length));\n /* full toggle renders all rooms vs. only the current area's visited rooms */\n if (!full) {\n visibleRooms = visibleRooms\n .filter (room => room.zone === currentRoom.zone || adjRooms.indexOf (room.id) !== -1)\n .filter (room => wasVisited (room.id));\n }\n\n let mhead = insertElement (null, 'div', null, 'mhead');\n let container = insertElement (null, 'div', null, 'mapcontainer');\n let mfoot = insertElement (null, 'div', null, 'mfoot');\n\n let ui = insertElement (mhead, 'div', 'ui', null, null);\n insertElement (ui, 'div', 'mapcaption', null, currentRoom.name);\n insertElement (ui, 'div', 'zonecaption', null, zoneData[currentRoom.zone].name);\n\n\n let mapdesc = insertElement (container, 'div', 'mapdesc', null);\n if (currentRoom.desc) {\n currentRoom.desc.map (twee => {\n let p = insertElement (null, 'p', null, null);\n new Wikifier (p, twee);\n p.normalize();\n console.log (p);\n if (hasNonEmptyChildNodes(p)) {\n mapdesc.appendChild (p);\n }\n });\n }\n\n if (currentRoom.npcs) {\n let npcList = currentRoom.npcs.map (npc => "* " + npc.twee).join(String.fromCharCode(0x20));\n new Wikifier(mapdesc, npcList);\n }\n\n insertElement (mapdesc, 'p', 'exits', null, "exits:");\n let exitList = insertElement (mapdesc, 'ul', null, 'exits');\n let exitDoms = currentRoom.exits.map ((exit, i) => {\n let eid = "r" + currentRoom.id + "_e" + i;\n let tid = "r" + exit.to;\n let eli = insertElement (null, 'li', null, null, null);\n let dir = worldDir (exit.pos);\n let elink = insertElement (eli, 'a', null, 'exitLink internalLink'\n , dir + " to " + (wasVisited (exit.to) ? mapData[exit.to].name : "???"));\n let priorColor = null;\n elink.addEventListener ('mouseover', ev => {\n let eicon = document.getElementById (eid);\n let ticon = document.getElementById (tid);\n priorColor = eicon.getAttributeNS(null, 'fill');\n if (priorColor) {\n eicon.setAttributeNS(null, 'fill', 'rgba(255,127,255,1)');\n } else {\n eicon.setAttributeNS(null, 'fill', '#ffef8f');\n }\n if (ticon) {\n ticon.setAttributeNS(null, 'stroke', 'rgba(0,126,255,0.75)');\n }\n });\n elink.addEventListener ('mouseout', ev => {\n let eicon = document.getElementById (eid);\n let ticon = document.getElementById (tid);\n if (priorColor === null) {\n eicon.removeAttributeNS(null, 'fill');\n } else {\n eicon.setAttributeNS(null, 'fill', priorColor);\n }\n if (ticon) {\n ticon.setAttributeNS(null, 'stroke', null);\n }\n });\n elink.addEventListener ('click', ev => {\n state.history[0].variables["_pos"] = exit.to;\n state.history[0].variables["_enteredFrom"] = "r" + exit.to + "_e" + exit.toExit;\n reshow ("mapblock");\n });\n return {dom: eli, dir: dir};\n });\n exitDoms.sort ((a, b) => {\n let sortDir =\n [ "north", "northwest", "west", "southwest", "south", "southeast", "east", "northeast", "up", "down", "inside"];\n let ad = sortDir.indexOf (a.dir);\n let bd = sortDir.indexOf (b.dir);\n return ad - bd;\n });\n exitDoms.map (e => exitList.appendChild (e.dom));\n\n let svgContainer = insertElement (container, "div", "mapframe", null);\n\n let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");\n svg.setAttributeNS (null, 'viewBox', "0 0 600 384");\n\n svgContainer.appendChild (svg);\n insertElement (svgContainer, "span", null, null, "(click & drag to rotate)");\n\n let camera = euler(tilt, angle, 0).scale(72);\n\n let start = new Point2d (0, 0);\n let offset = new Point2d (0, 0);\n let rect = null;\n\n svg.addEventListener ('mousedown', e => {\n isRotating = true;\n rect = svg.getBoundingClientRect();\n start = new Point2d (e.clientX - rect.x, e.clientY - rect.y);\n });\n svg.addEventListener ('mousemove', e => {\n if (!isRotating) {\n return;\n }\n offset = new Point2d (e.clientX - rect.x, e.clientY - rect.y);\n /* have it so you're rotating in the direction of the drag regardless of position (i.e., left -> right being clockwise in top half and left -> right being counterclockwise in bottom half) (this leaves a weird dead zone through the center of the map though) */\n let yBalance = Math.sin ((offset.y / rect.height * Math.PI) - Math.PI / 2) * -1;\n\n let t = offset;\n offset = offset.minus (start).clamp(30);\n start = t;\n tilt = tilt + offset.y * 0.004;\n angle = angle + offset.x * 0.004 * yBalance;\n\n tilt = Math.max (0, Math.min (Math.PI / 2, tilt));\n\n camera = euler(tilt, angle, 0).scale(72);\n renderRooms (svg, camera);\n });\n\n function renderRooms (svg, camera) {\n while (svg.firstChild) {\n svg.removeChild (svg.firstChild);\n }\n let centerOffset = camera.tvec3 (currentRoom.room.offset);\n\n let res = currentRoom.room.render (camera);\n res.floor.setAttributeNS(null, 'fill', zoneData[currentRoom.zone].colors.floor.asString());\n res.floor.id = "r" + currentRoom.id;\n if (res.floorOutline) {\n res.floorOutline.setAttributeNS(null, 'fill', 'none');\n res.floorOutline.setAttributeNS(null, 'stroke', zoneData[currentRoom.zone].colors.outline.asString());\n res.floorOutline.setAttributeNS(null, 'stroke-width', '2');\n } else {\n res.floor.setAttributeNS(null, 'stroke', zoneData[currentRoom.zone].colors.outline.asString());\n res.floor.setAttributeNS(null, 'stroke-width', '2');\n }\n\n res.outlines.setAttributeNS(null, 'fill', 'none');\n res.outlines.setAttributeNS(null, 'stroke', zoneData[currentRoom.zone].colors.walls.asString());\n res.outlines.setAttributeNS(null, 'stroke-width', '2');\n\n let exits = document.createElementNS("http://www.w3.org/2000/svg", "g");\n exits.setAttributeNS(null, 'fill', 'rgba(127,127,127,1)');\n let exitSides = document.createElementNS("http://www.w3.org/2000/svg", "g");\n exitSides.setAttributeNS(null, 'fill', 'none');\n currentRoom.exits.map ((exit, i) => {\n let c = document.createElementNS("http://www.w3.org/2000/svg", "circle");\n /* this is maybe a little messy wrt manually doing this vs. having a room function to calc all the points */\n let cpt = camera.tvec3 (exit.pos.plus (currentRoom.room.offset));\n c.setAttributeNS(null, 'cx', cpt.x);\n c.setAttributeNS(null, 'cy', cpt.z);\n c.setAttributeNS(null, 'r', 3);\n c.id = "r" + currentRoom.id + "_e" + i;\n // the exit you entered this room from\n if (c.id === state.history[0].variables["_enteredFrom"]) {\n c.setAttributeNS(null, 'fill', 'rgba(255,0,127,1)');\n }\n exits.appendChild (c);\n\n let a = document.createElementNS("http://www.w3.org/2000/svg", "circle");\n /* ditto */\n let exitRoom = mapData[exit.to];\n let exitSide = camera.tvec3 (exitRoom.exits[exit.toExit].pos.plus (exitRoom.room.offset));\n a.setAttributeNS(null, 'cx', exitSide.x);\n a.setAttributeNS(null, 'cy', exitSide.z);\n a.setAttributeNS(null, 'r', 3);\n a.id = "r" + exit.to + "_e" + exit.toExit;\n exitSides.appendChild (a);\n });\n\n let tg = document.createElementNS("http://www.w3.org/2000/svg", "g");\n tg.setAttributeNS(null, 'transform', 'translate('\n + (300 - centerOffset.x) + " "\n + (192 - centerOffset.z) + ')');\n\n let adjGroups = [];\n visibleRooms.map ((r, i) => {\n if (!adjGroups[r.zone]) {\n adjGroups[r.zone] = [];\n }\n adjGroups[r.zone].push (r);\n });\n adjGroups.map (g => {\n let floorG = document.createElementNS("http://www.w3.org/2000/svg", "g");\n floorG.setAttributeNS(null, 'fill', 'none');\n floorG.setAttributeNS(null, 'stroke', zoneData[g[0].zone].colors.outline.multAlpha(0.5).asString());\n floorG.setAttributeNS(null, 'stroke-width', '2');\n let wallG = document.createElementNS("http://www.w3.org/2000/svg", "g");\n wallG.setAttributeNS(null, 'fill', 'none');\n wallG.setAttributeNS(null, 'stroke', zoneData[g[0].zone].colors.walls.multAlpha(0.5).asString());\n wallG.setAttributeNS(null, 'stroke-width', '2');\n g.map (adj => {\n let adjRes = adj.room.render (camera);\n let floor = null;\n let outlines = null;\n if (adjRes.floorOutline) {\n floor = adjRes.floorOutline;\n } else {\n floor = adjRes.floor;\n }\n floor.id = "r" + adj.id;\n outlines = adjRes.outlines;\n floorG.appendChild (floor);\n wallG.appendChild (outlines);\n });\n tg.appendChild (floorG);\n tg.appendChild (wallG);\n });\n\n tg.appendChild (res.floor);\n if (res.floorOutline) {\n tg.appendChild (res.floorOutline);\n }\n tg.appendChild (res.outlines);\n tg.appendChild (exits);\n tg.appendChild (exitSides);\n\n svg.appendChild (tg);\n }\n renderRooms (svg, camera);\n return [mhead, container, mfoot];\n}\n\nfunction parseArg (str) { return (str[0] == "$"? eval(Wikifier.parse(str)) : str); }\n\nmacros.citymap = {\n handler: function (place, macroName, params, parser) {\n var mapID = params[0] !== undefined ? parseArg(params[0]) : null;\n let mapDOMs = renderMap(mapID, state.history[0].variables["_debug"]);\n mapDOMs.map (elem => place.insertBefore (elem, null));\n }\n};
<<nobr>>\n<<set $_debug to false>>\n\n<<set $_pos to 41>>\n<<set $_enteredFrom to null>>\n<<set $_visited to replicate (0, Math.ceil (mapData.length / 32))>>\n\n<<set $facts =\n [ "is your name"\n , "is your occupation"\n ]>>\n<<set $locations =\n [\n ]>>\n<<set $events =\n [\n ]>>\n<<set $people =\n [ "suspicious people"\n ]>>\n<<set $persons =\n [\n ]>>\n<<set $times =\n [\n ]>>\n\n<<set $rumormonger =\n { name: "rumormonger"\n , topics:\n { "do you know...":\n { "any...":\n { "non-wolf beasts": "rumormonger non-wolf"\n , "ashen beasts": "rumormonger ashen beasts"\n , "spark beasts": "rumormonger spark beasts"\n , "beasts who can hide as humans": "rumormonger beasts who hide"\n }\n }\n }\n }>>\n<<set $drunkard =\n { name: "drunkard"\n , topics:\n { "do you know...":\n { "any...":\n { "non-wolf beasts": "drunkard non-wolf"\n }\n }\n }\n }>>\n<<set $worker =\n { name: "worker"\n , topics:\n { "do you know...":\n { "any...":\n { "non-wolf beasts": "worker non-wolf"\n , "ashen beasts": "worker ashen beasts"\n }\n }\n }\n }>>\n\n<<set $goodbye_spec =\n { name: "goodbye_spec"\n , topics:\n { "goodbye": "goodbye interrupt"\n }\n }>>\n<<set $know_nothing =\n { name: "know_nothing"\n , topics:\n { "*": "know nothing"\n }\n }>>\n<<set $human =\n { name: "human"\n , topics:\n { "": "human intro"\n , "do you know...":\n { "any...":\n { "unturned humans": "human know humans"\n , "incomplete beasts": "human know altered"\n , "beasts": "human know beasts"\n }\n }\n }\n }>>\n\n<<set $factoryworkerUnique =\n { name: "young factoryworker (unique)"\n , topics:\n { "": "transformed young factoryworker splash"\n , "have you heard of...":\n { "the beast-masked hunter": "tfed young factoryworker heard of beast-masked"\n , "the beast-masked hunter's latest victim": "tfed young factoryworker heard of beast-masked victim"\n }\n , "do you know...":\n { "the beast-masked hunter": "tfed young factoryworker know beast-masked"\n , "the beast-masked hunter's latest victim": "tfed young factoryworker know beast-masked victim"\n }\n , "where is...":\n { "the beast-masked hunter's latest victim": "tfed young factoryworker where is beast-masked victim"\n }\n , "have you seen...":\n { "the beast-masked hunter": "tfed young factoryworker have you seen beast-masked"\n , "the beast-masked hunter's latest victim": "tfed young factoryworker have you seen beast-masked victim"\n }\n , "where were you...":\n { "*" : "tfed young factoryworker time"\n }\n }\n }>>\n\n<<set $bestialdockworkerUnique =\n { name: "bestial dockworker (unique)"\n , topics:\n { "": "bestial dockworker splash"\n , "goodbye": "bestial dockworker goodbye"\n , "do you know...":\n { "the beast-masked hunter's latest victim": "bestial dockworker know beast-masked victim"\n }\n , "where is...":\n { "the beast-masked hunter's latest victim": "bestial dockworker where is beast-masked victim"\n }\n , "have you seen...":\n { "the beast-masked hunter's latest victim": "bestial dockworker seen beast-masked victim"\n }\n , "where were you...":\n { "last night, after second bell": "bestial dockworker where last night second bell"\n }\n }\n }>>\n\n<<set $genericBeastmasked =\n { name: "generic beast-masked hunter knowledge"\n , topics:\n { "have you heard of...":\n { "the beast-masked hunter": "generic heard of beast-masked"\n , "the beast-masked hunter's latest victim": "generic heard of beast-masked victim"\n }\n }\n }>>\n\n<<set $nightwatchmanUnique =\n { name: "transformed night watchman (unique)"\n , topics:\n { "": "transformed night watchman splash"\n }\n }>>\n<<set $maskedChurchmanUnique =\n { name: "masked churchman (unique)"\n , topics:\n { "": "masked churchman splash"\n }\n }>>\n<<set $ladyNightUnique =\n { name: "lade of the night (unique)"\n , topics:\n { "": "lady of the night splash"\n }\n }>>\n\n\n<<set $youngFactoryworker =\n { title: "Young Factoryworker"\n , dialog: [$factoryworkerUnique, $goodbye_spec, $know_nothing]\n }>>\n<<set $bestialDockworker =\n { title: "Bestial Dockworker"\n , dialog: [$bestialdockworkerUnique, $genericBeastmasked, $goodbye_spec, $know_nothing]\n }>>\n<<set $nightWatchman =\n { title: "Night Watchman"\n , dialog: [$nightwatchmanUnique, $goodbye_spec, $know_nothing]\n }>>\n<<set $maskedChurchman =\n { title: "Masked Churchman"\n , dialog: [$maskedChurchmanUnique, $genericBeastmasked, $goodbye_spec, $know_nothing]\n }>>\n<<set $ladyOfTheNight =\n { title: "Lady of the Night"\n , dialog: [$ladyNightUnique, $goodbye_spec, $human, $know_nothing]\n }>>\n\n<<set $genericHuman =\n { title: "Idler"\n , dialog: [$rumormonger, $human, $goodbye_spec, $know_nothing]\n }>>\n<<endnobr>>
<<set $interrupt to true>>\nblah blah, goodbye\n\n[[→|map]]
"know nothing" fallback
<<display "masked hunter intro blurb">>\n\n[[→|map]]
There's been a break in the case.\n\nVarious members of the Kings of Kane Street and Red Street Regents, two street gangs with aspirations of being allied with the Royals, have been turning up dead. Each of the victims have been beasts, and all looking overpowered, bodily torn apart by claws.\n\nThere's been a rumor of a hidden hunter: one who wears a leather beast mask and fights with false claws forged from star-metal. This <<clue "beast-masked hunter">> has been picking off the gangs one by one. The going theory is this is sending a message to the Royals, or maybe the hunter working his way through the lesser gangs before trying bigger fry. The gangs are out for vengeance, and the Royals -- though they would never show it -- are undoubtedly getting uneasy.\n\nThere have been aspirants, beast and human alike, looking for the hunter for months: for revenge, to offer up his head to the Royals for protection, or for their own unknown motives. You're one of the latter.\n\nLast night there was an altercation in the undermarket. There's a new dead beast. You're not sure who, but there's a different buzz. <<clue "There's something different about this latest victim">>, though you don't know what, yet.\n\n<<grantTopic "the beast-masked hunter" "persons">>\n<<grantTopic "the beast-masked hunter's latest victim" "persons">>\n<<grantTopic "the latest killing by the beast-masked hunter" "events">>
<div class="npc_resp">Yeah, of course. Everybody has.</div>
He jolts forward.\n<div class="npc_resp">Of course! He's killed again, the menace! Killed <<clue "in the night">></div>\n<<autoresponse "In the night?">>\nHe sneers at you.\n<div class="npc_resp">What, you're trying an //investigation//? It was <<clue "just after second bell">>. Later when <<clue "we">> found the body.</div>\n\n<<grantTopic "last night, after second bell" "times">>
<div class="npc_resp">If I knew him, he'd be dead.</div>\n/% / <<display "tfed young factoryworker angry ask know">> %/
<div class="npc_resp">Fuck off; it's none of your business who I know. Or //knew//.</div>\n/% (+ heat up) %/
<div class="npc_resp">He was killed down in the undercity, at <<clue "Where Rats Drowned">>. <<clue "We've">> dragged the body away already.</div>
<div class="npc_resp">If I saw him, I'd've killed him.</div>\n/% / <<display "tfed young factoryworker angry ask know">> %/
<div class="npc_resp">//Seen// him? yeah, I've seen him. His body was <<clue "torn up all over">>. Clawed apart by that fucking hunter.</div>\n\n<<autoresponse "Torn up all over?">>\n\n<div class="npc_resp">The hunter got him <<clue "in the back">>. Shredded him up good.</div>
<div class="npc_resp">It's none of your fucking business, where I was or wasn't.</div>
{asking again or after the other personal-knowledge questions :}\n<div class="npc_resp">do i gotta repeat myself? i'd've killed him, if i knew where to get at him</div> (+ heat up)
<div class="npc_resp">Yeah, he's that mysterious hunter, right? Nobody's really seen him, though.</div>\n<div class="npc_resp">Still, can't argue with that many bodies.</div>
<div class="npc_resp">Yeah, last night, right? There was a big crowd down in the undercity.</div>\n<div class="npc_resp">Sounded like some people found the body while it was still warm.</div>
<div class="npc_resp">The guy he killed, it was <<clue "Mikhael the Crusher">>, right? That's what everybody is saying.</div>\n\n<<autoresponse "So, do you know him?">>\n\n<div class="npc_resp">Know //of// him, maybe. I'm not associated with those folks.</div>
<div class="npc_resp">He was killed over at <<clue "Where Rats Drowned">>, but the <<clue "Kane Street gang">> dragged him off before sunup.</div>
<div class="npc_resp">What, after he got himself killed? Nah. Heard he was <<clue "all clawed up">>, though.</div>
<div class="npc_resp">Working here. I heard some yelling further upriver, but figured it was just the usual drunkards arguing, until it got real loud.</div>\n\n<<autoresponse "Did you see anything unusual?">>\n\n<div class="npc_resp">What, aside from the crowd? <<clue "No, nothing">>.</div>
<div class="npc_title"><<if $_conv.name>><<print $_conv.name>>: <<endif>><<print $_conv.title>></div>
<<display "intro blurb">>\nThe city fell only recently, but the rot has been bubbling beneath for years.\n\nHe's a youth, one of many working at the factory or foundry -- not older than twenty, but so long infected that he's grown up more beast than human. When he stands up his body unfurls, looming a good seven feet tall: a gaunt, lanky monster with clawed limbs, gleaming eyes, and a mouth full of fangs. But it's easy enough to forget that when he's slumped down, sprawled out, limbs tucked in, eyes shaded from the light, chatting like any other youth. Only the way he tilts his head side-to-side, like a confused dog, when he hears you approach gives him away.<<nobr>>\n\n<div class="npc_resp">What do you want?</div>\n\n<<autoresponse "I just have a few questions for you...">>\n\nHe glowers.\n\n<div class="npc_resp">About what?</div>\n\n<<autoresponse "I'm looking for the beast-masked hunter.">>\n\nHe lets out a baying cackle of a laugh.\n\n<div class="npc_resp">You and everybody else. You think I know anything? Go ahead, ask away.</div>\n\n<<endnobr>>
<<display "intro blurb">>\nA dockworker who shows signs of infection.\n\nLike many dockworkers, he wears only rough cloth pants and a pair of worn sandals. He's thickly muscled from laboring, and his upper body is coated in a uniform layer of coarse dark hair. His face looks like a wolf's head that's been squashed in: his nose is giant, sharply crooked like it's been broken; his lips are tinged a dark purple-brown, like a dog's blackened lips. When he speaks, or pants for breath, he reveals a mouth crammed full of too many sharp teeth. He's got a shaggy mane of a beard that extends up to cover nearly his entire face: coming up high over his cheeks, with his hairline extending down nearly to his heavy brows.\n\nHe looks over at you.<<nobr>>\n/% !!first time: %/\n\n<div class="npc_resp">What?</div>\n\n<<autoresponse "I got some questions, if you have the time.">>\n\nHe snorts, making a half-mocking laugh.\n<div class="npc_resp">You wanna play detective? Sure, ask away.</div>\n\n/% !!repeated visit, without fucking him:\n<div class="npc_resp">You again. Asking questions not working out so good for you, huh?</div>\n\n<<autoresponse "i just have a few more things to ask...">>\n\n!!repeated visit, after having sex with him:\n<div class="npc_resp">You again.</div>\nHe leers, slinging the barrel over his shoulder down on its pallet with a loud //crash//. if you sucked his dick: He gropes his crotch with his newly-free hand, outlining the bloated mound of his sheath.\notherwise: He turns to face you, and his sheath is mounded up against the front of his pants, cocktip wetly oozing pre through the coarse fabric. He gives it a squeeze, milking out a darkening stain.\n\n<div class="npc_resp">Back for more, huh?</div>\n\n<div class="responses"><<response "i just have a few more things to ask...">>\n<<response "yeah please fuck my throat">></div>\n%/\n<<endnobr>>
<<set $interrupt to true>>\n<div class="npc_resp">What, that all the questions you got?</div>\n\n/%if you've raised heat by >2 pts in this convo: %/\\n<<autoresponse "Yeah, that's all for now">>\n\nYou head off.\n[[→|map]]\n/%\notherwise:\nhe gives you a clear once-over, eyes gleaming green-red when they catch the light, mouth opening in a bestial leer.\n\n<div class="responses"><<response "Yeah, that's all for now">>\n<<response "actually i got one more question; you wanna fuck around?">></div>\n%/
<dialog "How's your little beast hunt going?">>\n\nfound him\nhaven't found him
<div class="npc_resp">In the undercity for more //pedestrian// reasons, eh?</div>\nHe snorts and gropes his crotch.\n<<dialog "I got some time before my shift's up; you can blow me if you want.">>\n\nHis gaze roves, from the docked ship to the bays between the vaults of the riverside path, and, specifically, at a stacked pallet with a haphazard line of barrels next to it, forming a barrier half a person tall. He jerks his head to the side:\n<<dialog "Behind those barrels.">>\n\nHis lower half is even more transformed. His coarse hair thickens into shaggy fur, entirely coating his legs, and his feet are starting to warp and arch up into a beast's paws.\n\nUnsurprisingly, he has a beast's cock: sheath fat and heavy between his thighs, oversized balls lopsided in a drooping sac that hangs most of the way to his knees. He cants his hips and his balls lurch over to slap against his thigh, the nearly fist-sized orbs lolling against his hairy skin. His bestial cocktip pushes out into the open: flesh leeched of all color from the murky blue lights of the undercity, making his shaft appear an inky purple, with pitch-black veins. It shines in the dim light, already slick and slimy. He reaches down to squeeze his sheath milks out a cloudy droplet of pre from his sloped cocktip, smearing it across his gleaming cockhead with one clawed finger.
<<display "intro blurb">>\nA night watchman with an oil lamp clutched in his left hand.\n\nHe's visibly transformed: he's wearing a guard's coat that no longer fits the bestial contortions of his body; his chest -- his ribcage -- bulges out from his body, shaped like it fits an entirely different kind of being, and his coat hangs open, impossible to button across the protruding bulk of his animal chest. Because of the sheer stretch of his ribs, his stomach looks sunken and gaunt: taut skin tightly drawn from his bottom ribs down to his hips.\n\nHe's covered his face with a scarecrow's burlap-sack head as a mask, with stray handfuls of straw bulging out its shape, giving his head a misshapen appearance. All you can make from his facial features is that he's unshaven, with a shaggy beard that joins with the fur growing across his neck and fanning out over his shoulders, giving the appearance of wearing a fur mantle.\n\nMaybe he does it to hide his transformed face, or maybe because his animal eyes and nose can no longer tolerate the light and scented fumes from his watchman's lamp.
<<display "intro blurb">>\n{...}\n\n<div class="npc_resp">Well, aren't you looking fabulous tonight!</div>\nShe leans in closer.\n<div class="npc_resp">What do you say, darling?</div>\n\n<div class="responses">\\n<<response "I'm gay, actually." "lady gay">>\n<<response "I'm not interested in sex." "lady not interested">>\\n</div>
She rolls her eyes with a sarcastic flourish.\n<div class="npc_resp">It's always the handsome ones.</div>\n<div class="npc_resp">But gay or not, if you want some of my time, you better be prepared to pay.</div>\nYou flip her a silver.\n<<autoresponse "I have some questions.">>\n<div class="npc_resp">Well, I'll see what I can do.</div>
<div class="npc_resp">Good for you, but if you want some of my time, you better be prepared to pay.</div>\nYou flip her a silver.\n<<autoresponse "I have some questions.">>\n<div class="npc_resp">Well, I'll see what I can do.</div>
<<display "intro blurb">>\nA beast in churchclothes, slouching in all fours.\n\nHis ornate robe -- thick panels of starched white cloth, hemmed with gilt thread and tied with golden tassels -- drags on the ground, drenched in mud, hems shredded into scraps. At some point he began wearing a mask to hide his transformed face, but now to hide his muzzle he needs a half-dozen, strapped across his head in a haphazard fashion. He's slavering, and drool spills from the hollow eyes and mouths of his lower masks.
<div class="npc_title">Street Drunkard</div>\\n<ul class="npc_ranks"><li>Rank II Confabulator</li>\\n<li>Rank I Rumormonger</li></ul>\\n\nyr talking to a human, etc
"human know humans" chunk
"human know altered" chunk
"human know beasts" chunk
there are rumors that some among __the wanderers__, who camp outside town, have turned into __jackal-headed beasts__.\n\n<<grantTopic "the wanderers" "people">>\n<<grantTopic "jackal-headed beasts" "people">>\n\nthey say that down in __the gulches__ there's a __ram-headed beast__ that perches among the tombstones.\n\n<<grantTopic "the gulches" "locations">>\n<<grantTopic "ram-headed beasts" "people">>
> me? not specifically, no. there are vagrants and thieves all over the city who are ashen beasts __in disguise__ though.\n\n<<grantTopic "beasts who can hide as humans" "people">>
> are there others, besides __the executioner__? i imagine __the royals__ would be very pleased to hear of one, and equally as displeased to hear anybody's been poking around looking.\n\n<<grantTopic "the executioner" "persons">>\n<<grantTopic "the royals" "persons">>
> if i did, i certainly wouldn't tell you. those who can hide, hide to hunt, and deal brutally with those who betray their confidence. i wouldn't continue prying in those corners, if i were you.
i was passing through __the sewers__ a fortnight ago and saw the great gleaming eyes of some grotesque __rat beast__ just downstream.\n\n<<grantTopic "the sewers" "locations">>\n<<grantTopic "rat beasts" "people">>
last night, there was some drunkard outside the beerhall who was screaming about how there were giant __rat-beasts__ down in __the sewers__\n\n<<grantTopic "the sewers" "locations">>\n<<grantTopic "rat beasts" "people">>
there's __the king of kane street__; he and his gang are mostly ashen beasts. i wouldn't go looking for them if i were you, though.\n\n<<grantTopic "the king of kane street" "persons">>\n<<grantTopic "the kane street gang" "persons">>\n\nmikhel the crusher spends his nights at the bar down the street, __the hind's heart__. he's not so friendly, though.\n\n<<grantTopic "mikhel the crusher" "persons">>\n<<grantTopic "the hind's hart" "locations">>
<<nobr>>\n<<set $_granted to parameter(0)>>\n<<set $_topic to parameter(1)>>\n<<set $_check to (function (kind, topic) {\n let vartable = state.history[0].variables;\n let which = null;\n switch (kind) {\n case "locations":\n which = vartable["locations"];\n break;\n case "people":\n which = vartable["people"];\n break;\n case "persons":\n which = vartable["persons"];\n break;\n case "events":\n which = vartable["events"];\n break;\n default:\n throw new Error ("unexpected kind of \"" + kind + "\"");\n return false;\n }\n if (which.indexOf (topic) === -1) {\n which.push (topic);\n return true;\n } else {\n return false;\n }\n })($_topic, $_granted)>>\n<<if $_check>>\n''You can now ask about "<<print $_granted>>", under "<<print $_topic>>".''\n<<endif>>\n<<endnobr>>
<<if $prompt>>\\n<span class="askd"><<if $prompt !== "goodbye">><<print $prompt>><<if $what>> <<print $what>><<if $who>> <<print $who>><<endif>><<endif>>?<<else>>goodbye.<<endif>></span>\n<<endif>>
<<set $asking to false>>\\n<<set $prompt to "">>\\n<<set $what to "">>\\n<<set $who to "">>\\n/% this generates a blank statement to start, which would serve as the intro/splash text for a given encounter %/\n<<display "_response">>\\n\n<<display "build ask">>\\n<<display "construction ui">>
<<block ask>><<if !$asking>><<display "current statement">> <<display "ask click">><<else>><<display "stated">>\n<<display "_response">>\\n<<set $asking to false>>\\n\n<<display "build ask">>\\n<<endif>><<endblock>>
<<block base>><<block what>><<block who>><<if complete($prompt, $what, $who)>><span class="askq"><<replace "ask →">>\\n<<set $asking to true>>\\n<<set reshow("ask")>>\\n/% this strips away the outer layer of 'ask' wrapping, so that when we toggle 'ask' to render a new 'ask' inside of the old 'ask', we don't totally wipe the entire history when we _next_ toggle 'ask' etc. %/\\n<<set unroll (document.querySelector(".passage .ask"))>>\\n<<endreplace>></span><<endif>><<endblock>><<endblock>><<endblock>>
<<set $prompt to "">>\\n<<block base>><<set $what to "">><<set $who to "">>\\n<<block what>><<set $who to "">>\\n<<block who>>\\n<<if $prompt neq "">><div class="askcur"><<print $prompt>> <<print $what>> <<print $who>></div><<endif>>\\n<<endblock>><<endblock>><<endblock>>
<<nobr>>\n<<block asktable>><<if !$interrupt>><table><tbody><tr valign="top"><td>\n<<toggle $prompt "base" "what..." "what...">>\n<<toggle $prompt "base" "where is..." "where is...">>\n<<toggle $prompt "base" "have you heard of..." "have you heard of...">>\n<<toggle $prompt "base" "have you seen..." "have you seen...">>\n<<toggle $prompt "base" "do you know..." "do you know...">>\n<<toggle $prompt "base" "where were you..." "where were you...">>\n<<toggle $prompt "base" "goodbye" "goodbye">></td>\n<td><<block base>>\n<<if $prompt == "what...">>/% facts %/\n<<print groupList ("what",\n [ {title:"information",content:$facts}\n ])>>\n<<else if $prompt == "where is...">>/% locations, persons %/\n<<print groupList ("what",\n [ {title:"locations",content:$locations}\n , {title:"persons",content:$persons}\n ])>>\n<<else if $prompt == "where were you...">>/% times %/\n<<print groupList ("what",\n [ {title:"times",content:$times}\n ])>>\n<<else if $prompt == "have you heard of...">>/% persons, events %/\n<<print groupList ("what",\n [ {title:"persons",content:$persons}\n , {title:"events",content:$events}\n ])>>\n<<else if $prompt == "have you seen...">>/% people, persons %/\n<<print groupList ("what",\n [ {title:null,content:["any..."]}\n , {title:"persons",content:$persons}\n ])>>\n<<else if $prompt == "do you know...">>/% people, persons %/\n<<print groupList ("what",\n [ {title:null,content:["any..."]}\n , {title:"persons",content:$persons}\n ])>>\n<<else>>\n<<endif>>\n<<endblock>></td>\n<td><<block base>><<block what>><<if $what == "any...">>\n<<print groupList ("who",\n [ {title:"people",content:$people}\n ])>>\n<<else>>\n<<endif>><<endblock>><<endblock>></td></tr></tbody></table><<endif>>\n<<endblock>>\n<<endnobr>>
/% todo: when this gets run it should force a scroll to the latest question (currently it scrolls to the top of the page, which would get annoying on actual interactions) %/\\n<<set $_q = []>>\\n<<set $_q.push ($prompt)>>\\n<<if $what>><<set $_q.push ($what)>><<endif>>\\n<<if $who>><<set $_q.push ($who)>><<endif>>\\n/% asking about <<print ($_q\n .map(x => '"' + x + '"')\n .reduce((a, b) => a + ", " + b))>> %/\\n/% asking vs. <<print (function (p){\n return p.map (function (s){ return "'" + s.name + "'"; }).reduce(function(a,b) {return a + " -> " + b; });\n})($_p)>> %/\\n<<set $_a = lookupAnswer ($_q, $_conv.dialog)>>\\n<<set $interrupt to false>>\\n<<if $_a !== 0>>\\n/% got "<<print $_a.match>>" from "<<print $_a.archetype>>" %/\n<div class="resp"><<display $_a.match>></div>\\n<<else>>\\nERROR: null result; not even a fallback available\n<<endif>>\\n<<set reshow("asktable")>>
.mhead, .mfoot, .mapcontainer > * { background: #080808; margin: 0 0.165em; padding: 0.2em; }\n\n.mapcontainer { display: flex; justify-content: space-between; }\n.mapcontainer > * { flex-grow: 1; flex-basis: 50%; }\n.mapcontainer #ui { order: 1; }\n.mapcontainer #mapframe { order: 2; max-width: 50%; }\n\n.mhead, .mfoot { display: block; }\n.mhead { margin: 0 0.165em 0.33em; }\n.mfoot { margin: 0.33em 0.165em 0; }\n.mfoot p { margin: 0; }\n\nsvg { border: 2px solid #003100; }\n#mapcaption { border-bottom: 1px solid #eee576; max-width: 21em; padding-bottom: 0.2em; margin: 0 auto; }\n#zonecaption { padding-top: 0.2em; }\n\n#mapcaption { font-size: 125%; }\n\n#mapdesc p { margin: 0 0.2em 1.12em; }\n#mapdesc p#exits { margin-top: 1.5em; font-weight: bold; }\n#mapdesc p#exits:first-child { margin-top: 0em; }\n\n#zonecaption, #mapcaption, #mapframe span { display: block; text-align: center; }\nul.exits { margin: 0; padding: 0; }\nul.exits li { margin: 0 0 0 1.5em; padding: 0; }
table { vertical-align: top; }\n.asktable {\n position: relative;\n display: block;\n clear: both;\n margin: 0 -24px;\n border: 1px solid #fe8;\n border-image-source: url("./img/construct-border.png");\n border-image-slice: 24;\n border-image-width: auto;\n margin-bottom: 10em;\n background: #1f1c08;\n}\n.asktable::before, .asktable::after {\n content: "";\n display: block;\n position: absolute;\n left: -1px;\n right: -1px;\n height: 32px;\n}\n .asktable::before {\n top: -1px;\n background: no-repeat center url("./img/construct-border-top.png");\n }\n .asktable::after {\n bottom: -2px;\n background: no-repeat center url("./img/construct-border-bottom.png");\n }\ntable, tbody { border: none; }\ntr, td { }\n.gr { background: #3f3610; padding: 2px; }\n\n.askd, .askcur, .autoresponse, { font-weight: bold; color: #ffc; border-radius: 4px; padding: 0.25em; margin: 1em 3em 1em 0; }\n.clickedResponse { color: #ffc; }\n\n.responses { padding: 0.25em; border-radius: 4px; margin: 1em 3em 1em 0; }\n.autoresponse, .responses { display: block; border: 1px solid #808088; margin-bottom: 1.25em; }\n.askcur { display: inline-block; border: 1px solid #808088; margin: 1em 0; }\n.askq { display: inline-block; margin: calc(1.25em + 1px) 1.25em calc(1.25em + 1px) 0.25em; }\n.resp { }\n.askd, .autoresponse.ranResponse { border: 1px solid #eee576; }\n\n.clue { font-weight: bold; color: #fb8; }\n\n.npc_resp { border-radius: 4px; border: 1px solid #eee576; padding: 0.25em; margin: 1em 0 1em 3em; text-align: right; }\n\n.npc_title { font-weight: bold; font-size: 150%; color: #eb9; }\nul.npc_ranks { font-weight: bold; margin: 0; padding: 0; font-size: 125%; list-style: none; }\nul.npc_ranks li { margin-left: 1em; color: #fdb; }
input[type="radio"].toggleInput { display: none; }\ninput.toggleInput + label { padding: 2px; cursor: pointer; font-weight: bold; transition: 0.5s; }\n\ninput.toggleInput + label { background: transparent; color: #fe8; }\ninput.toggleInput + label:hover { background: #403810; color: #ffc; }\ninput.toggleInput:checked + label { background: #fe8; color: #000; }
@font-face {\n font-family: 'Libre Baskerville';\n font-style: normal;\n font-weight: 400;\n src: local('Libre Baskerville'), local('Libre-Baskerville'), url(./fonts/LibreBaskerville-Regular.ttf) format('truetype');\n}\n\nbody { margin: 1em 10px; font-family: "Libre Baskerville", serif; }\n#passages { margin: 0 2em; padding: 0; border: none; }\nul#sidebar { display: none; }\nhr { border: none; border-bottom: 1px solid #fe8; margin: 1.2em 4em; clear: both; }\n\na.internalLink { color: #fe8; transition: 0.5s; }\na.internalLink:hover { color: #ffc; text-shadow: 0px 0px 6px #982; text-decoration: none; }\n\n#passageStart { text-align: center; }\n#passagenew_game .body.content { text-align: center; }\n\nh1 { margin-top: 3em; }\n#passageStart ul.title { padding: 0; }\nul.title li { margin: 0; }\n.passage ul { margin-top: 0; }\n#passages .passage li { margin-right: 0; }\n\n\n\n#notes {display: block; position: fixed; bottom: 0; right: 3em; width: 15em; height: 3em; line-height: 3em; text-align: center; background: linear-gradient(to top, #fe8, transparent);\n}\n#notes a { color: #000; }\n\ntable .toggleContainer { display: block; }
window.complete = function (p1, p2, p3) {\n console.log ("complete: " + p1 + "" + p2 + "" + p3);\n if (p1 && !(p1.substr(-3) === "...")) {\n return true;\n }\n if (p1 && p2 && !(p2.substr(-3) === "...")) {\n return true;\n }\n if (p1 && p2 && p3 && !(p3.substr(-3) === "...")) {\n return true;\n }\n return false;\n}\n\nwindow.lookupAnswer = function (question, archetypes) {\n var match = null;\n for (var i = 0; i < archetypes.length; i++) {\n match = tryAnswer (question, archetypes[i]);\n if (match !== null) {\n return {archetype: archetypes[i].name, match: match};\n }\n }\n return null;\n}\n\nfunction tryAnswer (question, archetype) {\n let topic = archetype.topics;\n let answer = null;\n let qi = 0;\n\n while (qi < question.length) {\n if (topic[question[qi]]) {\n topic = topic[question[qi]];\n qi++;\n } else if (topic["*"]) {\n return topic["*"];\n } else {\n return null;\n }\n }\n if (qi >= question.length) {\n return topic;\n }\n return null;\n}
window.closeArrows = String.fromCharCode(0x3e) + String.fromCharCode(0x3e);\n\nwindow.groupList = function (target, groups) {\n return groups.map(function (group) {\n return (group.title ? '<div class="gr">' + group.title + '</div>' : '') +\n group.content.map(function (i) {\n return '<<toggle $' + target + ' "' + target + '" "' + i + '" "' + i + '"' + closeArrows;\n }).join(String.fromCharCode(0x20));\n }).join(String.fromCharCode(0x20));\n}
function innercontent(tag, parser) {\n /* this is a fucking disaster of a function but it replicates the twine behavior, which is, "to find an endblock do a plaintext search through the tweecode, totally ignorant of any structure aside from some manually-implemented depth checking". hope you don't have any literal closebrackets in yr code or else yr gonna get a lot of weird macro errors! */\n var\n i,\n textbegin = parser.source.indexOf(">>",parser.matchStart)+2,\n textend = -1,\n text = parser.source.slice(textbegin),\n depth = 0;\n for (i = 0; i < text.length; i++) {\n if (text.substr(i,tag.length + 5) === ("<<end" + tag)) {\n if(depth===0){\n textend=textbegin+i;\n break;\n }else{\n depth--;\n }\n } else if (text.substr(i,tag.length + 2) === ("<<" + tag)) {\n depth++;\n }\n }\n if (textend === -1) {\n throwError(place,"can't find matching end"+tag,parser.fullMatch());\n return;\n }\n return [textbegin, textend];\n}\n\nfunction rest(tag, parser) {\n var\n textbegin = parser.source.indexOf(">>",parser.matchStart)+2,\n text = parser.source.slice(textbegin);\n return [textbegin, textbegin + text.length];\n}\n\nfunction parseArg (str) { return (str[0] == "$"? eval(Wikifier.parse(str)) : str); }\nfunction varArg (str) { return (str[0] == "$"? str.substr(1) : str); }\nfunction varArg2 (str) {\n let params = null;\n if (str[0] == "$") {\n params = str.substring(1).split(".");\n } else {\n params = [str];\n }\n let final = params.pop();\n return ((vartable, val) => {\n let o = vartable;\n /* console.log ("setting " + val); */\n params.map (p => { /* console.log (p); */ o = o[p]; /* console.log (o); */ });\n /* console.log (final); */\n o[final] = val;\n });\n}\n\nmacros.block = {\n handler: function (place, macroName, params, parser) {\n var class_ = params[0][0] == "$"\n ? eval(Wikifier.parse(params[0]))\n : params[0];\n var block = insertElement (null, "span", null, "blockSpan " + class_.replace(" ", "_"));\n var inner_index = innercontent ("block", parser);\n block.tweecode = parser.source.slice(inner_index[0], inner_index[1]);\n parser.nextMatch=inner_index[1];\n place.insertBefore(block,null);\n new Wikifier(block, block.tweecode);\n }\n};\nmacros.endblock = { handler: function () {}};\n\nmacros.click = {\n handler: function (place, macroName, params, parser) {\n var linkText = params[0] !== undefined ? params[0] : null;\n var click = insertElement (null, "a", null, "click internalLink", linkText);\n\n var inner_index = innercontent ("click", parser);\n click.tweecode = parser.source.slice(inner_index[0], inner_index[1]);\n parser.nextMatch=inner_index[1];\n place.insertBefore(click,null);\n\n click.addEventListener ('click', function (e) {\n reshowOne (click);\n });\n }\n};\nmacros.endclick = { handler: function () {}};\n\nmacros.toggle = {\n handler: function (place, macroName, params, parser) {\n var varSet = params[0] !== undefined ? varArg2(params[0]) : null;\n var group = params[1] !== undefined ? parseArg(params[1]) : null;\n var value = params[2] !== undefined ? parseArg(params[2]) : null;\n var display = params[3] !== undefined ? parseArg(params[3]) : null;\n var pre = params[4] !== undefined ? parseArg(params[4]) : null;\n\n var c = insertElement (null, "span", null, "toggleContainer", null);\n if (pre) {\n insertText (c, pre);\n }\n var toggleInput = insertElement (c, "input", "toggle_" + group + "_" + value, "toggleInput toggleGroup_" + group, null);\n var toggleLabel = insertElement (c, "label", null, "toggleLabel", display);\n toggleInput.type = "radio";\n toggleInput.name = group;\n toggleInput.value = value;\n toggleLabel.htmlFor = "toggle_" + group + "_" + value;\n\n var changeList = [group];\n toggleInput.addEventListener ('change', function () {\n /* console.log (params[0] + " set to \"" + toggleInput.value + "\""); */\n varSet (state.history[0].variables, toggleInput.value);\n changeList.map (function (f) { /* console.log (f); */ reshow (f); });\n });\n\n place.insertBefore(c, null);\n }\n};\n\nmacros.response = {\n handler: function (place, macroName, params, parser) {\n var linkText = params[0] !== undefined ? parseArg(params[0]) : null;\n var displayPassage = params[1] !== undefined ? parseArg(params[1]) : null;\n\n let resp = insertElement (place, "span", null, "response");\n let respA = insertElement (resp, "a", null, "internalLink", linkText);\n\n let runClick = e => {\n resp.classList.add("ranResponse");\n let otherResps = Array.from (resp.parentNode.querySelectorAll(".response"));\n otherResps.map (r => {\n let respText = r.firstChild.innerText;\n if (r === resp) {\n r.removeChild (r.firstChild);\n insertElement (r, "span", null, "clickedResponse", respText);\n } else {\n r.removeChild (r.firstChild);\n insertElement (r, "span", null, "oldResponse", respText);\n }\n });\n /* is `place` what we want to place this at? (it's not, but `place.parentNode` is also not good) */\n let newPlace = document.createElement("div");\n place.parentNode.insertBefore (newPlace, place.nextSibling);\n console.log (newPlace);\n new Wikifier (newPlace, "<<display \"" + displayPassage + "\">>");\n scrollWindowTo (resp);\n };\n respA.addEventListener ('click', runClick);\n place.appendChild (resp);\n }\n};\nmacros.autoresponse = {\n handler: function (place, macroName, params, parser) {\n var linkText = params[0] !== undefined ? parseArg(params[0]) : null;\n var inner_index = rest ("autoresponse", parser);\n var after_resp = parser.source.slice(inner_index[0], inner_index[1]);\n parser.nextMatch = inner_index[1];\n\n let resp = insertElement (place, "span", null, "autoresponse");\n let respA = insertElement (resp, "a", null, "internalLink", linkText);\n\n let runClick = e => {\n resp.classList.add("ranResponse");\n resp.removeChild (respA);\n insertElement (resp, "span", null, "clickedResponse", linkText);\n /*respA.classList.add("autoClicked");\n respA.removeEventListener ('click', runClick);*/\n new Wikifier (place, after_resp);\n scrollWindowTo (resp);\n };\n respA.addEventListener ('click', runClick);\n place.appendChild (resp);\n }\n};\nmacros.clue = {\n handler: function (place, macroName, params, parser) {\n var clueText = params[0] !== undefined ? parseArg(params[0]) : null;\n insertElement (place, "span", null, "clue", clueText);\n }\n};\n\nwindow.reshow = function (name) {\n var\n rall=document.querySelectorAll(".passage .blockSpan." + name.replace(" ", "_")),\n ret=false;\n for(var i=0;i<rall.length;i++){\n ret=reshowOne(rall[i]);\n }\n return ret;\n}\n\nwindow.unroll = function (where) {\n var p = where.parentNode;\n while (where.firstChild) {\n p.insertBefore (where.firstChild, where);\n }\n p.removeChild (where);\n}\n\nfunction reshowOne (target) {\n target.innerHTML="";\n new Wikifier(target,target.tweecode);\n target.classList.add("blockSpanIn");\n if(target.timeout){\n clearTimeout(target.timeout);\n }\n target.timeout=setTimeout(\n function(){\n target.classList.remove("blockSpanIn");\n },\n 1);\n}
(function(){\n version.extensions.replaceMacrosCombined={major:1,minor:1,revision:7};\n var nullobj={handler:function(){}};\n function showVer(n,notrans){\n if(!n){return;}n.innerHTML="";\n new Wikifier(n,n.tweecode);\n n.setAttribute("data-enabled","true");\n n.style.display="inline";\n n.classList.remove("revision-span-out");\n if(!notrans) {\n n.classList.add("revision-span-in");\n if(n.timeout){\n clearTimeout(n.timeout);\n }\n n.timeout=setTimeout(function(){n.classList.remove("revision-span-in");n=null;},20);\n }\n }\n function hideVer(n,notrans){\n if(!n){return;}\n n.setAttribute("data-enabled","false");n.classList.remove("revision-span-in");if(n.timeout){clearTimeout(n.timeout);}\n if(!notrans){\n n.classList.add("revision-span-out");\n n.timeout=setTimeout(function(){\n if(n.getAttribute("data-enabled")=="false"){\n n.classList.remove("revision-span-out");\n n.style.display="none";\n n.innerHTML="";\n }\n n=null;\n },1000);\n }else{\n n.style.display="none";n.innerHTML="";n=null;\n }\n}\n\nfunction tagcontents(b,starttags,desttags,endtags,k){var l=0,c="",tg,a,i;function tagfound(i,e,endtag){for(var j=0;\nj<e.length;j++){if(a.indexOf("<<"+e[j]+(endtag?">>":""),i)==i){return e[j];}}}a=b.source.slice(k);for(i=0;i<a.length;i++){if(tg=tagfound(i,starttags)){l++;}else{if((tg=tagfound(i,desttags,true))&&l==0){b.nextMatch=k+i+tg.length+4;\nreturn[c,tg];}else{if(tg=tagfound(i,endtags,true)){l--;if(l<0){return null;}}}}c+=a.charAt(i);}return null;}\n\nvar begintags=[];\nvar endtags=[];\nfunction revisionSpanHandler(g,e,f,b){var k=b.source.indexOf(">>",b.matchStart)+2,vsns=[],vtype=e,flen=f.length,becomes,c,cn,m, h,vsn;\nfunction mkspan(vtype){h=insertElement(m,"span",null,"revision-span "+vtype);h.setAttribute("data-enabled",false);h.style.display="none";h.tweecode="";return h;}if(this.shorthand&&flen){while(f.length>0){vsns.push([f.shift(),(this.flavour=="insert"?"gains":"becomes")]);\n}}else{if(this.flavour=="insert"||(this.flavour=="continue"&&this.trigger=="time")){vsns.push(["","becomes"]);}}if(this.flavour=="continue"&&flen){b.nextMatch=k+b.source.slice(k).length;\nvsns.push([b.source.slice(k),vtype]);}else{becomes=["becomes","gains"];c=tagcontents(b,begintags,becomes.concat(endtags),endtags,k);if(c&&endtags.indexOf(c[1])==-1){while(c){vsns.push(c);\nc=tagcontents(b,begintags,becomes,endtags,b.nextMatch);}c=tagcontents(b,begintags,["end"+e],endtags,b.nextMatch);}if(!c){throwError(g,"can't find matching end"+e);\nreturn;}vsns.push(c);if(this.flavour=="continue"){k=b.nextMatch;b.nextMatch=k+b.source.slice(k).length;vsns.push([b.source.slice(k),""]);}}if(this.flavour=="remove"){vsns.push(["","becomes"]);\n}cn=0;m=insertElement(g,"span",null,e);m.setAttribute("data-flavour",this.flavour);h=mkspan("initial");vsn=vsns.shift();h.tweecode=vsn[0];showVer(h,true);while(vsns.length>0){if(vsn){vtype=vsn[1];\n}vsn=vsns.shift();h=mkspan(vtype);h.tweecode=vsn[0];}if(typeof this.setup=="function"){this.setup(m,g,f);}}\n\nfunction quantity(m){return(m.children.length-1)+(m.getAttribute("data-flavour")=="remove");\n}\n\nfunction revisionSetup(m,g,f){m.className+=" "+f[0].replace(" ","_");}\nfunction keySetup(m,g,f){\n var key=f[0];m.setEventListener("keydown",function l(e){var done=!revise("revise",m);\n if(done){m.removeEventListener("keydown",l);}});\n}\nfunction timeSetup(m,g,f){function cssTimeUnit(s){if(typeof s=="string"){if(s.slice(-2).toLowerCase()=="ms"){return Number(s.slice(0,-2))||0;\n}else{if(s.slice(-1).toLowerCase()=="s"){return Number(s.slice(0,-1))*1000||0;}}}throwError(g,s+" isn't a CSS time unit");return 0;}var tm=cssTimeUnit(f[0]);var s=state.history[0].passage.title;\nsetTimeout(function timefn(){if(state.history[0].passage.title==s){var done=!revise("revise",m);if(!done){setTimeout(timefn,tm);}}},tm);}\n\nfunction hoverSetup(m){var fn,noMouseEnter=(document.head.onmouseenter!==null),m1=m.children[0],m2=m.children[1],gains=m2.className.indexOf("gains")>-1;\nif(!m1||!m2){return;}m1.onmouseenter=function(e){var efp=document.elementFromPoint(e.clientX,e.clientY);while(efp&&efp!==this){efp=efp.parentNode;}if(!efp){return;\n}if(this.getAttribute("data-enabled")!="false"){revise("revise",this.parentNode);}};m2.onmouseleave=function(e){var efp=document.elementFromPoint(e.clientX,e.clientY);\nwhile(efp&&efp!==this){efp=efp.parentNode;}if(efp){return;}if(this.getAttribute("data-enabled")!="false"){revise("revert",this.parentNode);}};if(gains){m1.onmouseleave=m2.onmouseleave;\n}if(noMouseEnter){fn=function(n){return function(e){if(!event.relatedTarget||(event.relatedTarget!=this&&!(this.compareDocumentPosition(event.relatedTarget)&Node.DOCUMENT_POSITION_CONTAINED_BY))){this[n]();\n}};};m1.onmouseover=fn("onmouseenter");m2.onmouseout=fn("onmouseleave");if(gains){m1.onmouseout=m2.onmouseout;}}m=null;}\n\nfunction mouseSetup(m){\n var evt=(document.head.onmouseenter===null?"onmouseenter":"onmouseover");\n m[evt]=function(){\n var done=!revise("revise",this);if(done){this[evt]=null;}\n };\n m=null;\n}\n\nfunction linkSetup(m,g,f){\n var l=Wikifier.createInternalLink(),p=m.parentNode;\n l.className="internalLink replaceLink";\n p.insertBefore(l,m);\n l.insertBefore(m,null);\n l.onclick=function(){\n var p,done=false;\n if(m&&m.parentNode==this){\n done=!revise("revise",m);\n /* console.log (m);\n scrollWindowTo(m); */\n let l = document.querySelectorAll (".askd");\n scrollWindowTo(l[l.length-1]);\n }\n if(done){\n this.parentNode.insertBefore(m,this);\n this.parentNode.removeChild(this);\n }\n };\n l=null;\n}\n\nfunction visitedSetup(m,g,f){\n var i,done,shv=state.history[0].variables,os="once seen",d=(m.firstChild&&(this.flavour=="insert"?m.firstChild.nextSibling:m.firstChild).tweecode);\n shv[os]=shv[os]||{};\n if(d&&!shv[os].hasOwnProperty(d)){shv[os][d]=1;}else{for(i=shv[os][d];i>0&&!done;i--){done=!revise("revise",m,true);}if(shv[os].hasOwnProperty(d)){shv[os][d]+=1;\n}}}\n\n[{name:"insert",flavour:"insert",trigger:"link",setup:linkSetup}\n,{name:"timedinsert",flavour:"insert",trigger:"time",setup:timeSetup}\n,{name:"insertion",flavour:"insert",trigger:"revisemacro",setup:revisionSetup}\n,{name:"later",flavour:"insert",trigger:"visited",setup:visitedSetup}\n,{name:"keyinsert",flavour:"insert",trigger:"key",setup:keySetup}\n,{name:"replace",flavour:"replace",trigger:"link",setup:linkSetup}\n,{name:"timedreplace",flavour:"replace",trigger:"time",setup:timeSetup}\n,{name:"mousereplace",flavour:"replace",trigger:"mouse",setup:mouseSetup}\n,{name:"hoverreplace",flavour:"replace",trigger:"hover",setup:hoverSetup}\n,{name:"revision",flavour:"replace",trigger:"revisemacro",setup:revisionSetup}\n,{name:"keyreplace",flavour:"replace",trigger:"key",setup:keySetup}\n,{name:"timedremove",flavour:"remove",trigger:"time",setup:timeSetup}\n,{name:"mouseremove",flavour:"remove",trigger:"mouse",setup:mouseSetup}\n,{name:"hoverremove",flavour:"remove",trigger:"hover",setup:hoverSetup}\n,{name:"removal",flavour:"remove",trigger:"revisemacro",setup:revisionSetup}\n,{name:"once",flavour:"remove",trigger:"visited",setup:visitedSetup}\n,{name:"keyremove",flavour:"remove",trigger:"key",setup:keySetup}\n,{name:"continue",flavour:"continue",trigger:"link",setup:linkSetup}\n,{name:"timedcontinue",flavour:"continue",trigger:"time",setup:timeSetup}\n,{name:"mousecontinue",flavour:"continue",trigger:"mouse",setup:mouseSetup}\n,{name:"keycontinue",flavour:"continue",trigger:"key",setup:keySetup}\n,{name:"cycle",flavour:"cycle",trigger:"revisemacro",setup:revisionSetup}\n,{name:"mousecycle",flavour:"cycle",trigger:"mouse",setup:mouseSetup}\n,{name:"timedcycle",flavour:"cycle",trigger:"time",setup:timeSetup}\n,{name:"keycycle",flavour:"replace",trigger:"key",setup:keySetup}\n].forEach(function(e){\n e.handler=revisionSpanHandler;\n e.shorthand=(["link","mouse","hover"].indexOf(e.trigger)>-1);\n macros[e.name]=e;\n macros["end"+e.name]=nullobj;\n begintags.push(e.name);\n endtags.push("end"+e.name);\n });\n\nfunction insideDepartingSpan(elem){var r=elem.parentNode;\nwhile(!r.classList.contains("passage")){if(r.classList.contains("revision-span-out")){return true;}r=r.parentNode;}}function reviseAll(rt,rname){var rall=document.querySelectorAll(".passage [data-flavour]."+rname),ret=false;\nfor(var i=0;i<rall.length;i++){if(!insideDepartingSpan(rall[i])){ret=revise(rt,rall[i])||ret;}}return ret;}function revise(rt,r,notrans){var ind2,curr,next,ind=-1,rev=(rt=="revert"),rnd=(rt.indexOf("random")>-1),fl=r.getAttribute("data-flavour"),rc=r.childNodes,cyc=(fl=="cycle"),rcl=rc.length-1;\nfunction doToGainerSpans(n,fn){for(var k=n-1;k>=0;k--){if(rc[k+1].classList.contains("gains")){fn(rc[k],notrans);}else{break;}}}for(var k=0;k<=rcl;k++){if(rc[k].getAttribute("data-enabled")=="true"){ind=k;\n}}if(rev){ind-=1;}curr=(ind>=0?rc[ind]:(cyc?rc[rcl]:null));ind2=ind;if(rnd){ind2=(ind+(Math.floor(Math.random()*rcl)))%rcl;}next=((ind2<rcl)?rc[ind2+1]:(cyc?rc[0]:null));\nvar docurr=(rev?showVer:hideVer);var donext=(rev?hideVer:showVer);var currfn=function(){if(!(next&&next.classList.contains("gains"))||rnd){docurr(curr,notrans);doToGainerSpans(ind,docurr,notrans);\n}};var nextfn=function(){donext(next,notrans);if(rnd){doToGainerSpans(ind2+1,donext,notrans);}};if(!rev){currfn();nextfn();}else{nextfn();currfn();}return(cyc?true:(rev?(ind>0):(ind2<rcl-1)));\n}macros.revert=macros.revise=macros.randomise=macros.randomize={handler:function(a,b,c){var l,rev,rname;function disableLink(l){l.style.display="none";}function enableLink(l){l.style.display="inline";\n}function updateLink(l){if(l.className.indexOf("random")>-1){enableLink(l);return;}var rall=document.querySelectorAll(".passage [data-flavour]."+rname),cannext,canprev,i,ind,r,fl;\nfor(i=0;i<rall.length;i++){r=rall[i],fl=r.getAttribute("data-flavour");if(insideDepartingSpan(r)){continue;}if(fl=="cycle"){cannext=canprev=true;}else{if(r.firstChild.getAttribute("data-enabled")==!1+""){canprev=true;\n}if(r.lastChild.getAttribute("data-enabled")==!1+""){cannext=true;}}}var can=(l.classList.contains("revert")?canprev:cannext);(can?enableLink:disableLink)(l);}function toggleText(w){w.classList.toggle(rl+"Enabled");\nw.classList.toggle(rl+"Disabled");w.style.display=((w.style.display=="none")?"inline":"none");}var rl="reviseLink";if(c.length<2){throwError(a,b+" macro needs 2 parameters");\nreturn;}rname=c.shift().replace(" ","_");l=Wikifier.createInternalLink(a,null);l.className="internalLink "+rl+" "+rl+"_"+rname+" "+b;var v="";var end=false;var out=false;\nif(c.length>1&&c[0][0]=="$"){v=c[0].slice(1);c.shift();}switch(c[c.length-1]){case"end":end=true;c.pop();break;case"out":out=true;c.pop();break;}var h=state.history[0].variables;\nfor(var i=0;i<c.length;i++){var on=(i==Math.max(c.indexOf(h[v]),0));var d=insertElement(null,"span",null,rl+((on)?"En":"Dis")+"abled");if(on){h[v]=c[i];l.setAttribute("data-cycle",i);\n}else{d.style.display="none";}insertText(d,c[i]);l.appendChild(d);}l.onclick=function(){reviseAll(b,rname);var t=this.childNodes,u=this.getAttribute("data-cycle")-0,m=t.length,n,lall,i;\nif((end||out)&&u>=m-(end?2:1)){if(end){n=this.removeChild(t[u+1]||t[u]);n.className=rl+"End";n.style.display="inline";this.parentNode.replaceChild(n,this);}else{this.parentNode.removeChild(this);\nreturn;}}else{toggleText(t[u]);u=(u+1)%m;if(v){h[v]=c[u];}toggleText(t[u]);this.setAttribute("data-cycle",u);}lall=document.getElementsByClassName(rl+"_"+rname);\nfor(i=0;i<lall.length;i++){updateLink(lall[i]);}};disableLink(l);setTimeout((function(l){return function(){updateLink(l);};}(l)),1);l=null;}};macros.mouserevise=macros.hoverrevise={handler:function(a,b,c,d){var endtags=["end"+b],evt=(window.onmouseenter===null?"onmouseenter":"onmouseover"),t=tagcontents(d,[b],endtags,endtags,d.source.indexOf(">>",d.matchStart)+2);\nif(t){var rname=c[0].replace(" ","_"), h=insertElement(a,"span",null,"hoverrevise hoverrevise_"+rname),f=function(){var done=!reviseAll("revise",rname);if(b!="hoverrevise"&&done){this[evt]=null;\n}};new Wikifier(h,t[0]);if(b=="hoverrevise"){h.onmouseover=f;h.onmouseout=function(){reviseAll("revert",rname);};}else{h[evt]=f;}h=null;}}};macros.instantrevise={handler:function(a,b,c,d){reviseAll("revise",c[0].replace(" ","_"));\n}};macros.endmouserevise=nullobj;macros.endhoverrevise=nullobj;\n}());