WOLF GAME
xax
<<nobr>>\n<h1>WEREWOLF GAME</h1>\n<ul class="title">\n<li>[[new game|new_game]]</li>\n</ul>\n<div id="notes">[[content notes]]</div>\n<<display "static init">>\n<<endnobr>>
<<display "init">>\\n<<display "character creation">>\n\n[[=>|game_intro]]
<div class="meta">you start off investigating a murder. there's a lot of violence. there will be bad endings where you get killed.\n\nalso, the game is all about a city experiencing social upheaval and unrest due to a plague that's been spreading unchecked for ages. it turns out this was shockingly prescient for world events, given that i started outlining this in january/february. so this probably won't really take your mind off COVID-19, especially when the game gets into the cases that are more specifically about the beast plague itself.\n\n''this game contains sexual content!'' you play as a male character, and all the sex (so far) is with werewolf/beastmen characters. no player-centric sex scenes are obligatory.\n\nyou can have sex with some assholes. specifically, this is a detective game. you can fuck people of interest, which means both sometimes you might be fucking somebody who's a serial killer or some other unsavory character and you might not know it yet, and also you can fuck characters who presumably wouldn't want to fuck you if they knew you were investigating them.\n\nin terms of sexual content, currently the game contains:\n* sex in public\n* humans with dog dicks\n* knotting\n* animal behavior during sex (ass-to-ass knotting, drooling, barking, etc)\n* huge loads\nthese notes will expand as more content is added.\n\n[[<=|Start]]</div>
<<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>>
/% automatically included on every map room %/\\n<<if $bmh_investigate && mapData[$_pos].zone == 2>>/% if yr at the 'investigation' stage of of beast-masked hunter quest%/\\n* [[Investigate the environs closely|bmh investigate]]\n<<endif>>
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.minus = function (pt) {\n return new Point3d (this.x - pt.x, this.y - pt.y, this.z - pt.z);\n}\nPoint3d.prototype.magnitude = function () {\n return Math.sqrt (this.x * this.x + this.y * this.y + this.z * this.z);\n}\nPoint3d.prototype.normalize = function () {\n var m = this.magnitude();\n return new Point3d (this.x / m, this.y / m, this.z / m);\n}\nPoint3d.prototype.cross = function (vec) {\n return new Point3d\n ( this.y * vec.z - this.z * vec.y\n , this.z * vec.x - this.x * vec.z\n , this.x * vec.y - this.y * vec.x\n );\n}\nPoint3d.prototype.dot = function (vec) {\n return this.x * vec.x + this.y * vec.y + this.z * vec.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\nfunction Line3d (p1, p2) {\n this.o = new Point3d (p1.x, p1.y, p1.z);\n this.f = new Point3d (p2.x, p2.y, p2.z).minus(this.o);\n}\nLine3d.prototype.t = function (t_) {\n return this.o.plus (this.f.timesScalar (t_));\n}\nLine3d.prototype.asNormVectorT = function (t_) {\n return this.f.normalize().timesScalar (t_);\n}\nLine3d.prototype.distanceFromSegment = function (pt) {\n let w = pt.minus (this.o);\n let t = this.f.dot (w) / this.f.dot (this.f); // i think a dot product with itself is just like, the magnitude squared? so idk really what this is doing.\n if (t >= 0 && t <= 1) { // pt can draw orthogonally down and hit the line\n return pt.minus (this.t (t)).magnitude ();\n } else { // pt is closest to one of the endpoints\n let d1 = pt.minus (this.t (0)).magnitude ();\n let d2 = pt.minus (this.t (1)).magnitude ();\n return d1 < d2 ? d1 : d2;\n }\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 Exit (to, toExit, point, dir, enterTwee, exitTwee, pred) {\n this.to = to;\n this.toExit = toExit;\n this.pos = point;\n if (dir) {\n this.dir = dir;\n }\n this.callbacks = {};\n if (enterTwee) {\n this.callbacks.enter = enterTwee;\n }\n if (exitTwee) {\n this.callbacks.exit = exitTwee;\n }\n if (pred) {\n this.pred = pred;\n } else {\n this.pred = (() => true);\n }\n}\nExit.prototype.direction = function () {\n return this.dir ? this.dir : worldDir (this.pos);\n}\nExit.prototype.buildLink = function (currentRoom, ix) {\n let eid = "r" + currentRoom.id + "_e" + ix;\n let tid = "r" + this.to;\n let eli = insertElement (null, 'span', null, null, null);\n let dir = this.direction();\n let exitText = dir + " to " + (wasVisited (this.to) ? mapData[this.to].name : "???");\n let elink = null;\n elink = this.pred()\n ? insertElement (eli, 'a', null, 'exitLink internalLink', exitText)\n : insertElement (eli, 'span', null, 'barredExit', exitText);\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 if (!this.pred()) {\n return eli;\n }\n elink.addEventListener ('click', ev => {\n let paired = mapData[this.to].exits[this.toExit];\n state.history[0].variables["_pos"] = this.to;\n state.history[0].variables["_enteredFrom"] = "r" + this.to + "_e" + this.toExit;\n /* run this exit's exit callback, if present, and then run the matching exit's entrance callback, if present */\n let mapblock = document.querySelector(".passage .blockSpan.mapblock");\n /* these should display 'invisibly' since we immediately rerender the block without these wikifiers attached, but they should still run the relevant tweecode */\n if (this.callbacks.exit) {\n new Wikifier (mapblock, this.callbacks.exit);\n }\n if (paired.callbacks.enter) {\n new Wikifier (mapblock, paired.callbacks.enter);\n }\n reshow ("mapblock");\n });\n return eli;\n};\n\nfunction t (p, a, b) {\n return a + (b - a) * p;\n}\nfunction mapPairs (list, f) {\n return list.map ((i, ix, arr) => f (i, list[(ix+1) % list.length], ix, arr));\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.addOutline = function (outline) {\n this.outlines.push (outline);\n return this;\n}\nRoomBox.prototype.outset = function (point, width, depth, direction, writeOutline) {\n function mutateLoop (matchVar) {\n let matchedLoop = null;\n let matchedIx = null;\n let matchedLine = null;\n matchVar.map (loop => {\n if (loop.length <= 2) {\n return;\n }\n mapPairs (loop, (p1, p2, ix) => {\n let line = new Line3d (p1, p2);\n if (line.distanceFromSegment (point) <= 0.0001) {\n matchedLoop = loop;\n matchedIx = ix;\n matchedLine = line;\n }\n });\n });\n if (matchedIx === null) {\n return;\n }\n let woffset = width / 2;\n let woffsetPt = matchedLine.asNormVectorT(woffset); // woffset units along the matched line\n let outsetVector = null;\n /* this is obviously non-ideal but i figured this would be easier to handle in practice than having it automatically pick an 'outer' direction based on the looping direction */\n switch (direction) {\n case "north":\n outsetVector = new Point3d (0, 0, -1);\n break;\n case "south":\n outsetVector = new Point3d (0, 0, 1);\n break;\n case "east":\n outsetVector = new Point3d (1, 0, 0);\n break;\n case "west":\n outsetVector = new Point3d (-1, 0, 0);\n break;\n default:\n outsetVector = new Point3d (0, 0, 0);\n break;\n }\n let outsetPt = outsetVector.timesScalar (depth);\n let pback = point.minus (woffsetPt);\n let pfront = point.plus (woffsetPt);\n let pbackout = pback.plus (outsetPt);\n let pfrontout = pfront.plus (outsetPt);\n\n matchedLoop.splice ((matchedIx + 1) % matchedLoop.length, 0, pback, pbackout, pfrontout, pfront);\n }\n\n mutateLoop (this.floor);\n if (this.floorOutline) {\n mutateLoop ([this.floorOutline]);\n }\n if (writeOutline) {\n mutateLoop (this.outlines);\n }\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, height = 0.90) {\n let y = height * -1; /* invert the y axis b/c this is svg where +y is further down */\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 ,\n [ [ new Point3d ( 0.45, y, 0.45)\n , new Point3d ( 0.45, y,-0.45)\n , new Point3d (-0.45, y,-0.45)\n , new Point3d (-0.45, y, 0.45)\n ]\n , [ new Point3d (0.45, 0, 0.45)\n , new Point3d (0.45, y, 0.45)\n ]\n , [ new Point3d (0.45, 0, -0.45)\n , new Point3d (0.45, y, -0.45)\n ]\n , [ new Point3d (-0.45, 0, -0.45)\n , new Point3d (-0.45, y, -0.45)\n ]\n , [ new Point3d (-0.45, 0, 0.45)\n , new Point3d (-0.45, y, 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, dims) {\n let adirs = dirs.toLowerCase().split("").sort().join("");\n let dimension = dims ? dims : new Point3d (0.45, 0.40, 0.20);\n switch (adirs) {\n case "ns":\n return hall (offset, "Z", dimension.x, dimension.z);\n case "ew":\n return hall (offset, "X", dimension.x, dimension.z);\n case "es":\n return corner (offset, 0, dimension.x, dimension.z);\n case "en":\n return corner (offset, 1, dimension.x, dimension.z);\n case "nw":\n return corner (offset, 2, dimension.x, dimension.z);\n case "sw":\n return corner (offset, 3, dimension.x, dimension.z);\n case "esw":\n return tjunction (offset, 0, dimension.x, dimension.z);\n case "ens":\n return tjunction (offset, 1, dimension.x, dimension.z);\n case "enw":\n return tjunction (offset, 2, dimension.x, dimension.z);\n case "nsw":\n return tjunction (offset, 3, dimension.x, dimension.z);\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, length, breadth) {\n let edge = length ? length : 0.45;\n let corner = breadth ? breadth : 0.20;\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 ( corner, 0, corner) /* c */\n , new Point3d ( edge, 0, corner)\n , new Point3d ( edge, 0,-corner)\n , new Point3d ( -edge, 0,-corner)\n , new Point3d ( -edge, 0, corner)\n , new Point3d (-corner, 0, corner) /* c */\n , new Point3d (-corner, 0, edge)\n , new Point3d ( corner, 0, edge)\n ]\n ]\n , [ [ new Point3d ( corner, h, corner) /* c */\n , new Point3d ( edge, h, corner)\n , new Point3d ( edge, h,-corner)\n , new Point3d ( -edge, h,-corner)\n , new Point3d ( -edge, h, corner)\n , new Point3d (-corner, h, corner) /* c */\n , new Point3d (-corner, h, edge)\n , new Point3d ( corner, h, edge)\n ]\n , [ new Point3d ( corner, 0, corner)\n , new Point3d ( corner, h, corner) /* c */\n ]\n , [ new Point3d ( edge, 0, corner)\n , new Point3d ( edge, h, corner)\n ]\n , [ new Point3d ( edge, 0,-corner)\n , new Point3d ( edge, h,-corner)\n ]\n , [ new Point3d ( -edge, 0,-corner)\n , new Point3d ( -edge, h,-corner)\n ]\n , [ new Point3d ( -edge, 0, corner)\n , new Point3d ( -edge, h, corner)\n ]\n , [ new Point3d (-corner, 0, corner)\n , new Point3d (-corner, h, corner) /* c */\n ]\n , [ new Point3d (-corner, 0, edge)\n , new Point3d (-corner, h, edge)\n ]\n , [ new Point3d ( corner, 0, edge)\n , new Point3d ( corner, h, edge)\n ]\n ]\n ).rotate(rad);\n};\n\nwindow.corner = function (offset, rot, longLength, shortLength) {\n let edge = longLength ? longLength : 0.45;\n let corner = shortLength ? shortLength : 0.20;\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 ( corner, 0, corner) /* c */\n , new Point3d ( edge, 0, corner)\n , new Point3d ( edge, 0,-corner)\n , new Point3d (-corner, 0,-corner) /* c */\n , new Point3d (-corner, 0, edge)\n , new Point3d ( corner, 0, edge)\n ]\n ]\n ,\n [ [ new Point3d ( corner, h, corner)\n , new Point3d ( edge, h, corner)\n , new Point3d ( edge, h,-corner)\n , new Point3d (-corner, h,-corner)\n , new Point3d (-corner, h, edge)\n , new Point3d ( corner, h, edge)\n ]\n , [ new Point3d ( corner, 0, corner)\n , new Point3d ( corner, h, corner)\n ]\n , [ new Point3d ( edge, 0, corner)\n , new Point3d ( edge, h, corner)\n ]\n , [ new Point3d ( edge, 0,-corner)\n , new Point3d ( edge, h,-corner)\n ]\n , [ new Point3d (-corner, 0,-corner)\n , new Point3d (-corner, h,-corner)\n ]\n , [ new Point3d (-corner, 0, edge)\n , new Point3d (-corner, h, edge)\n ]\n , [ new Point3d ( corner, 0, edge)\n , new Point3d ( corner, h, edge)\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 [ new Exit (1, 0, new Point3d ( 0.45, -0.10, 0 ))\n , new Exit (3, 0, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (5, 0, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (7, 0, 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 [ new Exit (0, 0, new Point3d (-0.45, -0.10, 0) )\n , new Exit (2, 0, 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 [ new Exit (1, 1, 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 [ new Exit (0, 1, new Point3d (0, -0.10, -0.45) )\n , new Exit (4, 0, 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 [ new Exit (3, 1, 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 [ new Exit (0, 2, new Point3d (0.45, -0.10, 0) )\n , new Exit (6, 0, 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 [ new Exit (5, 1, 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 [ new Exit (0, 3, new Point3d (0, -0.10, 0.45))\n , new Exit (8, 0, 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 [ new Exit (7, 1, new Point3d (0, -0.10, 0.45) )\n , new Exit (9, 1, 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 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 that typifies the market street neighborhood to the much older, weathered marble of the old churchbuildings."\n ]\n , exits:\n [ new Exit (10, 0, new Point3d (0.25, -0.10, -0.45), "north")\n /* , new Exit (8, 1, new Point3d (-0.70, -0.60, 0)) */\n ]\n , zone: 1\n }\n , { id: 10\n , room: multipath (new Point3d ( 1.5, 0.5,-3), "SE")\n .outset (new Point3d (0, 0,-0.20), 0.20, 0.05, "north")\n , name: "Road of Bones"\n , desc: ["Tall buildings loom on all sides. A small plaza that makes up a common courtyard for the buildings nearby. There's a fenced bed in the center with a small tree growing from it. It may have been a topiary at some point."\n , "The bulk of the old church hill to the southwest casts the plaza into shadow for most of the day."]\n , exits:\n [ new Exit (9, 0, new Point3d (0, -0.10, 0.45))\n , new Exit (11, 0, new Point3d (0.45, -0.10, 0))\n , new Exit (18, 1, 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\". Maybe he named himself after the alley; it's definitely not the other way around."]\n , exits:\n [ new Exit (10, 1, new Point3d (-0.45, -0.10, 0))\n , new Exit (12, 0, 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 .outset (new Point3d (0.45, 0, 0), 0.20, 0.05, "east")\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 [ new Exit (11, 1, new Point3d (-0.45, -0.10, 0))\n , new Exit (13, 0, new Point3d (0, -0.10, -0.45))\n , new Exit (14, 0, new Point3d (0, -0.10, 0.45))\n , new Exit (15, 0, 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 .outset (new Point3d (-0.20, 0, 0.15), 0.20, 0.05, "west")\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 [ new Exit (12, 1, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (16, 0, new Point3d ( 0.45, -0.10, 0))\n , new Exit (17, 0, new Point3d (-0.20, -0.10, 0.15), "west")\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 [ new Exit (12, 2, new Point3d (0, -0.10, -0.45))\n , new Exit (41, 0, 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 [ new Exit (12, 3, new Point3d (-0.45, -0.10, 0))\n , new Exit (16, 1, 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 .outset (new Point3d ( 0, 0, 0.45), 0.20, 0.05, "south")\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 [ new Exit (13, 1, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (15, 1, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (20, 0, 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 .outset (new Point3d ( 0.45, 0,-0.10), 0.20, 0.05, "east")\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 [ new Exit (13, 2, new Point3d ( 0.45, -0.10,-0.10))\n , new Exit (18, 0, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (19, 0, 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 .outset (new Point3d (-0.20, 0, 0.35), 0.20, 0.05, "south")\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 [ new Exit (17, 1, new Point3d ( 0.45, -0.10, 0 ))\n , new Exit (10, 2, new Point3d (-0.20, -0.10, 0.35), "south")\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: "Rooms for Rent, Cheap!"\n , desc: ["The upper floor of Last Hope Hall is a long, narrow corridor along the long axis of the building that forks and twists as it goes. There are thirteen rooms on the upper floor, numbered, as well as some unlabeled doors that are likely storage cabinets."\n , "The only open space is immediately at the top of the stairway up: there's a small sitting area, with an arch window looking out over the rooftops, and two faded red couches on either side."]\n , exits:\n [ new Exit (17, 1, 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 [ new Exit (16, 2, new Point3d (0, -0.10, 0.45))\n , new Exit (21, 0, 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 , desc: ["The undercity was carved out of the natural caverns beneath the city: the walls, in many places, are smooth expanses of quarried stone, with a jagged and uneven line of brickwork starting just as the walls fade into darkness above. Rich blue witchlight flickers in recessed sconces along the walls, or in glittering webs strung across the ceiling, like stars in the night sky: it's always night, in the undercity."\n ,"The bare walls carry sound, transforming the scrape of boots, the murmuring of voices, the flow of the underground river, into a distorted rush like howling wind, continually breaking over you."]\n , exits:\n [ new Exit (20, 1, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (22, 0, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (25, 0, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (30, 0, 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 .outset (new Point3d ( 0, 0, 0.60), 0.20, 0.05, "south")\n .outset (new Point3d ( 0, 0,-0.60), 0.20, 0.05, "north")\n , name: "Where Rats Drowned"\n , desc: ["One of the main throughfares for the underground market. The market stalls are most tightly-clustered around the banks of the underground river, crowded together in overlapping awnings, cutting the broad river boulevards apart into several tight, mazy alleys.<<if $bmh_active>> The crowd seems thinner here than usual.<<endif>>"\n , "The river churns below, ink-black in the pale witchlight of the undercity, and the sound of its flow reflects off the cavern ceiling, filling the area with a continual rippling echo."\n , "You're on the <<print $bank>> bank of the river."\n , "<<if $bank eq 'north' && hasClue ('bmh', 'rats location')>>This is where the murder took place. [[Consider your clues|rats clues]].<<else if $bank eq 'south' && hasClue ('bmh', 'rats location')>>The murder took place across the river, on the north bank.<<endif>>"]\n , exits:\n [ new Exit (21, 1, new Point3d ( 0 , -0.10, 0.60), null\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (23, 0, new Point3d ( 0 , -0.10,-0.60), null\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (26, 4, new Point3d (-0.45, -0.10,-0.45), "west"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (26, 5, new Point3d (-0.45, -0.10, 0.45), "west"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (31, 0, new Point3d ( 0.45, -0.10,-0.45), "east"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (31, 1, new Point3d ( 0.45, -0.10, 0.45), "east"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n ]\n , zone: 2\n }\n , { id: 23\n , room: multipath (new Point3d ( 4.5, 1,-8.3), "NS")\n .outset (new Point3d ( 0.20, 0 , 0), 0.20, 0.25, "east")\n .outset (new Point3d ( 0.20,-0.40, 0), 0.20, 0.25, "east", true)\n , name: "Hunger for the Dark"\n , desc: ["A stately boulevard. The stones underfoot are paved in a complex geometric mosaic, forming a set of knotwork rings looping around a circular plaza, lit by a brilliant lamppost. The way is brightly-lit all the way down to the riverside, in a slightly-curving boulevard that eventually curves out of sight. The houses here have small lawns of dense ashmoss, gated with wrought iron fences, though of late a few have been torn apart, and the market now spills all the way up to the doorways."]\n , npcs:\n [ { twee: "A [[lady of the night|dialog][$_conv = 'ladyOfTheNight']] waits beneath a bright lamppost." }\n ]\n , exits:\n [ new Exit (22, 1, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (24, 0, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (32, 1, 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 , desc: ["The undercity howls. The mines that the ancient Dhvirians dug, which became the undercity, have many old passages disused by modern men. The old mines have ventilation channels: immense cylindrical pits that sink down to the bones of the earth, opening into lightless abysses breached thousands of years hence."\n , "This passage in the undercity runs over one such channel. The bridge is a delicate spur of masonry spanning across the center of the pit. Air is endlessly sucked down into the depths, creating a continual breeze. Beneath, the walls seep moisture, shining dewdrops reflecting up out of the depths long after the rest of the light has faded, and perhaps they eventually find their way down to some abyssal sea."]\n , exits:\n [ new Exit (23, 1, 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 , desc: ["A residential corner of the undercity. Houses carved out of the walls rise up into the darkness, with their windows glowing dimly with white-blue light. There's a square raised bed in the center of the corner, with a lamppost in the center, casting shadows all up the walls."\n , "There's a certain kind of ornamental fungus cultivated in the undercity, and the residents of this corner have lined the beds and their window boxes with different cultivars. Rippling flame-red sparkmoss cascades in rumpled folds down the edges of their containers, with stalks of orange-yellow lampweed rising up over a dense thicket of pale grey ashmoss. The air is thick with the heady scent of their spores, and bootprints stand out starkly in the dusty residue."]\n , exits:\n [ new Exit (21, 2, new Point3d (0.45, -0.10, 0))\n , new Exit (26, 0, 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 .outset (new Point3d ( 0, 0, 0.60), 0.20, 0.05, "south")\n .outset (new Point3d ( 0, 0,-0.60), 0.20, 0.05, "north")\n , name: "Where the Water Runs Black"\n , desc: ["The underground river churns below, stirring up thick mats of foam that spin within eddies. The bridge across the river, called \"Mirwake Span\" after its architect, is a broad, sturdy double-arch, punctuated with lampposts in the middle and at both ends. It is the only major pathway across the river, and on any market day it's crowded with people and stalls, across the bridge itself and over both banks."\n , "On the river banks, there are a few steep stone stairs down to the water surface: old, disused docks for fishingboats, from the times when the undercity had its own wharf. Now they are just treacherous, half-flooded stone surfaces, too dangerous for any of the passers-by to try navigating."]\n , exits:\n [ new Exit (25, 1, new Point3d ( 0 , -0.10, 0.60), null, "<<set $bank to 'south'>>")\n , new Exit (27, 0, new Point3d ( 0 , -0.10,-0.60), null, "<<set $bank to 'north'>>")\n , new Exit (28, 0, new Point3d (-0.45, -0.10,-0.45), "west", "<<set $bank to 'north'>>")\n , new Exit (28, 1, new Point3d (-0.45, -0.10, 0.45), "west", "<<set $bank to 'south'>>")\n , new Exit (22, 2, new Point3d ( 0.45, -0.10,-0.45), "east", "<<set $bank to 'north'>>")\n , new Exit (22, 3, new Point3d ( 0.45, -0.10, 0.45), "east", "<<set $bank to 'south'>>")\n ]\n , zone: 2\n }\n , { id: 27\n , room: multipath (new Point3d ( 3.5, 1,-8.3), "WS", new Point3d (0.45, 0.40, 0.10))\n , name: "Twisted as Sin"\n , desc: ["A residential corner of the undercity. The road becomes a gnarled, twisted sequence of forking and dead-ending backalleys that curl around wrought-iron fences and old stone masonry, with the lit windows of the houses above serving as flat, square stars, almost useless for guidance. The air is foggy with the rich scent of fungal spores."]\n , exits:\n [ new Exit (26, 1, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (29, 1, 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 .outset (new Point3d ( 0, 0,-0.60), 0.20, 0.05, "north")\n , name: "The River of Garbage"\n , desc: ["The path is nearly flush with the river's surface, and choppy waves slosh over the brickwork at the edge of the wharf. A few ships are docked, wide and slow merchant boats, being loaded or unloaded. Great pallets of goods are stacked back against the walls, covered in oiled tarpaulins."\n ,"The brickwork ends at this point: further downstream there's just the walls of the old cove, bare and steep, and the opening out to sea is a glowing dot. The placement of the cove, and the ringing buildings around the outer dock, mean that the sun doesn't reach far within, even at sunset. Beyond the end of the docks, slanted beams of light shine into the black water pouring from the undercity: black and roiling, stirring up dense, iridescent foam."\n ,"You're on the <<print $bank>> bank of the river."]\n , npcs:\n [ { twee: "<<if $bank eq 'south'>>There's a [[bestial dockworker|dialog][$_conv = 'bestialDockworker']] unloading barrels from a docked boat, one on each of his giant shoulders.<<endif>>" }\n ]\n , exits:\n [ new Exit (26, 2, new Point3d ( 0.45, -0.10,-0.45), "east"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (26, 3, new Point3d ( 0.45, -0.10, 0.45), "east"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (29, 0, new Point3d ( 0 , -0.10,-0.60), null\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n ]\n , zone: 2\n }\n , { id: 29\n , room: multipath (new Point3d ( 2.5, 1,-8.3), "SE", new Point3d (0.45, 0.40, 0.10))\n , name: "Lovers' Corner"\n , desc: ["A residential corner of the undercity. The buildings here were once manors, but have long-since been repartitioned into apartments. The ornate, complex architecture is still on display, with octagonal turrets, broad bay windows, and sharply-angled eaves bulging out onto the street, interlocking with each other in a fractal mess of metastatized masonry, decorated all over with grotesques of beasts and gargoyles."]\n , exits:\n [ new Exit (28, 2, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (27, 1, 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 , desc: ["Also called \"Butcher's Lane\", this was for many years part of a sealed back route between one of the undercity slaughterhouses and the royal alchemist's quarters, and used to supply the palace apothecary with blood and ground bone. It was only made public two hundred years back, after the revolution of the Butcher King deposed the monarchy for a generation, and rapidly became a part of the undercity market halls."]\n , exits:\n [ new Exit (21, 3, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (35, 1, 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 .outset (new Point3d ( 0, 0,-0.60), 0.20, 0.05, "north")\n , name: "Where Floodwaters Flowed"\n , desc: ["The undercity market roars louder than the river below. Both banks of the river are covered in stalls, with merchants hawking wares: spices, perfume, jewels; as well as the more mundane goods: rice, chickens, oil, ash. There's a greengrocer selling softly-glowing lamp-pepper plants next to their bundles of carrots. Across the way, the crowd looks like an extension of the river: a churning mass of shapes, lit in whorls and eddies by the pale blue lamps overhead."\n , "You're on the <<print $bank>> bank of the river."]\n , exits:\n [ new Exit (22, 4, new Point3d (-0.45, -0.10,-0.45), "west"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (22, 5, new Point3d (-0.45, -0.10, 0.45), "west"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (32, 0, new Point3d ( 0 , -0.10,-0.60), null\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (36, 1, new Point3d ( 0.45, -0.10,-0.45), "east"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (36, 2, new Point3d ( 0.45, -0.10, 0.45), "east"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n ]\n , zone: 2\n }\n , { id: 32\n , room: multipath (new Point3d ( 5.5, 1,-8.3), "NSW", new Point3d (0.45, 0.40, 0.10))\n , name: "Nameless Street"\n , desc: ["A twisting alleyway, almost accidentally formed by the gaps between old buildings. A single old lamppost lights a crossing at a confluence of building corners. The buildings are steep and stark, all on their side faces, with no entrances. Balconies with laundry-lines cluster along the higher stories, connecting them together in a messy web. Thickets of vinemold grow up the walls, spanning overhead across trellises and disused laundry-lines, and their insulating blanket absorbs even the noise of the crashing river, not too distant."\n , "Within the undercity market, even a street like this is not without its stalls, though they are haphazard things with damaged or likely-stolen wares, and the merchants are silent."]\n , exits:\n [ new Exit (31, 2, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (23, 1, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (33, 0, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (42, 0, new Point3d ( 0 , 0.10, 0 ))\n ]\n , zone: 2\n }\n , { id: 33\n , room: cube (new Point3d ( 5.5, 1,-9.3))\n , name: "The Dry Well"\n , desc: ["Above you, there is the sky. The lopsided square of the chamber simply has no roof. It is the bottom of an old quarry, or the shaft of a long-ago-abandoned mine; the walls smoothly ascend until they reach the surface, three-or-so stories up. There are bushes overhanging the edge, with stringy moss hanging down."\n , "The sky is speckled with the first stars, red turning purple. Distantly, you hear birds."]\n , exits:\n [ new Exit (32, 2, 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 , desc: ["An undercity plaza. Square, with a complex pattern in the flagstones. There's a ragged mezzanine partially ringing the plaza, and above that doors and balconies from houses carved from the rock, with worn stairways going up and down. Groups loiter on the landings, legs dangling down, heels kicking the crumbling stone."]\n , exits:\n [ new Exit (35, 0, 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 , desc: ["A crossing in the undercity. A shallow crossing, near enough to the surface, and to the market, to afford a plausible reason to visit for those living in the rich overside, but deep enough to also be visited easily by those that live in the depths. The play \"The Lovers\" is set here, and the audience observes the crossing paths and missed connections of a pair of lovers."]\n , exits:\n [ new Exit (34, 0, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (30, 1, new Point3d (-0.45, -0.10, 0 ))\n , new Exit (36, 0, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (38, 1, 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 .outset (new Point3d ( 0, 0, 0.60), 0.20, 0.05, "south")\n .outset (new Point3d ( 0, 0,-0.60), 0.20, 0.05, "north")\n , name: "The Dark Fountain"\n , desc: ["The underground river bursts from the wall in a torrent and crashes down into the river below, making its currents swirl and spin in murky spirals. It's so loud it's difficult to hear anything else."\n , "A dimly-luminous moss grows in clumps in the cracks of rocks above the waterfall, kept moist by the constant spray of water."\n , "You're on the <<print $bank>> bank of the river."]\n , exits:\n [ new Exit (35, 2, new Point3d ( 0 , -0.10, 0.60), null\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (31, 3, new Point3d (-0.45, -0.10,-0.45), "west"\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\n , new Exit (31, 4, new Point3d (-0.45, -0.10, 0.45), "west"\n , "<<set $bank to 'south'>>", null, () => getVar("bank") == "south")\n , new Exit (37, 0, new Point3d ( 0 , -0.10,-0.60), null\n , "<<set $bank to 'north'>>", null, () => getVar("bank") == "north")\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 , desc: ["A quiet residential corner. Only a few market stalls are set up, all facing the path to the underground river. Lamplight flickers in the windows of the houses, and there is movement behind the curtains."]\n , exits:\n [ new Exit (36, 3, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (40, 1, 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 , desc: ["The edge of the undermarket wharf. To the west is the wharf and market, and to the east the way descends further. The stairway down yawns, black on black, with not even witchlight to guide the way."]\n , exits:\n [ new Exit (39, 1, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (35, 3, 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 , desc: ["A passage hewn from solid rock, connecting the undercity wharf to the rest of the undermarket. There's a visible seam in the floor, where the river beneath has been bricked over. You can feel the waterfall thrum under your feet, even with the sound muffled."\n ,"The passageway is braced with pillars every thirteen paces, and each pair of pillars has a witchlight sconce set into it. The end result is that the main way is brightly lit in flickering blue light, but the spaces between the pillars -- enough space for a person to hide just out of sight -- is an inky, lightless black. The passage is famous for its use as a secret meeting spot, and as the location of several assassinations."]\n , npcs:\n [ { twee: "A [[night watchman|dialog][$_conv = 'nightWatchman']] walks through the passage, lantern held aloft." }\n ]\n , exits:\n [ new Exit (40, 0, new Point3d ( 0 , -0.10,-0.45))\n , new Exit (38, 0, 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 , desc: ["This was once the aging cellar of a winery. The walls are all masonry, as opposed to the solid rock of most of the rest of the undercity, and there are regularly-spaced recesses in the walls -- as tall as a man and twice as broad -- for storing massive bulk aging barrels. There are still long rectangular light spots across the bricks near the walls to show where wine racks once stood."]\n , exits:\n [ new Exit (39, 0, new Point3d ( 0 , -0.10, 0.45))\n , new Exit (37, 1, 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: ["The long and winding route of Kane Street follows the lower side of the bluffs, and just before it dead-ends at the chapel hill sepulchers it intersects with the old lowertown Market Street. Kane Street was a parkway path before the plague, but since then it's become a sprawling shantytown all its own, with its broad grass lawns buried under haphazard constructions."]\n , exits:\n [ new Exit (14, 1, new Point3d (0, -0.10,-0.45))\n ]\n , zone: 1\n }\n\n , { id: 42\n , room: cube (new Point3d ( 5.5, 2,-8.3), 0.45)\n .addOutline (\n [ new Point3d (-0.10,-0.45,-0.10)\n , new Point3d ( 0.10,-0.45,-0.10)\n , new Point3d ( 0.10,-0.45, 0.10)\n , new Point3d (-0.10,-0.45, 0.10)\n ])\n .addOutline (\n [ new Point3d (-0.10,-0.90,-0.10)\n , new Point3d ( 0.10,-0.90,-0.10)\n , new Point3d ( 0.10,-0.90, 0.10)\n , new Point3d (-0.10,-0.90, 0.10)\n ])\n .addOutline (\n [ new Point3d (-0.10,-0.45,-0.10)\n , new Point3d (-0.10,-0.90,-0.10)\n ])\n .addOutline (\n [ new Point3d ( 0.10,-0.45,-0.10)\n , new Point3d ( 0.10,-0.90,-0.10)\n ])\n .addOutline (\n [ new Point3d ( 0.10,-0.45, 0.10)\n , new Point3d ( 0.10,-0.90, 0.10)\n ])\n .addOutline (\n [ new Point3d (-0.10,-0.45, 0.10)\n , new Point3d (-0.10,-0.90, 0.10)\n ])\n , name: "Sewer of Ravenous Rats"\n , desc: ["This is the old sewer. The way down is a long stair circling an iron ladder: an access ladder converted, at some point in the past, into a passageway for people to walk down. Now it's a damp boulevard, wide with a low ceiling, with moisture oozing from the bricks. The roof is a shallow arch of crumbling old brick, only barely taller than a man. Shorter than some."]\n , exits:\n [ new Exit (32, 3, new Point3d ( 0 , -0.85, 0 ))\n , new Exit (43, 0, new Point3d (-0.45, -0.10, 0 ))\n ]\n , zone: 2\n }\n , { id: 43\n , room: multipath (new Point3d ( 4.5, 2,-8.3), "NE")\n , name: "The Deep Way"\n , desc: ["The sewer curves around a corner, and its brickwork goes from modern clay to ancient stonework. There's engraving over the stones, etched small, in a language you can't read. Barred and bolted grates on either side lead to unknown drainage chambers."]\n , exits:\n [ new Exit (42, 1, new Point3d (0.45, -0.10, 0 ))\n , new Exit (44, 0, new Point3d (0 , -0.10, -0.45))\n ]\n , zone: 2\n }\n , { id: 44\n , room: octogon (new Point3d ( 4.5, 2,-9.3))\n , name: "Who Can Suffer the Weight?"\n , desc: ["The sewer opens into a vast chasm. It yawns downward into utter blackness, to the very roots of the earth. The air is still, and at extreme intervals there comes a keening of winds from the darkness below."\n , "This is the lowest edge of the market."]\n , exits:\n [ new Exit (43, 1, new Point3d (0, -0.10, 0.45))\n ]\n , zone: 2\n }\n ];\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 /* due to the way twine shoves its state data into the browser's history object, it'll choke if we try to store an actual room object in a twine variable (since rooms contain exits and exits contain predicate functions, and apparently functions can't be serialized into the history object). we don't actually need to do that, though, since we can just store the index. we don't ever _use_ this currently, but, here it is if it needs to be used */\n state.history[0].variables["_currentRoomIx"] = ix;\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) { /* TODO/FIXME: have this not generate a list item for an npc item if the tweecode didn't generate any output (e.g., an infcheck that failed). this would mean making several wikifiers and joining them together, probably */\n let npcList = currentRoom.npcs.map (npc => "* " + npc.twee).join(String.fromCharCode(0x20));\n new Wikifier(mapdesc, npcList);\n }\n new Wikifier(mapdesc, "<<display \"map footer\">>");\n\n insertElement (mfoot, 'p', 'exit_p', null, "exits");\n let exitList = insertElement (mfoot, 'ul', 'exits', null);\n\n let exits = {};\n currentRoom.exits.map ((exit, i) => {\n let dir = exit.direction();\n if (!exits[dir]) {\n exits[dir] = [];\n }\n exits[dir].push ({exit: exit, ix: i});\n });\n\n [ ["northwest"], ["north"], ["northeast"]\n , ["west"], ["inside", "outside", "up", "down"], ["east"]\n , ["southwest"], ["south"], ["southeast"]\n ].map (dirs => {\n let eli = insertElement (null, 'li', null, dirs[0], null);\n dirs.map (dir => {\n if (!exits[dir]) {\n return;\n }\n exits[dir].map (exit => {\n eli.classList.add ("used");\n eli.appendChild (exit.exit.buildLink (currentRoom, exit.ix));\n });\n });\n if (eli.childNodes.length == 0) {\n eli.appendChild (document.createTextNode ("\u202f"));\n }\n exitList.appendChild (eli);\n });\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 $_clues to {}>>\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/% initialize the twine-variable store of each npc's vartable %/\n<<set $npcVars = {}>>\n<<set Object.keys (npcs).map (k => {\n let vars = vartable();\n vars["npcVars"][k] = npcs[k].vars ? npcs[k].vars : {};\n })>>\n\n<<endnobr>>
<<set window.topic = {}>>\n<<set topic["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 topic["drunkard"] =\n { name: "drunkard"\n , topics:\n { "do you know...":\n { "any...":\n { "non-wolf beasts": "drunkard non-wolf"\n }\n }\n }\n };\n topic["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 topic["goodbye_spec"] =\n { name: "goodbye_spec"\n , topics:\n { "goodbye": "goodbye interrupt"\n }\n };\n topic["know_nothing"] =\n { name: "know_nothing"\n , topics:\n { "*": "know nothing"\n }\n };\n topic["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 topic["factoryworkerUnique"] =\n { name: "young factoryworker (unique)"\n , topics:\n { "": "transformed young factoryworker splash"\n , "goodbye": "transformed young factoryworker goodbye"\n , "what...":\n { "is your name": "tfed factoryworker name"\n , "is your occupation": "tfed factoryworker occupation"\n }\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 , "the latest killing by the beast-masked hunter": "tfed young factoryworker heard of latest killing bmh"\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 topic["bestialdockworkerUnique"] =\n { name: "bestial dockworker (unique)"\n , topics:\n { "": "bestial dockworker splash"\n , "goodbye": "bestial dockworker goodbye"\n , "what...":\n { "is your name": "bestial dockworker name"\n , "is your occupation": "bestial dockworker occupation"\n }\n , "do you know...":\n { "the beast-masked hunter": "bestial dockworker know beast-masked"\n , "the beast-masked hunter's latest victim": "bestial dockworker know beast-masked victim"\n }\n , "where is...":\n { "the beast-masked hunter": "bestial dockworker where is beast-masked"\n , "the beast-masked hunter's latest victim": "bestial dockworker where is beast-masked victim"\n }\n , "have you heard of...":\n { "the latest killing by the beast-masked hunter": "bestial dockworker heard of latest killing"\n }\n , "have you seen...":\n { "the beast-masked hunter": "bestial dockworker seen beast-masked"\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 topic["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 topic["nightwatchmanUnique"] =\n { name: "transformed night watchman (unique)"\n , topics:\n { "": "transformed night watchman splash"\n , "what...":\n { "is your name": "transformed watchman name"\n , "is your occupation": "transformed watchman occupation"\n }\n , "where is...":\n {"the beast-masked hunter's latest victim": "transformed watchman where is beast-masked victim"\n }\n , "have you heard of...":\n { "the beast-masked hunter": "transformed watchman heard of beast-masked hunter"\n , "the beast-masked hunter's latest victim": "transformed watchman heard of latest victim"\n , "the latest killing by the beast-masked hunter": "transformed watchman heard of latest killing beast-masked"\n }\n , "do you know...":\n { "the beast-masked hunter": "transformed watchman know beast-masked"\n , "the beast-masked hunter's latest victim": "transformed watchman know beast-masked victim"\n }\n , "have you seen...":\n { "the beast-masked hunter": "transformed watchman seen beast-masked"\n }\n , "where were you...":\n { "last night, after second bell": "transformed watchman last night second bell"\n }\n }\n };\n topic["maskedChurchmanUnique"] =\n { name: "masked churchman (unique)"\n , topics:\n { "": "masked churchman splash"\n }\n };\n topic["ladyNightUnique"] =\n { name: "lade of the night (unique)"\n , topics:\n { "": "lady of the night splash"\n , "do you know...":\n { "any...":\n { "suspicious people": "lady night know suspicious"\n }\n }\n , "what...":\n { "is your occupation": "lady occupation"\n }\n , "have you heard of...":\n { "the beast-masked hunter": "lady heard of beast-masked"\n , "the beast-masked hunter's latest victim": "lady heard of beast-masked victim"\n , "the latest killing by the beast-masked hunter": "lady night heard of latest killing beast-masked"\n }\n , "have you seen...":\n { "the beast-masked hunter": "lady night seen beast-masked"\n , "the beast-masked hunter's latest victim": "lady night seen beast-masked victim"\n , "any...":\n { "suspicious people": "lady night seen suspicious"\n }\n }\n , "where were you...":\n { "last night, after second bell": "lady night where last night second bell"\n }\n }\n } >>\n\n<<set window.npcs = {}>>\n<<set npcs.youngFactoryworker =\n { title: "Young Factoryworker"\n , vars: { mode: "wary"}\n , dialog:\n [ topic["factoryworkerUnique"]\n , topic["goodbye_spec"]\n , topic["know_nothing"]\n ]\n };\n npcs.bestialDockworker =\n { title: "Bestial Dockworker"\n , qtag: "npc_idriss"\n , vars: {}\n , dialog:\n [ topic["bestialdockworkerUnique"]\n , topic["genericBeastmasked"]\n , topic["goodbye_spec"]\n , topic["know_nothing"]\n ]\n };\n npcs.nightWatchman =\n { title: "Night Watchman"\n , qtag: "npc_suggins"\n , vars: {}\n , dialog:\n [ topic["nightwatchmanUnique"]\n , topic["goodbye_spec"]\n , topic["know_nothing"]\n ]\n };\n npcs.ladyOfTheNight =\n { title: "Lady of the Night"\n , qtag: "npc_odessa"\n , vars: {}\n , dialog:\n [ topic["ladyNightUnique"]\n , topic["goodbye_spec"]\n , topic["human"]\n , topic["know_nothing"]\n ]\n };\n npcs.maskedChurchman =\n { title: "Masked Churchman"\n , vars: {}\n , dialog:\n [ topic["maskedChurchmanUnique"]\n , topic["genericBeastmasked"]\n , topic["goodbye_spec"]\n , topic["know_nothing"]\n ]\n };\n npcs.genericHuman =\n { title: "Idler"\n , dialog:\n [ topic["rumormonger"]\n , topic["human"]\n , topic["goodbye_spec"]\n , topic["know_nothing"]\n ]\n }>>\n<<endnobr>>
<<set $interrupt to true>>\\n<<autoresponse "That'll be all for now.">>\nYou take your leave.\n\n[[=>|map]]
<div class="npc">I don't know anything about that.</div>
<<display "masked hunter intro blurb">>\n\n<<display "tutorial">>\n\n[[=>|map]]
> <span class="clue">Text like this</span> is a //clue//, which gives you information: maybe about the person you're talking to, maybe about the case you're on. Every case has a place where you can reflect on your current clues and piece them together into new clues for further investigation; where specifically depends on the case.\n> \n> When talking to people, some clues can be clicked to ask a //follow-up question//. This could be useful and reveal more clues, or lead nowhere and waste time and favor -- or it could be dangerous. Maybe that clue was a slip of the tongue, and by drawing their attention to it, you're drawing their attention to //you//.
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<<set $bmh_active to true>>\\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">>
<<nobr>>\n<div class="npc">Yeah, he's that mysterious hunter, right? Nobody's really seen him, though.</div>\n<div class="npc">Still, can't argue with that many bodies.</div>\n<<endnobr>>
<<nobr>>\n<div class="npc">Yeah, last night, right? There was a big crowd down in the undercity.</div>\n<div class="npc">Sounded like some people found the body while it was still warm.</div>\n<<endnobr>>
<<nobr>>\n<div class="npc">The guy he killed, it was <<clue "Mikhael the Crusher" "bmh" "mikhael">>, right? That's what everybody is saying.</div>\n\n<<autoresponse "So, do you know him?">>\n\n<div class="npc">Know //of// him, maybe. I'm not associated with those folks.</div>\n\n<<endnobr>>
<<set $_back to "rats clues">>\\nKnowledge related to the beast-masked hunter:\n<<investigate "bmh">>\n<<block compare>>\n<<if $_compareFailureMsg>><<print $_compareFailureMsg>><<endif>><<endblock>>\n\n[[back to the city|map]]
<<if mapData[$_pos].name === "Where Rats Drowned" && $bank === "north">>\\nYou look at the flagstones. There's still a red-brown stain where Mikhael was killed, and then a smear going to the west that cuts off abruptly, which might be the trail from when his body was removed. It's hard to say if there's anything to be gleaned from this.\n\n[[<=|map]]\n<<else if mapData[$_pos].name === "Where Rats Drowned" && $bank === "south">>\\nYou spend some time looking at the site of the murder from across the street. Nothing in particular catches your eye. The dockfront is dark and slick, and you don't have a great view of the promenade itself -- there are market stalls in the way, now, though who can say if they were set up last night.\n\n[[<=|map]]\n<<else if mapData[$_pos].name === "Where Floodwaters Flowed" || mapData[$_pos].name === "Where the Water Runs Black">>\\nYou look at the flagstones. The market has been so busy today, even after the murder, that whatever trace Mikhael made are long gone, if there ever was any evidence.\n\n[[<=|map]]\n<<else if mapData[$_pos].name === "Nameless Street">>\\nYou pace through the alleyway. There are scuff marks on the walls that could be claws; some are still fresh. There are loose flagstones; they could have been dislodged recently. Or it could be nothing: the alley is quiet, disused, and secluded from the usual ebb and flow of the market. Loose flagstones could be anything.\n\n[[<=|map]]\n<<else if mapData[$_pos].name === "Sewer of Ravenous Rats">>\\nThere are scuff marks across the walls, old grime recently scraped away. There's a dark splatter of blood. You experimentally brush a sleeve across an unmarred section of the wall, and grimace at the mess it makes of the fabric, while hardly changing the appearance of the wall. It takes scraping metal or leather to leave marks at all similar.\n\n[[<=|map]]\n<<else if mapData[$_pos].name === "The Deep Way">>\\n<<display "bmh hideout">>\n<<else>>\\nYou spend some time checking out the area, but don't find anything relevant.\n\n[[<=|map]]\n<<endif>>
You check several of the unbarred grates. It's a minute's walk down a pitch-black tunnel, only to end at the bottom of a dropshaft. You suppose it's possible for someone to scale them, but not likely.\n\nThe fourth one you check is the jackpot. There's a rubble of fallen bricks, and the rubble is still fresh -- your fingers come back stained red with the powder, not yet soaked and washed away from the dampness of the sewers. There's a hole in the passageway, large enough for a man or beast to walk through, and past that, there's a dark passage with a flickering light at the end, past a corner.\n\n<<set $bmh_investigate to false>>\\n\n[[=>|bmh endgame]]
It's the beast-masked hunter's hideout.\n\nYou presume: it looks more like a leatherworker's workshop. It's dimly lit with an oil lamp, with strips of unworked leather hanging in strips, and covered tubs of strong-smelling lye. It hadn't occurred to you before, but it's possible that the beast-masked hunter tans the hide of his prey and works them into his as masks. Or perhaps it's more terrestrial leather; far be it from you to speculate.\n\nIf Mikhael was searching the undercity for the beast-masked hunter, and found this workshop, and the ensuing brawl brought them back up into the undermarket...\n\n...or if the beast-masked hunter captured Mikhael and brought him down here, still alive, only for Mikhael to escape and work his way to the surface...\n\n...either way, that would certainly explain everything. This is a secret the beast-masked hunter would be willing to kill to keep hidden.\n\nThere's a prickle up your spine. You turn, and find [[a figure blocking the exit|bmh confrontation]].
It's the beast-masked hunter. He is, in fact, wearing a mask: a wolf-head mask, made from broad strips of black leather, woven together, with rivets along the seams. His eyes gleam, luminous yellow slivers in the dark pits of the mask.\n\nHe's not much taller than you: humanoid; a little rangy, rather than muscled; with a spray of curly red-orange hair bursting out from the back of his mask. He's got a pair of metal claws clasped in his hands, shaped like beast's claws: the fabled starmetal claws he uses against the beasts. They look like black iron to you. It seems impossible that he could generate the force required to claw through Mikhael's body, just, a question of physical strength and human limbs.\n\nHe's more than a little battered. His clothes, a workman's outfit of overalls and a heavy jacket -- maybe a leatherworker after all -- are ragged at the seams, and several have split entirely, making his outfit half-rags. One of his sleeves has been torn open, and it's drenched in blackened blood.\n\nIf he killed Mikhael, you're no threat at all. You raise your hands.\n\n<<autoresponse "I'm not here to kill you.">>\\n\\nHe cocks his head to the side. Waiting for more.\\n\\n<<autoresponse "But if I can find you here, there are a lot more coming, and soon. You should abandon this place, quickly.">>\\n\\n<p>You're telling him things he already knows.</p>\\n\\n<p>His voice, when he speaks, is an animal's rasp.</p>\\n\\n<div class="npc">Those that come, I can kill. What beast is a match for me?</div>\\n\\nHe takes a step forward, fingers gripping his claws tighter.\\n\\n<<autoresponse "People know where I was looking. You kill me, they come down here.">>\\n\\nHe snorts, a rough bark of a laugh ripping up his throat.\\n\\n<div class="npc">Ah. A coward.</div>\\n\\n<<autoresponse "Who wants to die? Of course I'd want to avoid it...">>\\n<<autoresponse "And I have no quarrel with you. I got a proposition.">>\\n\\n<div class="npc">All this, hunting me down, risking your life, just to give me a message? Well, give it.</div>\\n\\n<<autoresponse "You attracted a lot of attention, killing beasts. With Mikhael dead, even the Royals might get involved, and-- you're strong, but nobody's //that// strong.">>\\n<<autoresponse "Alone, they'll track you down and kill you. You think you can stand up to the Executioner?">>\\n<<autoresponse "You wanna take down the beasts? I have some friends, see. They think maybe the Royals' reign has gone on long enough. But it's gonna take a hell of a lot of power to do that. They'd be very interested to speak with you.">>\\n<<autoresponse "You need a new place to lay low for a while. Might as well be with them.">>\\n\\n<div class="npc">These friends of yours. They think //they// can stand up to the Executioner?</div>\\n\\n<<autoresponse "They might.">>\\n\\n<div class="npc">They beasts?</div>\\n\\n<<autoresponse "Some of them.">>\\n\\nHe knocks his claws against his thigh.\\n\\n<div class="npc">Interesting.</div>\\n\\n<<autoresponse "It hasn't escaped their notice that you went after the most brutal of the beasts. The Royals -- to them, humans are just meat. My friends, they got nothing against humans.">>\\n\\nYou gesture at yourself:\\n\\n<<autoresponse "That's why they sent a human to talk.">>\\n\\nThere's a long pause.\\n\\n<div class="npc">I'll consider it.</div>\\n\\nHe steps aside. You don't yet take the opening.\\n\\n<div class="npc">But if they send anybody else down here, human or not, they won't like what happens to 'em.</div>\\n<<set $bmh_active to false>>\\n\n<hr>\n\n<div class="meta">[[that's the end of the demo folks|demo end]]</div>
<div class="meta">that's all so far!\n\nthis is a very early demo release, so maybe check out <a href="https://xax.itch.io/the-new-hive">the new hive</a>, my last project, for something more complete. or check out <a href="https://patreon.com/xax">my patreon</a> for more up-to-date versions, votes on future content, and other development stuff.\n\nthis version is mostly a tech demo of the dialog/investigation mechanics. pretty high up on my to-do list is to totally change the actual case details to give the case more of a sense of trajectory. right now it's mostly just comparing everything with everything when there are a bunch of redundant clues; it's really not ideal. it turns out making a detective puzzle is like, complex game design. oh well, i'm sure i'll get better at it as this goes.\n\n(likewise, some of the sex scenes are pretty abrupt -- not that i'm opposed to having some of them being basically just cruising, but in practice as i flesh out the setting & characters outside of an active case i'll probably tweak some of the interactions to turn them into more of a visual novel thing where you actually have to, you know, talk to people for a bit before the option to hit on them shows up.)\n\nanyway hope you all enjoyed it and are interested in seeing how it grows from here! the plan is to have monthly releases, with each one focused on fleshing out a particular section of the game, which should avoid some of the writing issues i had with 'the new hive'. smaller updates, more focused, more frequent.\n</div>\n\n<hr>\n\n\n\n<<autoresponse "But before you run off to another hidey hole...">>\\n<div class="responses">[[You wanna fuck around some?|post-credits]]</div>
He tilts his head to the side.\n\n<<autoresponse "You probably got time!">>\n\nYou say it a little defensively.\n\nHe makes a rough, rasping noise from under the mask; it takes you a moment to realize he's laughing.\n\n<div class="npc">Hunting for more than my hideout, aren't you?</div>\n\nHe exhales; in the chill sewer air, his overheated breath billows out under his mask as a fog. He stalks forward: different than his threatening posturing from earlier, and not just because he tosses his metal claws aside.\n\n<div class="npc">Sure. Let's fuck.</div>\n\nHis eyes gleam behind his mask, catching the light red-green. He pins you to the wall, grip iron-hard. This is, after all, the man who tore Mikhael apart with his bare hands. His body is all lean, hard muscle under his clothes, unnaturally hot as he presses your bodies together.\n\n<<autoresponse "That's a little--">>\n\nHe shifts a thigh between your legs, grinding up against your balls. You cut off with a sharp exhale.\n\n<<autoresponse "That's a little abrupt, huh?">>\n\nHe laughs against your shoulder, shrugging off his jacket, leaving him in his dark overalls and undershirt. His arms are covered in freckles, so many and so thick they dapple across the backs of his arms and spread across his shoulders, and the red hair across his forearms is thick and dark.\n\n<div class="npc">You're the one who offered.</div>\n\nHe shoves his mask up, leather muzzle askew, revealing a stubbled jaw and scarred lips, pulled back to reveal his teeth. He's got two sets of canines. He scrapes his jaw up across your throat, fangs leaving stinging lines, and wetly sucks at your Adam's apple. There's a flush all down your body, skin prickling, cock thickening in your pants. You can feel the hard line of-- his sheath; he has a sheath-- press against your stomach. He repositions himself, unbuckling something, loosening his rough overalls, and his slow humps get a lot more drag with loose fabric shifting between you.\n\nIf the Beast-masked Hunter is half-beast himself, that answers a lot of questions about how he managed to fight against beasts. His animal cock knifes up against his undershirt, seeping pre onto the fabric, and he reaches down and gropes your crotch, fingers curling back against your balls, palm grinding down against your shaft. You let out a reedy moan, rutting into his touch, rolling your hips up against his. You reach up and tug at the straps of his overalls, shoving them off his shoulders, and his pants are dragged down an inch lower by the heavy material, straining around his sheath. You slide your arm down between you, pushing into the confines of his pants: hot, slightly sweat-damp, his fur thick and curly as you curl your hand around his sheath, stroking it along his unsheathing cock. He moans into your throat, biting up your neck to your jaw, and hungrily kisses you as you stroke him off. He tugs at the hem of your shirt, pulling back to drag it over your head, hands sliding down your chest to rub against the plane of your stomach, his slightly-pointed fingernails dipping under the waist of your pants.\n\nHis dick spurts pre all across the back of your hand, unsheathing in jackrabbit jerks, and he shoves his undershirt up and his pants down, cock jolting out into the open. His sheath is framed in a shaggy spray of tawny red-orange fur, cock itself blood-red as it juts up from his crotch, bloated and heavy. His balls loll beneath, bouncing off his thighs before drooping down lower, shaggy skin stretched out around a pair of heavy, fist-sized balls. His cock, only unsheathed a handspan, burbles out a messy cord of pre down his length, making the red flesh glimmer in the dim light. You curl your hand back around it with a wet squelch, oily heat smearing across your palm as you stroke, and he dips his hand into your pants, curling his hand around your erect cock, stroking you slowly as he pulls your pants down to mid-thigh, baring your cock as well.\n\nHe humps against you, cocks sliding together, his bestial length slowly lurching further and further from his fat sheath until it spans up his tightly-defined stomach, pointed tip digging against the underside of his pectorals. You play with the tip, a rubbery spur slick and wet with his rich, rank-smelling pre, squelching across your hand as you slop your fingers across the curve of his cocktip. Your own cock, big for a human but still //human//, butts up against the bloated middle of his shaft, loose flesh becoming slick and frictionless with his own copious pre. Your thrusts drag against each other, cocks slipping along a slimy trough pinned between you, his loose grip adding just enough friction to send sparks of pleasure rippling through you. His kisses you hungrily, fangs digging into your lips, tongue heavy as he shoves it into your mouth, gurgling out a feral growl as he humps you like an animal, cock gushing sloppy spurts of pre over your entire chest, drizzling down to coat his already-slathered shaft.\n\nHe makes a soft, needy sound when you break the kiss, mouth open, his lips just as bruised as your own. You give his cock a final stroke, from the tip all the down to the base, knot still a turgid lump within his sheath, and sink down to your knees before him, letting his cock smear all the way up your chest until it smacks you in the chin, lurching across your face to rest, heavily, right between your brows. Its smell is like getting punched in the face: rank and meaty, spicy, and so potent it stings when you inhale, making your eyes water and your mouth drool uncontrollably. He croons above you, face still half-obscured by the mask, but his mouth open, slavering as he stares down at you.\n\nYou lick across the underside of his cock, catching a slobbery mess of frothy, churned-up pre across your tongue, and struggle to swallow it, burning like liqueur down your throat. His cock twitches, smacking solidly across your face and spewing out a rope of pre that splatters across /%{your hair}%/your hair, oozing down over one eyebrow. You squint as you tilt your head up, slobbering all across the underside of his cock, sliding up his shaft until you can suck the tip into your mouth. You stroke the rest of his shaft, playing with his sheath, and he lets out a buzzing groan, sloppy strings of drool spilling over his lips to splatter down across your face, his bestial slobber just as acrid as his pre. He humps your face, hands gripping your head, less trying to fuck your mouth and more rubbing his cock all over it, working the slimy mess coating his shaft into your skin until you're glazed. Your lips are already bruised, stinging with bitemarks from his fangs, and they burn more as you open wide to catch his shaft between your lips, tongue lapping across his vividly-red flesh.\n\nYour cock, hard and still glazed with the same frothy mess coating his shaft, almost accidentally glances against his calf, grinding over the ankle of his boot, and a shockingly loud groan rips its way up your throat as you press down, cock dragging against the stiff leather. He obligingly shifts his leg, letting you hump against his leg. He's breathing hard, drool continually bubbling over his lips and spilling down all over you in stringy lines, drenching you in slobber and pre. He drags a hand across your face, obscuring your vision under a layer of slimy whorls, smeared up your cheeks and over your eyebrows, and then insistently shoves his fingers into your mouth, letting you lap up the frothy slurry. You groan around his fingers, throat buzzing, and that's what finally gets his knot to surface, pushing its way out of his sheath with a meaty //pop//, bulky and fist-sized even mostly soft. You suck across its curves, tongue tracing the gristled seams where its fat chambers meet with the rest of his shaft, and all the while his cock is jutting straight up, streamers of pre webbing across your /%{hair}%/hair, sluicing down your face, hanging in a line of jiggling cords across your jaw. His pre is almost jelly-like, churning up into a mess of greyish bubbles, coating your mouth and throat and making the act of swallowing strange and frictionless.\n\nYou worship his knot, hands sliding up to play with his shaft as you kiss and suck across its bulk, lips spreading across the dark red flesh with a //squelch//, pulling free with a lewd //pop//, leaving you to lap across the churned-up mess you've left behind, gulping down his frothy pre until his knot is dully shining. He groans, hand on the back of your head, guiding you on as you shove your tongue into his sheath or vainly strain your lips to suck his entire knot into your mouth.\n\nDizzily, you feel your own orgasm approach -- cock sliding across his legs, the drag of the heavy fabric and the slick slide of his leather boots -- and you moan, eyes shut, just focusing on the heavy weight of his cock across your face, of the throb of his knot like a heart, how you can feel his pulse through your lips, spread out across his bloated flesh. You let out a keening whine, almost sobbing as you hunch forward, cock slick with pre, each thrust bringing you closer, closer, closer, until you finally cum with a groan, painting messy streaks all up his pant leg as you grind your slick, slippery cockhead against a fold in the fabric.\n\nYou suckle across his knot the whole time, its weight a continual reminder, and the sight when you open your eyes -- squinting through one, with a web of slime dragging against your eyelashes -- makes your cock lurch and spurt again. The Beast-masked Hunter is above you, his knot in your mouth, lean muscular body wet with sweat and shaking from the force of his panting breaths. He's hunched forward, mouth open, fangs on display, drooling down across your body, webbing you together with streams of bestial slobber.\n\nHis knot throbs against your mouth, cock squirting pre onto the wall behind you, and he snarls deep in his chest as he draws back, thrusts forward, sliding his monstrous shaft across your bruised lips once, twice, a third time all the way down to the knot, before he lets out a howl and cums, cock erupting with the force of a cannon, spraying a sharp blast of cum point-blank against the wall behind you, making its backsplash spray everywhere. You curl your hands around his shaft, stroking it as you aim it down, making sure his next shots catch you full in the face, rubbery and thick burbling over your lips, swilling in the bottom of your mouth. You smear some of the mess down his shaft, squelching around his knot, and his snarl, never quite died-down, raises up into a buzzsaw roar as his bloated knot beats hard between your hands, almost too-hot to touch, disproportionately huge even on his bestial cock. He drenches your face in gush after gush of thick grey beast cum, tethering you solidly together with the finger-thick ropes clinging tight across your skin, again and again in slimy layers until the semisolid mass finally starts drooling down your face in murky sheets.\n\nHe drags his hands across your face on the way to wrapping them around his cock, all four of your hands together nearly enough to cover his shaft, and you provide a slick, clenching tunnel for him to fuck, spewing out his slimy load all over your face each time he bottoms out, making a drenched, dripping tunnel that spills cords of cum all down your forearms, dripping from your elbows.\n\nHis cock keeps spurting for minutes, pulse after pulse of cum splattering against you until you entirely lose the reflex to jerk back with each splatter, just letting him paint your face, your chest, the wall behind you, with cum. When his pulses finally slow, it turns into a soupy mess drooling out, pissing out the dregs of his load in a wet pile across the crown of your head, oozing down through your slathered hair to cut, slimy and wet, thought the half-dried mess.\n\nHe pulls back, cock still bobbing before him, and the ropes of slimy cum painted across your face are still tethered to him -- they crawl over your skin like enormous slugs, stretching out between you in fat, swaying strands.\n\n<div class="npc">'s been a while. I really needed that.</div>\n\nHe looks down at you -- you assume, eyes caked with cum, knees squelching in the churned-up puddle he left behind. You wave a hand between you, dragging out the cords, some of them squirming off your face and slapping across your forearm. His grin is lopsided, showing off his fangs.\n\n<div class="npc">I'll be sure to return the favor when I see you again.</div>\n\n<<autoresponse "I'll be waiting.">>\n\n<hr>\n\n<div class="meta">//that's// the end of the demo</div>
<div class="npc_title"><<print npcs[$_conv].title>><<if getConvVar ("name")>> "<<print getConvVar ("name")>>"<<endif>></div>\\n/%<<if npcs[$_conv].qtag>><<nobr>><code>[debug npc info data dump:\n\n<<clues npcs[$_conv].qtag>>\n\n]</code><<endnobr>><<endif>>%/
<<display "intro blurb">>\nThe city fell only recently, but the rot has been bubbling beneath for years.\n\n<<set alterConvVar ("visited", x => !!x + 1)>>\\n<<nobr>>He'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.\n\n<<if getConvVar ("mode") == "suspicious">>/% after getting him mad %/\nHe hears you coming and glowers, eyes catching the light and reflecting red-green.\n\n<div class="npc">You again.</div>\n<div class="npc">What're you gonna bother me with this time?</div>\n\n<<autoresponse "I just have a few more questions...">>\n\n<<else if getConvVar ("mode") == "chaser reject">>/% after he's rejected you %/\n[...]\n\n<<else if getConvVar ("mode") == "rejected">>/% after you've rejected him %/\n[...]\n\n<<else if getConvVar ("mode") == "fucked">>/% after you've fucked %/\n[...]\n\n<<else if !getConvVar ("met")>>/% first time %/\n Only the way he tilts his head side-to-side, like a confused dog, when he hears you approach gives him away.\n\n<div class="npc">What do you want?</div>\n\n<<autoresponse "I just have a few questions for you...">>\n\nHe glowers.\n\n<div class="npc">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">You and everybody else. You think I know anything? Go ahead, ask away.</div>\n\n<<set alterConvVar ("met", x => true)>>\n<<else>> /% revisit without other state change %/\nHe hears you coming and tips his head to watch you approach.\n\n<div class="npc">You again.</div>\n<div class="npc">How's your hunt going?</div>\n\n<<autoresponse "No luck so far.">>\n\nHe snorts.\n\n<div class="npc">Wouldn't expect any.</div>\n\n<<autoresponse "I just have a few more questions...">>\n/% heat up from talking to him again, but only the once %/\n<<endif>>\n\n<<endnobr>>
<<set $interrupt to true>>\\n<<autoresponse "I think that's all for now.">>\n\n<<if getConvVar ("mode") == "wary" && getConvVar ("visited") > 1>>\n<div class="npc">Wait a second.</div>\n\nYou look back at him; you haven't yet gotten up.\n\n<div class="npc">If you're gonna be around here stirring up trouble, you might as well make yourself useful.</div>\n\nHis face breaks open in a craggy leer.\n\n<div class="npc">You wanna go up to my room?</div>\n\nHis friends hoot a little, his interest transferred over to them. Their eyes turn towards you.\n\n<div class="responses">\\n<<response "Sure, we can fuck." "tf factoryworker room fuck intro">>\n<<response "Not interested." "tf factoryworker reject">>\n</div>\n<<else>>\nHe gives you a lazy, ironic salute as you go.\n\n[[=>|map]]\n<<endif>>
<<set alterConvVar ("mode", x => "rejected")>>\nHis face collapses into an angry scowl.\n\n<div class="npc">Fine, be like that. You don't know what you're missing out on.</div>\n\nYou leave.\n\n[[=>|map]]
<<set $interrupt to true; reshow("asktable")>>\\nHis leer grows until his bared teeth take up most of his face.\n\n<div class="npc">Good.</div>\n\nHe gets to his feet, towering over you, and curls one clawed hand around your wrist, guiding you with him. He turns around, walking backwards towards the stairs as he pulls you along; this just makes his gait even more unnatural. He lopes.\n\n<div class="npc">You're not too bad-looking. Your face is gonna look real good when I got my knot sunk in your ass.</div>\n\n[[=>|tfed factoryworker bed fuck]]
He takes you up the stairs and down the hallway to the room with the "<<clue "II" "npc_alexi" "alexi staying in room #2 at last hope hall">>" placard. He unlocks the door and pulls you inside.\n\nHis rooms are fairly bare: a spill of clothes from the open wardrobe, and some papers on the desk, and a bed with crumpled sheets. There's a small window beside the bed that looks out over the lower roof. <<if getConvVar ("name")>>Alexi<<else>>The factoryworker<<endif>> looms in front of you, pushing you back against the door as he closes it.\n\nHe stoops down, pushing his mouth against your neck, and snuffles across your skin, lips parting to let his fangs rasp across your throat.\n\n<div class="npc"> Gonna enjoy wrecking you.</div>\n\nHe snarls across your throat, his breath catching when you tilt you head up to give him better access. His tongue unfurls from his mouth, heavy and wet digging into the flesh of your throat as he laves across your skin, a hungry buzz building in his throat. His hands curl around your forearms, pinning you back against the door with his wiry, inhuman beast's strength. He practically envelops you in his lanky body, his long limbs folding down to wrap around you: his knees bracketing your thighs, elbows pressed to your shoulders, forearms long spars down your sides. He fists a hand into the fabric of his shirt and tears it apart with a single jerk, sending scraps of fabric draping across his chest as he tosses it aside. You take the opportunity to strip out of your shirt and toss it aside before he can tear it apart too, leaving you both bare-chested.\n\n<<if getConvVar ("name")>>Alexi's<<else>>His<<endif>> core looks human: body lean and muscular, tightly toned, but still human, with only a scarce dusting of grey-blond hair across his chest and down his stomach. Only two nipples. His sheath makes an unmistakable bulge in his trousers, blocky and heavy when he grinds your bodies together, filling the air with the rank smell of beast. His claws slide across your chest, feeling the slope of your muscles, your nipples, pebbled in the air. He scrapes the edge of his claw over your peaked nipple, and you let out a sharp breath at the sensation; he lets out a bestial snarl, pulling you tight against his chest, and ruts his hips, sliding the fattening mound of his sheath against your stomach, cock stiff as it smacks against you. You slide your hands down, fingers curling on the waist of his pants, thumbs sliding across the rope tie of his belt, back and forth over the pale hair of his stomach. His muscles convulse under your touch, his entire body spasming, and he snarls again, cock pulsing and staining the front of his pants with his thick, slimy precum.\n\nHe practically hurls you across the room: twisting fast and pushing you backwards, your feet stumbling on the floorboards until you hit the bedframe and topple backwards. You tug on the laces of your pants, letting them sag lower on your hips. He looms over you: eyes gleaming, lips pulled back, slavering, his cock stabbing at the front of his pants. He tears them off just as easily, leaving only a wet shred of fabric draped over his cock until he plucks that off too, leaving him totally naked. His forearms and calves are the most transformed -- long animal limbs, coated in stiff white-grey fur that tapers out up his biceps and thighs. And then, framed neatly in a tangle of hair between his thighs, is his beast cock: lopsided balls each the size of a fist, with the bone-white spar of his cock pushing out from his leathery sheath. He lurches forward, pinning you down, drooling across your face and neck, and his cock unsheathes wetly between you, wetly burbling up across your stomach. Only the tip is white; it mottles pale pink past that, with only a few white splotches past that. He gets worked-up easily; his knot grinds against your thigh, still beneath his sheath, but as he keeps humping against you it steadily works its way up, more and more of his cock unsheathing in fits and jerks until his knot pops out into the open, flesh ash pink with deep, dark purple veins mazed across its surface, winding their way down to the root of his cock.\n\nYou rut together like animals: his thrusts work down your pants until your cock grinds against the stiff fur of his sheath. You wrap your hands across his back, groaning into his shoulder as he snarls and drools, tongue flopping from his mouth and smearing frothy drool all up your neck and the side of your face. His cock is a brutal skewer: long and thin -- not quite as thick as your wrist -- with a sharply-pointed tip that shudders and ripples. He drools out thin, acrid precum across your chest, dousing you with the rank, animal smell, hungrily chewing across your neck as he humps you in wet strokes, lathering up his pre into waves of opaque sludge.\n\nHe pulls back, cock webbed to your stomach with thick, grimy lines of pre, slithering over your skin as it pulses. He strokes himself, catching the slathered mess across one hand and then smearing it back across your chest.\n\n<div class="npc">I'm gonna fuck you hard.</div>\n\nHe raises your left leg -- you kick your foot out of your pants, now pooled at your ankles -- and licks a slobbery stripe from your knee across your calves, matting down your hair. He uses his other hand to slide between your thighs, claws prickling across the curve of your ass, to rub across your asshole. His fingers are still slick with pre, and he slides his claws back and forth, smearing it all against your hole. He draws back, cock an enormous poker hanging off his body, and he stabs forward, tip glancing off your balls, sliding between your thighs, skewing against your cheeks. He lets out another burst of stringy pre, glazing your skin with his rank slime, and he fucks through that, churning it up into grimy dollops, pushing the mess into your asshole. He fucks you open, cocktip stabbing inside and immediately pulling back, spreading you enough for the next gush of pre to splatter shockingly deep inside you: wet and hot, slick oozing out over your asshole, and he fucks in again, gushing inside you.\n\nYou let out a hoarse groan, and he takes both your legs in hand and folds you double, getting a clear view of your ass as he sinks his bestial cock into you. He mounts the bed, one clawed paw digging into the frame for leverage, body a mass of twisted limbs above you, mouth split open into an impossibly-wide grin as he bears down harder. He snarls from deep in his chest, cock squelching and slurping inside you as he carves your asshole open, and you groan and clasp at his forearms, going bug-eyed as he sinks into your guts. He croons, gurgling around his lolling tongue as he stares down at you, watching your face change as he stabs deeper, pulls back, stirs his cock around inside. Your guts warp around his shaft, clinging tight, each thrust knocking the breath from your lungs, and you whine, asshole clenching and grasping his shaft as he slides deeper. Pre gurgles inside you, squirting out around his pumping shaft.\n\nYou arch your back and push down, opening yourself so that his next thrust slides deeper than either of you were expecting: nearly the full length of his cock stabbing deep into your guts, making your stomach swell out with the length of his cock, his knot smacking hard against your cheeks as he almost bottoms out inside you. He groans, hunching over you, humping you like a dog: cock ripping out and plunging back in, knot spanking your cheeks in an obscene wet tattoo. He's so deep that when he spurts pre you only feel it as a diffuse pressure, wet and heavy slickly pouring inside you, churning up with his thrusts into a sloppy lather. Cords of slimy pre web across his knot, squelching against your dripping ass as he mashes down and then spooling out again when he draws back.\n\nHe grinds his knot against your hole, pressing its beating swell against your bloated lips in an obscene kiss. Pre burbles out of you around the huge crest, drizzling down the slathered crack of your ass, and he flexes, watching as his cock lurches in your guts, sending the long bulge of his cock twisting inside you. He beats his knot against your hole, watching as you whine and squirm, asshole flexing open in shuddering jerks. When he pulls back you feel hollowed out: guts empty, hole gaping, only to be filled aching when he shoves back in. The heavy, curving walls of his knot slop against your gaping hole, halfway slipping in only to be yanked back out again, over and over until your hole is a shuddering, pulsing mess, vainly trying to clench around his knot as he batters it into you. He finally leans down, tongue slobbering all across your face, shoving into your open mouth, and bears down hard: claws tense on your arm, cock jerking hard inside you, as his knot scrapes its way through your tight passage, popping inside you with a sudden lurch and a blossoming of heat. You moan around his tongue, gurgling as you gulp down his frothy drool, and he settles atop you, sinking his knot into your guts. His sheath grinds against your broken hole, leathery skin folding up into a dense mass, and he cums almost as an afterthought: knot hardening, cock twitching rhythmically. He floods your guts, his cum gurgling deeper into your plugged guts, and he sloppily makes out with you, licking your face, slavering across your jaw, as he stirs his knot around inside you. You groan, straightening to lessen the pressure against your flooded guts, and the movement sends his load swirling deeper, audibly sloshing as heat blooms deep inside you. You huff for breath around his tongue, whining into his mouth, and he purrs in response, chest buzzing as he dumps his load inside you, pulse after pulse flooding you until you ache with fullness.\n\nEventually he slumps to the side, enveloping you in his limbs, keeping you seated tight on his pulsing knot as his cock starts steadily pouring into you, pissing out beast cum into your guts. His balls squirm against the backs of your thighs. Your stomach swells slowly, cum gurgling deeper, flooding you until you look pregnant: belly enormous, its heat radating through your entire body, making you flushed and sweaty. His claw strokes over your stomach, digging in hard enough to knock the breath from your lungs, and he laughs lowly against your shoulder as he makes a game of it: palm outstretched across your great wobbling belly, grinding down hard, his cock pulsing sharply inside you every time you gasp and wheeze.\n\nThe dusk light has faded considerably by the time his knot starts to soften. Cum leaks around the loosening seal, squirting out into his sheets when he bears down, and he kneads your belly with both hands, forcing sloppy squirts of cum out around his turgid knot, grinding down so deeply you think you'll have big handprint bruises all across your stomach. He wrenches his knot out with an explosion of watery cum, only to grind it back against your bruised, broken hole, forcing it back inside with a wrench that makes your entire body spasm. He sluggishly knotfucks you with his shrinking knot, stemming the flow of cum only to pop his slippery knot out again with an enormous gush, over and over until you can see the shifting swell of his cock inside you again. His sheets are soaked and dripping, mattress sodden, by the time he pulls himself entirely free, leaving your broken, cratered hole only sluggishly oozing a sloppy waterfall of churned-up beast cum.\n\nYou take gasping gulps of breath, entire body drenched in sweat. He idly strokes his cock, milking his knot, burbling out a slimy mess of greyish cum across your side.\n\n<div class="npc">That was real nice, but you should probably get going now.</div>\n\nHe nuzzles you from behind.\n\n<div class="npc">You're a good lay.</div>\n\nYou drag yourself to your feet -- cum winding its way down your thighs, hole feeling like an enormous bruise between your cheeks, swollen and scraping over itself -- and try to take a few wobbly steps. You clean yourself up as well as you can manage with his sheets; he doesn't seem to mind the mess: he stays sprawled out on his ruined mattress, body slathered with his load.\n\nYou get dressed, hissing in pain when you have to bend down to pull your pants up. Your skin is still tacky with cum, fresh cum still leaking from your wrecked ass, as you pull your clothes on and stagger to the door. Outside, you have to lean against the wall and take a few deep, gasping breaths before you can keep walking.\n\n[[=>|map]]
/% what... is your name %/\\n<<set alterConvVar ("name", x => "Alexi")>>\\nHe languidly extends a hand to shake. His forearm is a beast's claw: grey-furred, with thick black claws.\n\n<div class="npc">Alexi.</div>\n\nHis grip is brutal. He could probably rip your arm off, and he's making sure you know that.\n\nYou give him your name.
/% what... is your occupation %/\\n<div class="npc">What's it your business? I work over at the <<clue "foundry" "npc_alexi" "works at foundry">>, 's no secret.</div>
<<autoresponse "Which foundry?">>\n\n<div class="npc"><<clue "Hawes" "npc_alexi" "works at hawes standard metal foundry">>. Practically the only one in the city still running.</div>
<<set $interrupt to true>>\n<<set alterConvVar ("mode", x => "chaser reject")>>\n[...]\n\nHe laughs at you.\n\n> Idiot. Beast-chaser after somebody's knot, huh? Pathetic.\n\n[...]\n\n[[=>|map]]
<<nobr>>\n<div class="npc">Yeah, of course. Everybody has.</div>\n\nHe leans forward, gleaming yellow eyes on you.\n\n<div class="npc">You're looking for him, right? What you looking for somebody like that for?</div>\n\n<div class="responses">\\n<<response "I suppose I'm just curious." "tfed factoryworker response curious">>\n<<response "Information pays." "tfed factoryworker response information">>\n<<response "It's not important." "tfed factoryworker response important">>\n</div>\n<<endnobr>>
<<nobr>>\nHe looks over you appraisingly.\n\n<div class="npc">That it does.</div>\n<div class="npc">Lemme tell you something: you find something out, I know some people who'd be interested in paying good money to hear it. Just something to think about.</div>\n/% bumps mood %/\n<<endnobr>>
<<nobr>>\nHe snorts.\n\n<div class="npc">You know what they say about curiosity. Get too close and you're gonna get into things you wish you hadn't.</div>\n\nHe bares his teeth at you, showing off a mouth of craggy fangs.\n\n<div class="npc">Just a friendly warning.</div>\n<<endnobr>>
<<nobr>>\nHis eyes gleam.\n\n<div class="npc">Cocky little fucker, aren't you? Don't think you get to decide what's important.</div>\n<div class="npc">I'll give you a friendly warning: this is none of your business. Get into it and you might not like what happens.</div>\n\n<<if getConvVar ("mode") == "wary">><<set alterConvVar ("mode", x => "suspicious")>><<endif>>\n/% + heat up %/\n<<endnobr>>
<div class="npc">Heard of him? I knew him! And that's how <<clue "he" "bmh" "mikhael" "tfed young factoryworker he">> goes, huh, murdered <<clue "in the night" "bmh" "in the night" "tfed factoryworker in the night">>.</div>
He jolts forward.\n<div class="npc">Of course! That fucking hunter! He wasn't content with picking off small fry anymore; he wanted to land a big catch. It's gonna be his last; everyone will come down hard. There's no escaping, not after this.</div>
<<autoresponse "He?">>\n<div class="npc"><<clue "Mikhael" "bmh" "mikhael">>, the guy he killed. Big beast guy, couldn't miss him.</div>
<<autoresponse "In the night?">>\nHe sneers at you.\n<div class="npc">What, you're trying an //investigation//? It was last night, <<clue "just after second bell" "bmh" "after second bell">>. Later when <<clue "we" "bmh" "factoryworker we">> found the body.</div>\n<<grantTopic "last night, after second bell" "times">>
<div class="npc">If I knew him, he'd be dead.</div>\n/% / <<display "tfed young factoryworker angry ask know">> %/
<div class="npc">Fuck off; it's none of your business who I know. Or //knew//.</div>\n<<if getConvVar ("mode") == "wary">><<set alterConvVar ("mode", x => "suspicious")>><<endif>>\n/% (+ heat up) %/
<div class="npc">He was killed down in the undercity, at <<clue "Where Rats Drowned" "bmh" "rats location">>. <<clue "We've" "bmh" "factoryworker we">> dragged the body away already.</div>
<div class="npc">If I saw him, I'd've killed him.</div>\n/% / <<display "tfed young factoryworker angry ask know">> %/
<div class="npc">//Seen// him? yeah, I've seen him. His body was <<clue "torn up all over" "npc_alexi" "..." "tfed factoryworker torn up">>. Clawed apart by that fucking hunter.</div>
<<autoresponse "Torn up all over?">>\n\n<div class="npc">The hunter got him <<clue "in the back" "bmh" "attacked from the back">>. Shredded him up good.</div>
<div class="npc">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">do i gotta repeat myself? i'd've killed him, if i knew where to get at him</div>\n<<if getConvVar ("mode") == "wary">><<set alterConvVar ("mode", x => "suspicious")>><<endif>>\n/% + heat up %/
<<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 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<<if getConvVar ("fucked")>>/% !! repeated visit, after having sex with him: %/\n<div class="npc">You again.</div>\nHe leers, slinging the barrel over his shoulder down on its pallet with a loud //crash//. He gropes his crotch with his newly-free hand, outlining the bloated mound of his sheath./%otherwise: 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">Back for more, huh?</div>\n\nYou awkwardly clear your throat.\n<<autoresponse "I just have a few more things to ask...">>\n/%<<response "yeah please fuck my throat">>%/\n<<else if !getConvVar("met")>>/% !!first time: %/\n\n<div class="npc">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">You wanna play detective? Sure, ask away.</div>\n\n<<else>>/% !!repeated visit, without fucking him: %/\n<div class="npc">You again. How's your investigation going, huh?</div>\n\n<<autoresponse "As well as can be expected.">>\n<<autoresponse "I just have a few more things to ask...">>\n<<endif>>\n<<set alterConvVar ("met", x => true)>>\n<<endnobr>>
<<set $interrupt to true>>\\n<div class="npc">What, that all the questions you got?</div>\n/%if you've raised heat by >2 pts in this convo: %/\\n<<autoresponse "Yeah, that's all for now.">>\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%/
<<set alterConvVar ("name", x => "Idriss")>>\\n<div class="npc">My name? I'm <<clue "Idriss" "npc_idriss" "idriss" "bestial dockworker name indepth">>.</div>
<<nobr>>\n<<autoresponse "That's your name?">>\n\n<div class="npc">Yeah, I'm sure that's my name. You're taking your detective game serious, huh?</div>\n<<endnobr>>
<div class="npc">Yeah, of course. Happened <<clue "right here" "npc_idriss" "..." "bestial dockworker rats">>.</div>
<<nobr>>\n<<autoresponse "//Right// here?">>\n\n<div class="npc">Well, over at <<clue "Where Rats Drowned" "bmh" "rats location">>, just across the bridge and up the bank.</div>\n<<endnobr>>
<div class="npc">Everybody says they have, right? Seeing figures perched on the lanterns, or glowing eyes up in the cavern wings. Never seen anything like that, myself.</div>=
He snorts.\n\n<div class="npc">What? Of course not. You think anybody who //does// know him is gonna admit to it like that?</div>\n/% + heat up %/
He lets out a laugh.\n\n<div class="npc">What, you think I know?</div>\n/% + heat up %/
<div class="npc">He was killed over at <<clue "Where Rats Drowned" "bmh" "rats location">>, but the <<clue "Kane Street gang" "bmh" "kane st gang has body">> dragged him off before sunup.</div>
<div class="npc">What, after he got himself killed? Nah. Heard he was <<clue "all clawed up" "bmh" "body torn up">>, though.</div>
/% where were you... last night, after second bell %/\\n<div class="npc">Working here. I heard some yelling further upriver, but figured it was just the usual drunkards arguing, until it got real loud. I saw there was a crowd gathering, but I didn't think that much of it until I heard the news from people leaving.</div>\n\n<<autoresponse "Did you see anything unusual?">>\n\n<div class="npc">What, aside from the crowd? <<clue "No, nothing" "bmh" "dockworker saw nothing">>.</div>
<<nobr>>\nHe has a giant barrel slung over his shoulder. He rolls his shoulders, shifting its weight to his forearms, and lets it down on a pallet already stacked with a half-dozen identical barrels.\n\n<div class="npc">I'm a dockworker.</div>\n\nHe says it like it's obvious. It is. But...\n\n<<autoresponse "Paid by individual boatmasters, or by the harbormaster's guild?">>\n\nHe snorts.\n\n<div class="npc"><<clue "Harbormaster" "npc_idriss" "works for harbormaster guild" "bestial dockworker harbormaster">>. Got a charter on all the <<clue "regular shipments" "npc_idriss" "regular shipments" "bestial dockworker regular shipments">>. Don't do <<clue "many" "npc_idriss" "odd jobs" "bestial dockworker odd jobs">> odd jobs.</div>\n<<endnobr>>
<<autoresponse "You got your guild credentials on you?">>\nHe lets out a bark of a laugh.\n<div class="npc"> What, you serious? You think you're some kinda bureaucrat? That's rich.</div>
<<autoresponse "What are the regular shipments you handle?">>\n<div class="npc">You real <<clue "interested" "npc_idriss" "flirt" "bestial dockworker interested">> in the docks?</div>\n<div class="npc">It's been lean since the beast plague got going. Fewer ships in, lots fewer ships out. The undercity wharf handles the big imports -- ore, alchemy, spices -- and a few of the exports, marble mostly.</div>
<<autoresponse "You said you don't do //many// odd jobs. You do some?">>\nHe gives you a cockeyed grin.\n<div class="npc">Most everybody does //<<clue "something" "npc_idriss" "flirt" "bestial dockworker something">>//, down in the undercity. It'd be more of a surprise if I didn't.</div>
<<autoresponse "I'm interested in //something//, that's for sure.">>\nHe gives you a once-over, lips peeling back to make a crooked leer.\n<div class="npc">Detective, you said you were. You sure you're not looking for <<clue "something else" "npc_idriss" "proposition" "bestial dockworker proposition">>?</div>
<<autoresponse "Oh yeah? And what kind of somethings do you do?">>\n\nHe gives you a once-over, lips peeling back to make a crooked leer.\n<div class="npc">Detective, you said you were. You sure you're not looking for <<clue "something else" "npc_idriss" "proposition" "bestial dockworker proposition">>?</div>
<<autoresponse "What else can you give me?">>\n\nHis leer pulls open into a grin.\n<div class="npc">Well if it isn't my lucky day. Yeah, I think I got something for you.</div>\nHe gropes his crotch, looking up the wharf for anybody else.\n<div class="responses">\\n<<response "Yeah, that's what I'm looking for." "bestial dockworker blow behind barrels">>\n<<response "I think there's been a misunderstanding..." "bestial dockworker whoops">>\n</div>
He straightens up.\n<div class="npc">Was there?</div>\n<div class="npc">Well, you know where to find me if you change your mind.</div>
<dialog "How's your little beast hunt going?">>\n\nfound him\nhaven't found him
<<set $interrupt to true; reshow("asktable")>>\\n<div class="npc">I got some time before my shift's up; you can blow me if you want.</div>\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<div class="npc">Behind those barrels.</div>\n<<set alterConvVar ("fucked", x => true)>>\\n\n[[=>|bestial dockworker barrels bj full scene]]
He's close behind you as you walk towards the barrels. The heat of his body radiates against your back. You reach the pallet and run your hand over the oaken barrels, and he presses against your back, hands on your hips, sliding up your chest, and he presses a hungry kiss against the side of your neck. You turn around, and he slides even closer, knee pressing between your legs, mouth scraping up the line of your jaw to nip at your lips; you can feel, for a moment, the uneven line of his craggy fangs.\n\n<div class="npc">Get on your knees.</div>\n\nHe says it against your teeth, breath puffing across your face. You drop to your knees. Yes, at this height you're entirely hidden from the rest of the pier. His hands drop to his waist, tugging at the tied cloth belt, and when he undoes it his pants drop, pooling at his ankles, leaving him entirely naked.\n\nHis chest is thickly muscled, broad and hairy, but his 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 and milks out a cloudy droplet of pre from his sloped cocktip, smearing it across his gleaming cockhead with one clawed finger.\n\nYou lean in, face nestled against his thigh, and lap across his hairy knuckles, tongue catching a sliver of his raw cockflesh. He tastes bestial, animal: rank and meaty, a little acrid. He lets out a chuffing breath, a snarl catching in his throat, and you look up at him as you lap across his cocktip, lips pursing around the sharp tip.\n\nMuscles across his stomach clench and ripple under his coarse hair, and his cock flexes in its sheath, slowly spilling out into the open. You lap at the tip as it pushes out, eyes focused down at the increasing length. It's obscene, a grotesque beast's shaft hanging off his body. The sound of it resounds off the walls: gurgling and slurping, sloppy and wet as it spills out into the open, mottled flesh coated in his own rank issue. It twitches, the smooth, sleek flesh becoming marred by corded veins, each heartbeat sending them racing up his shaft in lightning-bolt patterns before they subside, making his entire shaft pulsate.\n\nHe lets out a breath like a snarl, a hot cloud in the chill air, and leans in, his cock stabbing against the back of your throat, stretching your lips wide. He looms over you, his altered, inhuman cock pulsing, and a burst of hot, rank fluid burbles across your tongue, heavy and greasy, cutting out the wet, musty smell of the undercity and replacing it with the thick, meaty taste of his cock.\n\nHe cups your head with one clawed hand, fingers curling around your chin. His cock visibly throbs, veins fluttering to the surface in time with his heartbeat, and beneath his sheath his balls roil and squirm, the flesh of his sac tensing and releasing, sending his balls lurching across his hairy thighs. You drag your tongue across the underside of his shaft, licking up to the pointed tip and then curling your tongue around the gnarled divot of his cocktube, bobbing on the final few inches of his cock. He groans, rewarding you with another spurt of heavy, rank precome, and you swallow it down. Its slimy texture sticks in your throat, slippery and strange when you swallow again.\n\nFully unsheathed, his cock juts out from his crotch as an enormous spar, easily as long as your forearm from the elbow to your fingertips. His flesh is spongy, still-soft around the rigid core. Suckling on the tip, you wrap your hands around it with a squelch, softly stroking to evenly coat it with a sheen of spit and pre. He croons, cockflesh thickening in your grasp, and you blow the tip of his dick, feeling him firm up in little lurches and ticks until his cock is an iron-hard span of heated flesh, almost no give at all when you squeeze down. Your slippery fingers slurp through the lather you've worked up, coating his shaft in an oily froth that smears across the backs of your hands and oozes down your wrists in thick dollops of cream. He grunts, cock erratically spurting hot gushes of pre in the back of your mouth, each one a sharp burst of rank, acrid flavor. Your lips ache from slurping over the thickness of his shaft, bruised and swollen, sensation prickling across them when you pull back and pant across the tip, letting him drizzle out his pre over your tongue, drooling out of your mouth in slobbery strands between your breathless gulps. Ooze drools down your front, staining your shirt, and he lets out a bark of a laugh.\n\n<div class="npc">Gonna want to take that off; it's gonna get a lot messier.</div>\n\nYou pull back, hands leaving dark smears on your shirt as you haul it over your head and toss it aside. The cool air of the undercity prickles across your skin, raising goosebumps, but then he presses back in close, the wash of heat off his body enveloping you.\n\n<div class="npc">Too bad we can't lay you out here, so I can properly fuck your throat.</div>\n\nHe punctuates it with a slap against the surface of the barrels. It's true, this isn't a good position to try deepthroating anything: his sheath points straight out, tethered to his stomach so it can't hang any lower; and he's got a bone in his dick, so it sure as hell can't bend. You're only sucking on the first few inches and stroking the rest; that's enough to dig into the back of your throat, but after that there's no way you're getting the right angle to sink any deeper.\n\nYou look up at him, cocktip resting in your open mouth, and he groans and aims himself up, cocktip digging into the corner of your mouth, sending his next gush of pre spraying up across your cheeks, over one eye. You squint it half-closed, letting yourself sag forward so you can slurp across a few inches of his cock.\n\nHis knot pops free of his sheath with a wet slurp, splattering thickened internal slime against your forearms. There's a ribbon of slime hanging from the underside of his shaft, slapping against your fingers and smearing back out as you stroke, and now you splatter the entire mess across the bulk of his turgid knot, fingers kneading the bulb of flesh, working his bestial fluids into his skin. He groans, hips lurching forward, fucking a few inches of cock into your mouth, and you groan around it.\n\n<div class="npc">Lemme pop a knot in your hands.</div>\n\nHe snarls, fucking his cock past your bruised lips, and you groan and slide your other hand down, both hands cradling his fattening knot. His shaft darkens as it goes down, purple tinging to a pitch black, all the red leeched out from the bluish lights, until his knot is a perfect glossy black, bashing against your fingers. He grabs your head, starting to properly fuck your face, jabbing his sharp cocktip against the back of your throat again and again, coaxing out wet, slobbery gags.\n\nHe leers down at you, eyes gleaming in the darkness, the muscles across his chest and stomach rippling, hair soaked to his skin in clumps from sweat. His knot throbs between your hands, squelching obscenely as he fucks your fists, and you alternate between cupping down hard and letting it slip out of your grasp, huge knot knocking against your hands before you let it back in. Every time you squeeze down, holding him tight, the whole length of his cock ripples and shudders, and he spits out thicker, heavier bursts of pre into your flooded mouth. With him facefucking you, you're beyond swallowing it all: you just gulp when you can, between breaths, and let the rest burble out over your swollen lips, smearing all down your bare chest. He groans, catching a jiggling strand hanging from your chin and smearing it all up the side of your face, palm squelching against your cheek.\n\nHis hips snap forward, burying more of his cock in your throat -- you gurgle around it, knifetip point jabbing against soft tissue -- and he groans deep and low, sloppy wet noises coming from your fucked-open throat as he picks up the pace: hands bracketing your face, your elbows resting on his thighs as you keep your hold on his swelling knot. Your fingers spread apart, slices of cockflesh bulging through as his knot inflates fully, becoming a burning coal between your fingers. He lets out a guttural roar, maybe loud enough to attract notice, and his cock erupts against your throat, instantly filling your mouth with a wash of hot, rank beast cum. You choke, cum burning up through your sinuses and drooling from your nose, and he lurches forward, thrusts jabbing into your mouth once, twice, before he pulls out too far and slips free, totally drenching your face in huge squirting gushes of thick slime, spraying into /% {your hair} %/your hair and oozing down your face before he can grind a hand against his shaft and slip it back inside your open, drooling mouth.\n\nThe first few pulses are thick, tarry: a dense, opaque slime, murky grey. It sticks to your teeth and skin, catching in blobs down your /% {beard/skin} %/skin, and then -- as he sags back against the barrels above you with a long sigh -- you can feel it change, thinning out, the sharp spurts turning into a continual flow punctuated with even pulses. Watery ooze floods your mouth, coating your throat, washing through the mess of thicker sludge all down your front, making it all pour down to your crotch.\n\nYou're hard too. You've known this for a while but you've been more interested in sucking dick. Still, cumming all down your front, <<if getConvVar ("name")>>Idriss<<else>>he<<endif>> notices and presses his calf against your tented cock, skewed up across your hip.\n\n<div class="npc">You wanna cum too?</div>\n\nHe grinds his leg against your crotch to punctuate it, and you moan around his dick and nod, humping his leg. His knot is still throbbing hard in your hands, in time with the pulses overflowing your mouth, and you close your eyes and grind against him, focusing on the heat of his body; the muscled line of his calf; the rank taste of his cum; the sharp pants of his breath as you keep suckling on his spurting cock. The hard, implacable weight of his knot.\n\nYou cum with a groan, gurgling wetly around his still-spurting cock, and shoot into your pants, humping up against his leg, rolling your hips as you ride it out. His pant leg is soaked in a mixture of yours and his loads, and the wet drag of the fabric is enough stimulation to keep you grinding against him, even after you finish. He's still cumming. It takes a long time for him to stop. His balls lurch, pulled up under his sheath into a gigantic block bigger than both your fists, and you can lean forward and press down with your forearms, feeling the strange internal muscles rhythmically fire, keeping him pumping and pumping and pumping.\n\nBy the time he's actually done, you're a drenched mess: face coated, /%{beard soaked to your skin/jaw and chin a slathered mess}%/jaw and chin a slathered mess, a slimy waterfall spilling down the center of your chest. His knot softens, and he finally pulls free, leaving your fingers aching from being clenched for so long. You gasp for breath, letting excess cum spill from your open mouth, stomach sloshing from the immense amount you managed to swallow.\n\n<div class="npc">Lemme get you cleaned off.</div>\n\nHe leans to the side, pulling a folded insulating tarpaulin off the pallet, and scrubs it down your front. You help, /%{trying to wipe the mess free of your beard/dragging it down your face}%/scrubbing down your face and saturating it with his cum too; you don't know how effective it is at actually cleaning you. By the time you stand up, your skin is at least dry, if crusted and flaky. There's not much you can do about the squelching mess you shot into your pants.\n\nYou wipe your hands off on your pants. <<if getConvVar ("name")>>Idriss'<<else>>His<<endif>> cock is still unsheathed, weakly twitching as he drools out the last of his load onto the stone pier, and slowly, sluggishly, his shrinking knot slurps back inside his sheath, letting it push back over his shaft. He widens his stance a little as his still-turgid cock sinks back inside him.\n\nHe gives you a cockeyed grin and pulls you to your feet, tossing you your shirt as he pulls his pants back up and pulls his cloth belt taut.\n\n<div class="npc">That was a real nice relief. Sure hope your investigation leads you back here.</div>\n\nHe gives you a wink, tying his belt with a snap. His sheath, still-swollen, makes a fat tent, and there's more than a few flecks of cum soaked into his pant legs.\n\n<div class="npc">See you around.</div>\n\n[[=>|map]]
<<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, making his head look lopsided. All you can make from his facial features is that he's unshaven: a shaggy beard erupts out beneath the sack, joining 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.\n\nBefore the plague, the watchmen were conscripted from the local homeowners by the local constable for sessions of two weeks, but with no officers left to appoint a constabulary, much less any constables left to select men from the wards, there are only haggard collections of volunteer militiamen, haphazardly walking their rounds.\n\nHe looks at you, blocking his route, and hoists his lamp higher:\n<div class="npc">Well? What're you up to?</div>\n<<autoresponse "I'm looking into the latest killing of the beast-masked hunter.">>\n<div class="npc">Oh, //are// you now? That's dangerous work to get yourself involved in, you know.</div>\n<<autoresponse "If I could just ask you a few questions...">>
/% what... is your name %/\\n<<set alterConvVar ("name", x => "Finn Suggins")>>\\n<div class="npc">My name? <<clue "Finn Suggins" "npc_suggins" "finn suggins">>, at your service.</div>\n\nHe extends the hand that's not holding the lamp. He's growing thick yellowed claws. You shake hands.
/% what... is your occupation %/\\n<div class="npc"><<clue "Now" "npc_suggins" "..." "transformed watchman days">>? I'm on <<clue "watch" "npc_suggins" "..." "transformed watchman watching for">>!</div>
<<autoresponse "Do you have some other job during the day?">>\n\n<div class="npc">Yes, yeah, of course; walking the rounds doesn't exactly pay.</div>\n\nHe shifts his weight, bringing his lamp closer to your face, close enough the flame burns hot.\n\n<div class="npc">I don't think that's any of your business, though.</div>
/% have you heard of... the beast-masked hunter %/\\n<div class="npc">Yeah, who hasn't. Why are you asking?</div>\n<<autoresponse "I'm looking for him.">>\n<div class="npc">Oh, are you now. Only for good purposes, I'm sure.</div>\n<div class="npc">He killed <<clue "Mikhael the Crusher" "bmh" "mikhael" "transformed watchman mikhael">> last night, and <<clue "good riddance" "npc_suggins" "critical of kane st kings" "transformed watchman critical">>. Without him, the Kane St. Kings are gonna have a harder time shaking everybody down for protection.</div>
/% have you heard of... the beast-masked hunter's latest victim %/\\n<div class="npc"><<clue "Mikhael the Crusher" "bmh" "mikhael" "transformed watchman mikhael">>? Certainly. He was an enforcer for the local gang, the Kane St. Kings. Without him, the Kane St. Kings are gonna have a harder time shaking everybody down for protection.</div>
/% have you heard of... the latest killing by the beast-masked hunter %/\\n<div class="npc">Grisly business, that. The gangs will be at each others throats now; more than they were before. Would you believe it; I <<clue "heard it happen" "npc_suggins" "..." "transformed watchman last night second bell">>!</div>
/% where is... the beast-masked hunter's latest victim %/\n<div class="npc">He died over at <<clue "Where Rats Drown" "bmh" "rats location">>; the Kane St. Kings dragged his corpse back to their hideout this morning. They say <<clue "you can gain a beast's power if you eat its flesh" "bmh" "beast cannibalism">>; I'm sure they're having a feast as we speak.</div>
<<autoresponse "You seem pretty critical of them">>\n<div class="npc">Of course! Who wouldn't be? They're all just thugs with delusions of grandeur. The city went to hell after the Royals took city hall, and all the gangs are jockeying for which neighborhoods they can run as fiefdoms. It's a disgrace.</div>
<div class="npc">//Know// him? No, I've never had any dealings with him, nor heard of anyone who's ever met with him. I suppose I <<clue "heard him" "bmh" "watchman heard altercation">>, <<clue "last night" "bmh" "in the night" "transformed watchman where last night">>.</div>\n/% + heat up? %/
<<autoresponse "Where were you, when you heard this?">>\n<div class="npc">At that point in my rounds, I was just approaching <<clue "The Dark Fountain" "bmh" "altercation near dark fountain">></div>
<div class="npc">Seen him? No. Seems like no one has, just shadows and silhouettes. I suppose I <<clue "heard him" "bmh" "watchman heard altercation" "transformed watchman mikhael chase">>, <<clue "last night" "bmh" "in the night">>.</div>
<div class="npc">Oh, I'd certainly had my dealings with the Kane St. Kings, but never anything in-person. Aside from seeing <<clue "Mikhael's" "bmh" "mikhael" "transformed watchman mikhael">> corpse, that is.</div>
<<autoresponse "Mikhael?">>\n<div class="npc">Yes, Mikhael the Crusher. The main enforcer of the Kane St. Kings. A brutal murderer, who's now found a fitting end.</div>\nHe lets out a wracking laugh.\n<div class="npc"> A proper beast can kill an army, but, well, the Kane St. Kings are without their beast now.</div>
<div class="npc">I was at <<clue "The Dark Fountain" "bmh" "altercation near dark fountain">>, walking the watch. I must have just missed them; I heard <<clue "running and yelling" "bmh" "watchman heard altercation" "transformed watchman mikhael chase">> and then howling and fighting. It only took moments to arrive <<clue "on the scene" "npc_suggins" "..." "transformed watchman scene">>, but it was already over. Mikhael was killed in an instant. That kind of noise draws a crowd fast, but even so, no one could say they saw anything. Everyone's saying it was the beast-masked hunter, but if so, it's the <<clue "first time the hunter has killed like this" "bmh" "unusual behavior with body" "transformed watchman killed unusual">>: attacked quick, and left the body where it lay.</div>
<<autoresponse "Running and yelling?">>\n<div class="npc">Roaring, the sounds of a fight; the usual. I thought it could've just been drunkards, or an assault -- a normal assault, I mean -- until I saw <<clue "the body" "npc_suggins" "..." "transformed watchman the body">>.</div>
<<autoresponse "On the scene?">>\n<div class="npc">Yes, the corpse was left sprawled at <<clue "Where Rats Drowned" "bmh" "rats location">>, just over from here.</div>
<<autoresponse "Killed in what way?">>\n<div class="npc">Fast and sloppy. If nothing else, all the beast-masked hunter's killings have been methodical, and he's stolen away with the bodies until he can string them up. Here, though-- there's something else going on. And that's not even getting into the condition of <<clue "the body" "npc_suggins" "..." "transformed watchman the body">>.</div>
<<autoresponse "The body?">>\n<div class="npc">Oh, it was <<clue "all torn up" "bmh" "body torn up">>. Huge rents all down his back and sides -- must be those star-metal claws everybody's said they've seen.</div>
<<nobr>>\n<<autoresponse "What are you on watch for?">>\n\n<div class="npc">For <<clue "beasts" "npc_suggins" "..." "transformed watchman beasts">>! We're not here looking for every loiterer and <<clue "couple fucking in the streets" "npc_suggins" "..." "transformed watchman fucking">>; we're here to defend against the so-called 'royals' and their hangers-on, and any gangs thinking they can follow their lead and try to <<clue "snatch people off the street" "npc_suggins" "..." "transformed watchman snatch">></div>\n<<endnobr>>
<<autoresponse "Beasts?">>\n\n<div class="npc">The plague is getting to people. When they go feral-- it's dangerous. But more often than that, those that turn into proper beasts go mad. The urges of the beast are basal; it's human desires for power that do the worst. Somebody's gotta keep an eye out.</div>
<<autoresponse "Snatching people off the streets?">>\n<div class="npc">The Royals snatch humans for their fucking vivisections, and some of the other gangs have started to copy them. Whoever can tell the Royals how to turn a human into a beast-- they'll be rich for the rest of their days. Rich in the forsaken world of beasts they're trying to create. But to find that out, they need a steady supply of human flesh.</div>\nHe lets out a bark of a laugh.\n<div class="npc">We're a little insulated from that here; all the gangs here are more mercantile-minded, wanting to control the flow of goods through the wharf and market. But there have still been abductions.</div>
<<nobr>>\n<<autoresponse "You catch people fucking in the streets?">>\n\n<div class="npc">Plenty of people can't handle their beast blood.</div>\n\nHis voice rasps, hoarse and low through his burlap-sack hood. his breath steams through the fabric.\n\n<div class="npc">They get <<clue "urges" "npc_suggins" "..." "transformed watchman urges">>. Not my place to moralize to them about restraint.</div>\n\nHe's quite transformed himself; his body more animal than human: coat not fitting across his barrel chest, broad haunches too wide for his trousers, pulling the fabric awkwardly. His sheath is a big lump beneath his belt, ill-fitting trousers bunched and twisted all across his lower body.\n<<endnobr>>
<<nobr>>\n<<set $interrupt to true>>\n<<autoresponse "You got some urges of your own you want to handle?">>\n\nHe lets out a coarse laugh.\n\n<div class="npc">Sounds like you're the one who wants to indulge your urges.</div>\n\nHe looks aside: the alcoves in the pass are deep and dark.\n\n<div class="npc">How about you step aside and see what we can sate together?</div>\n\n[[step into the dark alcove =>|night watchman knotting alcove]]\n<<endnobr>>
You step with him into the darkness.\n\nHe shutters his lantern and puts it aside, only a thin light between a few uneven slats belying its presence at all.\n\nHe steps close. At first, in the dark, it's all touch: his body is warped and misshapen, much closer to a beast walking on its hind legs than that of a man. His belly is sunken under his giant ribs, bulky pelvis protruding from his well-muscled hips, and his hands wrap around your sides more like claws.\n\nThe mound of his sheath is unmistakable: hot through the fabric, big and bulky tethered to his underbelly. It shifts under your fingers, through his pants, and he lets out a hiss as he unsheaths, raw cock scraping against the coarse fabric. Dampness clings to your hand, seeping through, and he lurches forward, grinding against you: his cock jostles, a hard spar pressing against your front, lurching back and forth. He clashes against your own cock, hardening, and bears down harder, letting your trapped erections slide against each other, a firm line of contact running all down your shaft. He lets out a guttural groan, and his cock shudders, a wet splotch seeping through the fabric, adding a drag to your rutting thrusts.\n\nHe tugs at his belt, the worn leather parting easily, and his pants sag down his hips, ill-fitting on his altered posture. His cock lurches out, just a gleam in the darkness, and smacks wetly against your front. It's slick, wet with the slimy fluid of his sheath, and he ruts forward, drizzling rank slime over your shirt, and you tug it to the side, letting him grind up across your stomach, pre smearing down your skin.\n\nYou tug open the laces of your pants before the mess gets any worse. Your cock skews out, grinding against his slimy length, and he curls them both together in one huge claw: your cockhead slurping against the underside of his bestial shaft, getting entirely drenched in his rank pre. The heady scent wafts up across your face, so intense it feels like it's boring into your skull.\n\nHe groans, surging forward, and pins you against the wall: claws on your shoulders, leg pushing in between your thighs, other hand curling around your knee and raising one leg so he can hump across the full length of your thighs and belly, pinning your cock to your stomach. The coarse fur of his sheath grinds against your cock, sodden and dripping, and the immense, lopsided mass of his balls swing forward to punctuate each thrust, crashing against your thighs with a lewd //crack//. You curl your hands around his cock: it's a gnarled, bestial club, pulsing with an inhuman vigor, flesh pulpy and soft, slick and frictionless, in a way no human dick has ever been. He croons, breath billowing across your neck, the indistinct imprint of teeth felt through the burlap sack, and fucks your hands, cock loudly slopping through your fists.\n\nHe hauls your raised leg across his thigh so that you're half-balanced on his lap, and pushes his hand against the crotch of your pants, shoving them down to mid-thigh. His cock smears against the fabric, spurting an abrupt gush of pre that sprays all the way up your chest, and then his cock lurches down to saw against the back of your thigh, butting against the cheek of your ass.\n\nYou groan, grinding down against his fat length.<<nobr>>\n\n<div class="npc">Yeah, c'mon, fuck me.</div>\n\n<<endnobr>>He snarls in your ear, breath rank, and angles his cock up, pointed tip stabbing against your thigh, hip, inner thigh, before you reach back, arch your back just-so, and guide his stiff cock between your cheeks, jabbing into your hole with a lurch. He burbles pre, spilling in a liquid line down the /%{hairy}%/hairy curve of your ass, slopping his tip against your spreading hole until it's glazed and dripping, wetly yielding. It still burns when he shoves inside, bestial cock fat and rigid, and you let out a guttural groan, moving with him for a moment as he fucks his cocktip into your hole, basting your ass in gushes of pre, excess burbling out down his shaft. You both groan when he sinks in deeper, cock carving you open, a huge rigid pipe impaling you deeper. You whine, clenching and flexing internal muscles to straighten your guts, and it's with a shock that has you going bug-eyed that he draws back and shoves deeper, fucking half his cock into your ass. He hauls you onto his lap, his knees bent, your back pinned against a pillar, and snarls in your face, your own cock pinned against the furl of your pants, leaking into your underwear. You press your hands against his hips, some vague leverage as he fucks you with animal thrusts. His cock throbs inside you, slick and hot, asshole splayed obscenely around his bulky shaft. It's fattest in the middle, slimming down before his knot, and he rolls your asshole back and forth over the widest bit, leaving your hole bruised and aching, spread out into a sloppy gape.\n\nHe sinks deeper, cocktip probing into your guts, and you let out a shaky exhale, straightening the line of your back to align your guts, neatly opening yourself up for his cock. He stabs yet-deeper still, the hot pulse of his spurting cockhead up past your bellybutton, listing to the side as he slides into your guts. He sinks an iota deeper, and his knot crashes against your ass with a //crack//, leaving you gasping. His hands drop, digging into the meat of your ass, spreading your cheeks wide to let him grind his knot right against your gape, and he bends his knees, bouncing you on his knot. Slimy webs of pre splatter between you, excess pre burbling out around his knot and dragging back into huge drooling strands, snapping when he shoves forward again, splattering down against your calves. Again and again, each time your bloated ass spreading fractionally more, the contact wetter and wetter. He snarls against your throat, coarse beard -- or fur -- scraping against your chest.\n\nHis knot slots into place with a full-body wrench. He doesn't fit it all the way in, not yet, but it's wedged in place, a continual burning stretch inside you, flesh sliding against flesh. His claws slice into your hips as he grinds you down, bodily dragging you down onto his bulging knot, once, twice, a third time before your abused flesh finally parts, asshole blossoming out into a fat bulging crater and sucking in the meat of his knot, letting it socket inside you like a hot coal. He buries his head in your throat, restraining his howl -- a strangled choking noise rumbling in his throat -- as he bucks inside you, knot bashing against your prostate, delving deeper into your guts with a hollow slurp. His sheath scrapes against your ass, rubbery flesh grinding against your hole and wrinkling up into hairy folds, letting him press just a fraction deeper.\n\nHis knot throbs, hard and getting harder, entire shaft shuddering and swelling. His cock erupts, so deep inside now you only feel it as a pressure that sloshes and pours, heat blooming out from the hot poker of his shaft. He finally finds his voice, broken rasp turning into a guttural keening as he dumps his bestial load into you. His initial lurches of his shaft inside you settle down into a continual pulse: muscles under his sheath clench steadily, each one adding a fresh burst of heat inside you. He slumps forward against your chest and breathes heavily. Then, after a moment, he twists around, knocking you off his lap and onto your wobbly legs -- you collapse down onto your knees in a sprawled mess, a sharp moan ripping out of your throat as his knot lurches inside you -- and then he twists around, body looking much more natural on all fours, and drags you a half-step backwards, your ass stuck between his haunches. Tied ass-to-ass. His knot is pinned against your prostate, throbbing hard with each pulse, and the steady pressure makes your cock sluggishly pulse and drool. Your pants are bunched around mid-thigh, cock jutting out over them, and without him humping into you you can actually usefully wrap your hand around your dick. His knot is //big//, a hot heavy weight stretching you out, and you find yourself rutting back against him, his knot lurching inside you, as you jerk yourself off. He's just panting on all fours, knot popped inside you, load gurgling into your guts, and you rock back against him, both sprawled on the floor, mating like animals.\n\nYou cum with a groan, stone cool on your forehead when you collapse, and your cock shudders and spits out a stringy mess of cum all over your pants. Without the edge of arousal to cut it, the sheer size of his knot is harder to handle: you whimper and squirm, asshole wetly sucking on the underside of his knot, still way too huge to work its way out any time soon.\n\nYour guts are a sloppy, flooded mess by the time he starts to soften. Thin grey cum burbles out around his turgid knot, abrupt squirts gushing out when you shift position. His hips jerk above you, stirring his cock around inside you before pulling back, and his still-engorged knot bulges out your ass into an enormous mound before he finally pulls free with a sloppy //squelch//, splattering cum everywhere, excess immediately pouring from your well-fucked ass. It still feels like he's inside you; your inner walls clench against nothing, bearing down on where his knot was lodged. The mess of cum drooling from your guts stings as it winds its way over your aching flesh, pooling against the bruised lip of your swollen asshole.\n\nHis cock, still unsheathed, knocks against your thighs as he shifts behind you, one clawed hand catching your elbow and helping you get to your feet.\n<div class="npc">That do you good?</div>\n<<autoresponse "Yeah.">>\nCum burbles from your ass, audibly gurgling as you stand up. You're definitely gonna be walking wobbly-legged for a while. You're leaving a puddle beneath you.\n\nHe tries to straighten his coat; pull up his pants. You get the first actually glimpse of his cock when he shifts out into the light: a colossal cudgel, blood red, stained in a slimy layer of drooling grey cum. It bobs out before him, most of the size of your forearm, with his shrinking, sunken knot nearly as big as your fist, digging into the matted fur of his sheath. Seeing it sends a throb of heat through your ass: all that was inside you.\n\n[[=>|map]]
<<display "intro blurb">>\nA lady of the night, working under a pale blue streetlamp.\n\nShe's dressed starkly, in a close-fitting black overcoat made from thick fabric, and a long dress that comes down to her ankles. They are both embroidered in alabaster thread with looping images of flowers across the hems, looping up along the seams. Her fingernails are painted alabaster, to match her clothes. Her face shows no sign of the beast plague at all. She keeps an eye on the market crowd, tabulating some internal calculation.\n\nShe observes you watching her, and tilts her head towards you in acknowledgment.\n\n<<if getConvVar ("met")>>\\n<div class="npc">You again. If you want to speak more, I hope you have more coin.</div>\n\n<<autoresponse "Really?">>\n<div class="npc">I'm a working woman.</div>\nYou flip her a silver.\n<<autoresponse "I just have a few more questions...">>\n<<else>>\\n<div class="npc">Well, aren't you a handsome one.</div>\nShe leans in closer.\n<div class="npc">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>\n<<set alterConvVar ("met", x => true)>>\n<<endif>>
She rolls her eyes with a sarcastic flourish.\n<div class="npc">It's always the handsome ones.</div>\n<div class="npc">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">Well, I'll see what I can do.</div>
<div class="npc">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">Well, I'll see what I can do.</div>
/% what... is your name %/\\n<div class="npc">You can call me <<clue "Odessa" "npc_odessa" "odessa">></div>
/% what... is your occupation %/\\n<div class="npc">Oh, and here I thought you were willing to be coy. You're //certainly// not interested in my product.</div>
/% have you heard of... the beast-masked hunter %/\\n<div class="npc">Oh yes, the fearsome hunter who strikes in the dark and leaves no witnesses. Everyone's heard of them.</div>
/% have you heard of... the beast-masked hunter's latest victim %/\\n<div class="npc"><<clue "The beast enforcer" "npc_odessa" "..." "lady beast enforcer">>? Yes, it'll certainly <<clue "shake things up" "npc_odessa" "..." "lady shake things up">> in the underground.</div>
<<autoresponse "Beast enforcer?">>\n\n<div class="npc">It was <<clue "'the Crusher'" "bmh" "mikhael">>, the enforcer of the Kings of Kane St., who got himself killed. He was named <<clue "Mikhael">>, I think.</div>
<<autoresponse "Shake things up?">>\n\n<div class="npc">The Kings of Kane St. have been vying with <<clue "The Dark" "npc_odessa" "the dark" "lady the dark">> over control of the undermarket. It's all been behind the scenes, of course -- they don't want to spook the merchants by fighting in the streets. But this is a big blow for them. The Crusher was most of their threat, and without that...</div>
<<autoresponse "What is The Dark?">>\n\n<div class="npc">Oh, haven't you heard? The witch-cult, rumored to be situated somewhere in the undercity depths. Like everybody else in this benighted era, they've been flexing their strength and looking to expand. The Royals have been very explicit about it: anybody who can control a slice of the city is welcome to have it, so long as it doesn't interfere with their plans, and there's only so long the guilds can maintain control of the markets.</div>
/% do you know... any... suspicious people %/\\n<div class="npc">I think you'll understand that my work requires a bit of circumspection. I'm sure you wouldn't appreciate it if I told the Kings of Kane St. I saw somebody asking nosy questions.</div>
/% have you seen... the beast-masked hunter %/\\n<div class="npc">Who has? certainly I've seen many people; any one of them could be the beast-masked hunter out of their disguise.</div>
/% have you seen... the beast-masked hunter's latest victim %/\\n<<set alterConvVar ("name", x => "Odessa")>>\\n<div class="npc"><<clue "The Crusher" "npc_odessa" "..." "lady night the crusher">>? he came around through the market every so often, but I don't think it was a regular occurrence.</div>
/% have you seen... any... suspicious people %/\\n<div class="npc">Oh, always.</div>\n\nShe pointedly does not follow up on that.
/% have you heard of... the latest killing by the beast-masked hunter %/\n<div class="npc">Oh yes, just dreadful news. Bad for business too; the market is quieter today.</div>
/% where were you... last night, after second bell %/\\n<div class="npc">This same corner. If I was looking down at the river I would've seen it happen, you think? I heard <<clue "fighting from the riverside" "bmh" "lady heard fight" "lady night fighting from the riverside">>, but <<clue "I never saw anything directly" "bmh" "lady saw nothing" "lady night never saw anything">>. I thought it was just some drunken brawl or a mugging, until the crowd started forming.</div>
<<autoresponse "The Crusher?">>\n\n<div class="npc">Oh, yes, <<clue "Mikhael the Crusher" "bmh" "mikhael">>, that's who got killed.</div>
<<autoresponse "Could you hear anything?">>\n\n<div class="npc">It didn't sound like they were talking. Just bangs and crashes, not unusual for some drunks, brawling or not. Then roaring. All very close, <<clue "just down by the riverside" "bmh" "rats location">>.</div>
<<autoresponse "So nobody passed by on this street before then?">>\n<div class="npc">They didn't run past me, if that's what you're asking.</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 case "times":\n which = vartable["times"];\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, npcs[$_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")>>
window.getConvVar = function (varname) {\n let vars = vartable();\n let conv = vars["_conv"];\n return vars["npcVars"][conv][varname];\n}\nwindow.alterConvVar = function (varname, f) {\n let vars = vartable();\n let conv = vars["_conv"];\n let res = f (vars["npcVars"][conv][varname]);\n vars["npcVars"][conv][varname] = res;\n return res;\n}\nwindow.vartable = function () {\n return state.history[0].variables;\n}
You consider.\n\n<<print $_compareResult.result>>\n\nnew clue: <<print showClues ($_compareResult.clue, $_investigating)>>\n\n<<if $_compareResult.spec>><<print $_compareResult.spec>><<endif>>\n\n<<print "[[=>|" + $_back + "]]">>
.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; }\np#exit_p { margin: 0.2em 0.2em 0; font-weight: bold; text-align: center; }\np#exit_p:first-child { margin-top: 0em; }\nul#exits { display: flex; flex-wrap: wrap; margin: 0; padding: 0; text-align: center; }\nul#exits li { flex-basis: 33.33%; margin: 0; padding: 0; list-style: none;}\nul#exits li.used { background: #111; border-radius: 4px; }\nul#exits li.used a { display: block; }\n\n.barredExit { text-decoration: line-through; }\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: 1em 0;\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:root {\n --dialog-margin: 40%;\n}\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.askd, .askcur { display: inline-block; }\n\n.autoresponse, .responses { padding: 0.25em; border-radius: 4px; margin: 1em var(--dialog-margin) 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, #passages a.clue { font-weight: bold; color: #fb8; }\n#passages a.clue:hover { color: #fdc; text-shadow: 0px 0px 6px #982; text-decoration: none; }\n\n#consider { color: #000; background: #fb8; display: inline-block; padding: 0.2em 0.4em; border: none; border-radius: 8px; font-size: 120%; transition: 0.5s; cursor: pointer; }\n#consider:hover {background: #fdc; box-shadow: 0 0 16px 2px #787044; }\n\n.npc { border-radius: 4px; border: 1px solid #eee576; padding: 0.25em; margin: 1em 0 1em var(--dialog-margin); 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: 0 10px 1em; 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\nbutton { font-family: "Libre Baskerville", serif; }\n\na.internalLink { color: #fe8; transition: 0.5s; }\na.internalLink:hover { color: #ffc; text-shadow: 0px 0px 6px #982; text-decoration: none; }\n\n#notes {\n display: block;\n position: fixed;\n bottom: 0;\n height: 3em;\n line-height: 3em;\n text-align: center;\n background: #fe8;\n background: linear-gradient(to top, #fe8, transparent);\n}\n#notes a { color: #000; transition: 0.5s; }\n#notes a:hover { color: #402; }\n\n#notes { right: 3em; width: 15em; }\n\n.meta a.internalLink { color: #4d6ad8; transition: none; }\n.meta a.internalLink:hover { color: #8ea6ff; text-shadow: 0 0 transparent; text-decoration: underline; }\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; padding-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; }\n\n.meta { color: #ddd; font-family: monospace; }\ndiv.meta { margin: 1em 0 1em 1em; padding-left: 0.5em; border-left: 4px solid #aaa; }
window.getVar = function (name) {\n return state.history[0].variables[name];\n}
window.showClues = function (clues, investigating) {\n if (!clues) { /* could be null/undefined */\n return;\n }\n console.log (clues);\n return (Array.from (clues)\n .map (c => "<<clue \"" + c + "\" " + investigating + " \"" + c + "\">>")\n .join(String.fromCharCode(0x20)));\n}
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\n .filter(field => field.content.length > 0)\n .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(/ /g, "_"));\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 cleanValue = value\n .replace(/ /g, "_")\n .replace(/'|"|\?/g, "");\n var toggleInput = insertElement (c, "input", "toggle_" + group + "_" + cleanValue, "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 + "_" + cleanValue;\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 /* in the case of multiple <<response>> macros in a row, only the first will actually correctly unset the interrupt, since the later responses will run after the first one will. ofc the 'interrupt' was stored in the first place since sometimes you really don't want to unset it after the response, so this will cause a regression in that case. */\n let interrupt = false; //state.history[0].variables["interrupt"];\n state.history[0].variables["interrupt"] = true;\n\n let runClick = e => {\n state.history[0].variables["interrupt"] = interrupt;\n reshow("asktable");\n\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 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");\n new Wikifier (respA, linkText); /*this runs twee formatting in the text */\n\n /* having an active (unclicked) autoresponse should hide any conversation ui... */\n let interrupt = state.history[0].variables["interrupt"];\n state.history[0].variables["interrupt"] = true;\n reshow("asktable");\n\n let runClick = e => {\n /* ...and clicking it should make the response ui visible again (if it should've been visible before) */\n state.history[0].variables["interrupt"] = interrupt;\n reshow("asktable");\n\n resp.classList.add("ranResponse");\n resp.removeChild (respA);\n let respT = insertElement (resp, "span", null, "clickedResponse");\n new Wikifier (respT, linkText); /* this _reruns_ the text so don't put any counter increments or w/e in there*/\n\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 var relatedQuest = params[1] !== undefined ? parseArg(params[1]) : null;\n var questFlag = params[2] !== undefined ? parseArg(params[2]) : null;\n var followupPassage = params[3] !== undefined ? parseArg(params[3]) : null;\n if (followupPassage) {\n let clueWrapper = insertElement (place, "span");\n let clueLink = insertElement (clueWrapper, "a", null, "clue internalLink", clueText);\n\n let runClick = e => {\n clueWrapper.removeChild (clueLink);\n insertElement (clueWrapper, "span", null, "clue clickedClue", clueText);\n let place2 = document.querySelector (".blockSpan.ask");\n let c = document.createElement ("div");\n place2.parentNode.insertBefore (c, place2);\n new Wikifier (c, "<<display \"" + followupPassage + "\">>");\n scrollWindowTo (place2);\n };\n clueLink.addEventListener ('click', runClick);\n } else {\n insertElement (place, "span", null, "clue", clueText);\n }\n\n if (relatedQuest && questFlag) {\n if (!state.history[0].variables["_clues"][relatedQuest]) {\n state.history[0].variables["_clues"][relatedQuest] = [];\n }\n if (state.history[0].variables["_clues"][relatedQuest].indexOf (questFlag) === -1) {\n state.history[0].variables["_clues"][relatedQuest].push (questFlag);\n }\n }\n }\n};\n\n\nmacros.clues = {\n handler: function (place, macroName, params, parser) {\n let investigating = params[0] !== undefined ? parseArg(params[0]) : null;\n let clues = state.history[0].variables["_clues"][investigating];\n let qData = clueData[investigating];\n let caseTitle = qData && qData.case ? qData.case : "the current case";\n if (clues === undefined) {\n insertElement (place, "p", null, null, "You have no clues on " + caseTitle + ".");\n return;\n }\n\n let c = insertElement (null, "dl", null, "clueList");\n let quest = qData ? qData.clues : null;\n\n clues.map (clue => {\n let title = quest && quest[clue].title ? quest[clue].title : clue;\n let desc = quest && quest[clue].desc ? quest[clue].desc : "";\n insertElement (c, "dt", null, null, title);\n if (desc !== "") {\n insertElement (c, "dd", null, null, desc);\n }\n });\n place.appendChild (c);\n }\n};\nmacros.investigate =\n { handler: function (place, macroName, params, parser) {\n let investigating = params[0] !== undefined ? parseArg(params[0]) : null;\n let clues = state.history[0].variables["_clues"][investigating];\n let qData = clueData[investigating];\n let caseTitle = qData && qData.case ? qData.case : "the current case";\n if (clues === undefined) {\n insertElement (place, "p", null, null, "You have no clues on " + caseTitle + ".");\n return;\n }\n\n let c = insertElement (null, "dl", null, "clueList");\n let consider = insertElement (null, "button", "consider", null, "compare clues");\n let quest = qData ? qData.clues : null;\n let revmap = {};\n\n clues.map ((clue, i) => {\n let title = quest && quest[clue] && quest[clue].title ? quest[clue].title : clue;\n let desc = quest && quest[clue] && quest[clue].desc ? quest[clue].desc : "";\n let tElem = insertElement (c, "dt", null, null, null);\n let escapedTitle = title; /* title.replace(/"/g, "\\\""); */\n new Wikifier (tElem, "<<toggle $_i \"i" + i + "\" \"" + clue + "\" \"" + escapedTitle + "\">>");\n if (desc !== "") {\n insertElement (c, "dd", null, null, desc);\n }\n });\n let selected2 = [];\n Array.from (c.querySelectorAll (".toggleLabel")).map ((elem, i) => {\n revmap[elem.getAttribute("for")] = clues[i];\n elem.addEventListener ('click', function (e) {\n let target = elem.getAttribute("for");\n if (selected2.indexOf (target) !== -1) {\n /* reclicking something that's already selected; ignore */\n return;\n }\n console.log ("selecting \"" + target + "\"");\n selected2.push (target);\n if (selected2.length > 2) {\n let toUnselect = selected2.shift();\n console.log ("unselecting \"" + toUnselect + "\"");\n let input = c.querySelector("#" + toUnselect);\n input.checked = false;\n }\n })\n });\n consider.addEventListener ('click', function (e) {\n if (selected2.length !== 2) {\n return;\n }\n /* try looking up derived clues based on the two selected items */\n let revs = selected2.map (i => revmap[i]);\n let res = qData.derive.find (e =>\n (e.p1 == revs[0] && e.p2 == revs[1])\n || (e.p1 == revs[1] && e.p2 == revs[0]));\n if (res) {\n state.history[0].variables["_investigating"] = investigating;\n state.history[0].variables["_compareResult"] = res;\n /* do something with `res.result`, whether that's a 'success' transition or a unique 'failure' message */\n if (res.clue || res.spec) { /* transition to the compare result passage, which handles the rest */\n state.display("compare result");\n } else {\n /* otherwise, display inline (as a reshown block?) the result message and do nothing else */\n state.history[0].variables["_compareFailureMsg"] = res.result;\n reshow ("compare");\n }\n } else {\n /* display generic failure message */\n state.history[0].variables["_compareFailureMsg"] = "I can't think of anything those have to do with each other.";\n reshow ("compare");\n }\n });\n place.appendChild (c);\n place.appendChild (consider);\n }\n };\n\n\n\n\n\n\nwindow.clueData =\n { "bmh":\n { "case": "the case of the Beast-masked Hunter"\n , "clues":\n { "in the night":\n { title: "Time of killing"\n , desc: "The murder occurred last night, in the early morning."\n }\n , "after second bell":\n { title: "Specific time of killing"\n , desc: "The murder occurred not long after second bell."\n }\n , "factoryworker we":\n { title: "\'We\'"\n , desc: "The young factoryworker said \'we\' took the victim's body."\n }\n , "kane st gang has body":\n { title: "Kane St. Gang has body"\n , desc: "According to the night watchman, the Kane St. Kings dragged away the body in the early morning."\n }\n , "rats location":\n { title: "Corpse location"\n , desc: "The corpse was found at Where Rats Drowned, only moments after death."\n }\n , "body torn up":\n { title: "Body torn up"\n , desc: "Reportedly, the body was savagely torn apart."\n }\n , "attacked from the back":\n { title: "Attacked from the back"\n , desc: "Reportedly, the body was attacked from behind and deeply-scored across the back. The killing blow was through the back, under the ribs, presumably crushing the heart or other vital organs."\n }\n , "mikhael":\n { title: "Mikhael"\n , desc: "The victim was Mikhael the Crusher, an enforcer for the Kane St. Kings."\n }\n , "dockworker saw nothing":\n { title: "The Bestial Dockworker didn't see anything"\n }\n , "lady heard fight": {}\n , "lady saw nothing":\n { title: "The Lady of the Night didn't see anything"\n }\n , "watchman heard altercation": {}\n , "altercation near dark fountain": {}\n , "altercation didn't start north or west":\n { title: "Altercation didn't start from the north or west"\n , desc: "Witnesses nearby heard the altercation itself, but not anything leading up to it, at least not from those directions."\n }\n , "unusual behavior with body":\n { title: "Unusual treatment of the body"\n , desc: "The pattern of the beast-masked hunter's killings is traditionally a murder in an isolated location, and binding and displaying the corpse somewhere public later. That the corpse was left where it lies implies a different actor or a different motive."\n }\n\n , "inciting incident was nearby": {}\n , "altercation didn't start from north or west":\n { title: "Doesn't sound like the altercation started from the north or west streets"\n }\n , "did mikhael come into contact with the beast-masked hunter immediately prior?":\n { title: "Did Mikhael come across the Beast-masked Hunter?"\n , desc: "Could he have found the Beast-masked Hunter on purpose, or been captured?"\n }\n , "was mikhael trying to get away?":\n { title: "Was Mikhael trying to get away?"\n , desc: "Could he have been trying to reach the Kane St. Kings gang when he was killed?"\n }\n , "beast-masked hunter hideout nearby?":\n { title: "Is the Beast-masked Hunter's hideout nearby?"\n , desc: "Could it be that the Beast-masked Hunter has a base of operations near here, in the undercity?"\n }\n }\n , "derive":\n [ { p1: "factoryworker we"\n , p2: "kane st gang has body"\n , result: "The young factoryworker either is, or would like to appear, associated with the Kane St. Kings."\n /* , spec: "(maybe have this grant the topic 'kane st kings'? or like, have the kane st kings come up grant that topic, and have this be like 'you can now PRESS the transformed factoryworker about the kane st kings', which would denote a new autoresponse for that topic for that npc)" */\n }\n , { p1: "mikhael"\n , p2: "kane st gang has body"\n , result: "Presumably they retrieved is body because he was their worker. That's not too surprising; these days it would be quite unusual for next-of-kin to be an actual family member, rather than an associate to some degree."\n }\n , { p1: "unusual behavior with body"\n , p2: "rats location"\n , result: "The murder was definitely not in an isolated location. Perhaps Mikhael was captured and broke free, or stumbled across the beast-masked hunter, leading to an accidental confrontation."\n , clue: ["did mikhael come into contact with the beast-masked hunter immediately prior?"]\n }\n , { p1: "attacked from the back"\n , p2: "rats location"\n , result: "The reports of the body being clawed apart fit with the way the Beast-masked Hunter has killed before, but him leaving the body is unusual..."\n , clue: ["unusual behavior with body"]\n }\n , { p1: "dockworker saw nothing"\n , p2: "lady saw nothing"\n , result: "From statements, it sounds like the altercation didn't start to the north or west of Where Rats Drowned."\n , clue: ["altercation didn't start from north or west"]\n }\n , { p1: "attacked from the back"\n , p2: "mikhael"\n , result: "Could it be that Mikhael was trying to run to the Kane St. Kings hideout, rather than fight the Beast-masked Hunter? It's impossible to say without seeing what condition the Beast-masked Hunter left the fight in, and Mikhael wasn't known for running from a fight."\n , clue: ["was mikhael trying to get away?"]\n }\n , { p1: "lady heard fight"\n , p2: "watchman heard altercation"\n , result: "From statements, it sounds like the altercation started fairly close to Where Rats Drowned."\n , clue: ["inciting incident was nearby"]\n }\n , { p1: "did mikhael come into contact with the beast-masked hunter immediately prior?"\n , p2: "was mikhael trying to get away?"\n , result: "It's possible that mikhael either was captured and broke free, or discovered the Beast-masked Hunter's hideout, and was killed trying to take that information back to the Kane St. Kings. It's also possible that mikhael simply happened across the beast-masked hunter on patrol, and somehow goaded him into a fight. This is all highly speculative, but it might be worth checking to see if there are any signs of unusual activity in the nearby streets."\n , clue: ["beast-masked hunter hideout nearby?"]\n , spec: "<<set $bmh_investigate to true>>(<<clue \"You can now closely investigate the streets\">>)"\n }\n , { p1: "altercation didn't start from north or west"\n , p2: "beast-masked hunter hideout nearby?"\n , result: "If that suspicion is correct and the beast-masked hunter keeps a hideout in the market, at least that rules out the western and northwestern streets."\n }\n , { p1: "altercation near dark fountain"\n , p2: "inciting incident was nearby"\n , result: "It sounds like the altercation didn't move much once it started, and whatever started it was abrupt. It might be worth checking to see if there's any further clues nearby."\n , spec: "<<set $bmh_investigate to true>>(<<clue \"You can now closely investigate the streets\">>)"\n }\n ]\n }\n , "npc_idriss":\n { "case": "Idriss"\n }\n };\n\nwindow.hasClue = function (quest, clue) {\n let clues = state.history[0].variables["_clues"];\n return clues[quest] ? (clues[quest].indexOf(clue) !== -1) : false;\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}());