WOLF GAME
xax
<<nobr>>\n<h1>WEREWOLF GAME</h1>\n<ul class="title">\n<li>[[new game|new_game]]</li>\n<li>[[content notes]]</li>\n</ul>\n<<endnobr>>
<<display "init">>\n<<display "character creation">>\n\n[[→|map]]
...\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" "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: write descriptions for rooms. place npcs (depending on time?)}}}
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}\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 , 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: ["The street underfoot is rough pavingstones. There are rut marks from decades of wagons, and now they're nearly entirely covered by the overlapped stalls, growing in from the edge of the road like angular fungus. Shuttered buildings loom on all sides. The crowd around you teems."]\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 ]\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. 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 , 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 [ "<<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: 1, 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 , 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 , 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\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) */\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);\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 0>>\n<<set $_enteredFrom to null>>\n<<set $_visited to replicate (0, Math.ceil (mapData.length / 32))>>\n\n<<set $locations =\n [ \n ]>>\n<<set $events =\n [ "the beast-masked hunter's last victim"\n ]>>\n<<set $people =\n [ "suspicious people"\n ]>>\n<<set $persons =\n [ "the beast-masked hunter"\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 }\n }>>\n<<set $bestialdockworkerUnique =\n { name: "bestial dockworker (unique)"\n , topics:\n { "": "bestial dockworker splash"\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\n\n<<set $youngFactoryworker = [$factoryworkerUnique, $goodbye_spec, $know_nothing]>>\n<<set $bestialDockworker = [$bestialdockworkerUnique, $goodbye_spec, $know_nothing]>>\n<<set $nightWatchman = [$nightwatchmanUnique, $goodbye_spec, $know_nothing]>>\n<<set $maskedChurchman = [$maskedChurchmanUnique, $goodbye_spec, $know_nothing]>>\n\n<<set $genericHuman = [$rumormonger, $human, $goodbye_spec, $know_nothing]>>\n<<endnobr>>
<<set $interrupt to true>>\nblah blah, goodbye\n\n[[→|map]]
"know nothing" fallback
<div class="npc_title">Young Factoryworker</div>\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>>
<div class="npc_title">Bestial Dockworker</div>\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 for you, 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_rest">Back for more, huh?</div>\n\n<<response "i just have a few more things to ask...">>\n<<response "yeah please fuck my throat">>\n<<endnobr>>
<div class="npc_resp">What, that all the questions you got?</div>\n\nif you've raised heat by >2 pts in this convo:\n<<response "Yeah, that's all for now">>\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<<response "Yeah, that's all for now">>\n<<response "actually i got one more question; you wanna suck my dick?">>\n<<response "actually i got one more question; you wanna let me suck on your dick?">>
<div class="npc_title">Night Watchman</div>\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.
<div class="npc_title">Masked Churchman</div>\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" "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" "goodbye" "goodbye">></td>\n<td><<block base>>\n<<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 == "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)>>\\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; }\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.autoresponse { 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.autoran { border: 1px solid #eee576; }\n\n.npc_resp { border-radius: 4px; border: 1px solid #eee576; padding: 0.25em; margin: 1em 0 1em 3em; }\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.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 console.log (inner_index);\n var after_resp = parser.source.slice(inner_index[0], inner_index[1]);\n console.log (after_resp);\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("autoran");\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 console.log (resp);\n place.appendChild (resp);\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}());