sandbox.html 104 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071
  1. <!DOCTYPE html>
  2. <!--[if lte IE 8]><html class="lt-ie9"><![endif]-->
  3. <!--[if gt IE 8]><!--><html><!--<![endif]-->
  4. <head>
  5. <meta charset="utf-8">
  6. <title>Sugarcane</title>
  7. <meta name="viewport" content="width=device-width, initial-scale=1">
  8. <!--
  9. Made in Twee 1.4.3
  10. Built on 08 Dec 2014 at 17:56:03, -0500
  11. Sugarcane is based on:
  12. TiddlyWiki 1.2.39 by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
  13. Published under a BSD open source license
  14. Copyright (c) Osmosoft Limited 2005
  15. Redistribution and use in source and binary forms, with or without modification,
  16. are permitted provided that the following conditions are met:
  17. Redistributions of source code must retain the above copyright notice, this
  18. list of conditions and the following disclaimer.
  19. Redistributions in binary form must reproduce the above copyright notice, this
  20. list of conditions and the following disclaimer in the documentation and/or other
  21. materials provided with the distribution.
  22. Neither the name of the Osmosoft Limited nor the names of its contributors may be
  23. used to endorse or promote products derived from this software without specific
  24. prior written permission.
  25. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  26. EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  27. OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
  28. SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  30. TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  31. BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  32. CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  33. ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  34. DAMAGE.
  35. -->
  36. <script title="engine">
  37. (function(){
  38. /*
  39. **
  40. ** Basic utility functions
  41. **
  42. */
  43. function clone(a) {
  44. var constructor, b, proto;
  45. // Primitive types and functions are flat-out passed by value.
  46. if (!a || typeof a != "object") {
  47. return a;
  48. }
  49. constructor = a.constructor;
  50. if (constructor == Date || constructor == RegExp) {
  51. b = new constructor(a);
  52. }
  53. else if (constructor == Array) {
  54. b = [];
  55. }
  56. else if (a.nodeType && typeof a.cloneNode == "function") {
  57. b = a.cloneNode(true);
  58. }
  59. else {
  60. proto = (typeof Object.getPrototypeOf == "function" ? Object.getPrototypeOf(a) : a.__proto__);
  61. b = proto ? Object.create(proto) : {};
  62. }
  63. // This should work on both arrays and objects equally -
  64. // even copying expando properties foolishly added to arrays.
  65. for (var property in a) {
  66. if (Object.prototype.hasOwnProperty.call(a,property) && !isCyclic(a[property])) {
  67. if (typeof a[property] == "object") {
  68. try {
  69. b[property] = clone(a[property]);
  70. continue;
  71. }
  72. catch(e) {}
  73. }
  74. b[property] = a[property];
  75. }
  76. }
  77. return b;
  78. }
  79. function isCyclic(obj) {
  80. var properties = [];
  81. return (function recurse(obj) {
  82. var key, i, ownProps = [];
  83. if (obj && typeof obj == "object") {
  84. if (properties.indexOf(obj) > -1) {
  85. return true;
  86. }
  87. properties.push(obj);
  88. for (key in obj) {
  89. if (Object.prototype.hasOwnProperty.call(obj,key) && recurse(obj[key])) {
  90. return true;
  91. }
  92. }
  93. }
  94. return false;
  95. }(obj));
  96. }
  97. function insertElement(a, d, f, c, e) {
  98. var b = document.createElement(d);
  99. if (f) {
  100. b.id = f
  101. }
  102. if (c) {
  103. b.className = c
  104. }
  105. if (e) {
  106. insertText(b, e)
  107. }
  108. if (a) {
  109. a.appendChild(b)
  110. }
  111. return b
  112. }
  113. function addClickHandler(el, fn) {
  114. if (el.addEventListener) {
  115. el.addEventListener('click', fn);
  116. } else if (el.attachEvent) {
  117. el.attachEvent('onclick', fn);
  118. }
  119. }
  120. function insertText(a, b) {
  121. return a.appendChild(document.createTextNode(b))
  122. }
  123. function removeChildren(a) {
  124. while (a.hasChildNodes()) {
  125. a.removeChild(a.firstChild)
  126. }
  127. }
  128. function findPassageParent(el) {
  129. while(el && el != document.body && !~el.className.indexOf("passage")) {
  130. el = el.parentNode;
  131. }
  132. return el == document.body ? null : el;
  133. }
  134. function setPageElement(c, b, a) {
  135. var place;
  136. if (place = (typeof c == "string" ? document.getElementById(c) : c)) {
  137. removeChildren(place);
  138. if (tale.has(b)) {
  139. new Wikifier(place, tale.get(b).processText())
  140. } else {
  141. new Wikifier(place, a)
  142. }
  143. }
  144. }
  145. var scrollWindowInterval;
  146. function scrollWindowTo(e, margin) {
  147. var d = window.scrollY ? window.scrollY : document.documentElement.scrollTop,
  148. m = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight,
  149. g = k(e),
  150. j = (d > g) ? -1 : 1,
  151. b = 0,
  152. c = Math.abs(d - g);
  153. scrollWindowInterval && window.clearInterval(scrollWindowInterval);
  154. if (c) {
  155. scrollWindowInterval = window.setInterval(h, 25);
  156. }
  157. function h() {
  158. b += 0.1;
  159. window.scrollTo(0, d + j * (c * Math.easeInOut(b)));
  160. if (b >= 1) {
  161. window.clearInterval(scrollWindowInterval)
  162. }
  163. }
  164. function k(o) {
  165. var p = a(o),
  166. h = o.offsetHeight,
  167. n = d + m;
  168. p = Math.min(Math.max(p + (margin || 0) * ( p < d ? -1 : 1), 0), n);
  169. if (p < d) {
  170. return p
  171. } else {
  172. if (p+h > n) {
  173. if (h < m) {
  174. return (p - (m - h) + 20)
  175. } else {
  176. return p
  177. }
  178. } else {
  179. return p
  180. }
  181. }
  182. }
  183. function a(l) {
  184. var m = 0;
  185. while (l.offsetParent) {
  186. m += l.offsetTop;
  187. l = l.offsetParent
  188. }
  189. return m
  190. }
  191. }
  192. // Returns an object containing the properties in neu which differ from old.
  193. function delta(old,neu) {
  194. var vars, ret = {};
  195. if (old && neu) {
  196. for (vars in neu) {
  197. if (neu[vars] !== old[vars]) {
  198. ret[vars] = neu[vars];
  199. }
  200. }
  201. }
  202. return ret;
  203. }
  204. // Convert various exotic objects into JSON-serialisable plain objects.
  205. function decompile(val) {
  206. var i, ret;
  207. if ((typeof val != "object" && typeof val != "function") || !val) {
  208. return val;
  209. }
  210. // Passage objects: store only their name
  211. else if (val instanceof Passage) {
  212. return { "[[Passage]]" : val.id };
  213. }
  214. else if (Array.isArray(val)) {
  215. ret = [];
  216. }
  217. else {
  218. ret = {};
  219. }
  220. // Deep-copy own properties
  221. for (i in val) {
  222. if (Object.prototype.hasOwnProperty.call(val,i) && !isCyclic(val[i])) {
  223. ret[i] = decompile(val[i]);
  224. }
  225. }
  226. // Functions: store the decompiled function body as a property
  227. // arbitrarily called "[[Call]]".
  228. if (typeof val == "function" || val instanceof RegExp) {
  229. try {
  230. // Check if it can be recompiled (i.e. isn't a bound or native)
  231. internalEval(val+"");
  232. ret["[[Call]]"] = val+"";
  233. } catch(e) {
  234. // Silently fail
  235. ret["[[Call]]"] = "function(){}"
  236. }
  237. }
  238. return ret;
  239. }
  240. // Reverses the effects of decompile()
  241. // Takes recently JSON.parse()d objects and makes them exotic again.
  242. function recompile(val) {
  243. var i, ret = val;
  244. if (val && typeof val == "object") {
  245. // Passages
  246. if (typeof val["[[Passage]]"] == "number") {
  247. return tale.get(val["[[Passage]]"]);
  248. }
  249. // Functions/RegExps
  250. if (typeof val["[[Call]]"] == "string") {
  251. try {
  252. ret = internalEval(val["[[Call]]"]);
  253. }
  254. catch(e){}
  255. }
  256. // Recursively recompile all properties
  257. // or, if a function/regexp, deep-copy them to the new function.
  258. for (i in val) {
  259. if (Object.prototype.hasOwnProperty.call(val,i)) {
  260. ret[i] = recompile(val[i]);
  261. }
  262. }
  263. }
  264. return ret;
  265. }
  266. function addStyle(b) {
  267. if (document.createStyleSheet) {
  268. document.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd", "&nbsp;<style>" + b + "</style>")
  269. } else {
  270. var a = document.createElement("style");
  271. a.appendChild(document.createTextNode(b));
  272. document.getElementsByTagName("head")[0].appendChild(a)
  273. }
  274. }
  275. function alterCSS(text) {
  276. var temp = '', imgPassages = tale.lookup("tags", "Twine.image");
  277. // Remove comments
  278. text = text.replace(/\/\*(?:[^\*]|\*(?!\/))*\*\//g,'');
  279. // Replace :link
  280. text = text.replace(/:link/g,"[class*=Link]");
  281. // Replace :visited
  282. text = text.replace(/:visited/g,".visitedLink");
  283. // Hoist @import
  284. text = text.replace(/@import\s+(?:url\s*\(\s*['"]?|['"])[^"'\s]+(?:['"]?\s*\)|['"])\s*([\w\s\(\)\d\:,\-]*);/g, function(e) {
  285. temp += e; return '';
  286. });
  287. text = temp + text;
  288. // Add images
  289. return text.replace(new RegExp(Wikifier.imageFormatter.lookahead, "gim"), function(m,p1,p2,p3,src) {
  290. for (var i = 0; i < imgPassages.length; i++) {
  291. if (imgPassages[i].title == src) {
  292. src = imgPassages[i].text;
  293. break;
  294. }
  295. }
  296. return "url(" + src + ")"
  297. });
  298. }
  299. function setTransitionCSS(styleText) {
  300. styleText = alterCSS(styleText);
  301. var style = document.getElementById("transitionCSS");
  302. style.styleSheet ? (style.styleSheet.cssText = styleText) : (style.innerHTML = styleText);
  303. }
  304. function throwError(a, b, tooltip) {
  305. if (a) {
  306. var elem = insertElement(a, "span", null, "marked", b);
  307. tooltip && elem.setAttribute("title", tooltip);
  308. } else {
  309. alert("Regrettably, this " + tale.identity() + "'s code just ran into a problem:\n" + b + ".\n"+softErrorMessage);
  310. }
  311. }
  312. Math.easeInOut = function (a) {
  313. return (1 - ((Math.cos(a * Math.PI) + 1) / 2))
  314. };
  315. String.prototype.readMacroParams = function (keepquotes) {
  316. var exec, re = /(?:\s*)(?:(?:"([^"]*)")|(?:'([^']*)')|(?:\[\[((?:[^\]]|\](?!\]))*)\]\])|([^"'\s]\S*))/mg,
  317. params = [];
  318. do {
  319. var val;
  320. exec = re.exec(this);
  321. if (exec) {
  322. if (exec[1]) {
  323. val = exec[1];
  324. keepquotes && (val = '"' + val + '"');
  325. } else if (exec[2]) {
  326. val = exec[2];
  327. keepquotes && (val = "'" + val + "'");
  328. } else if (exec[3]) {
  329. val = exec[3];
  330. keepquotes && (val = '"' + val.replace('"','\\"') + '"');
  331. } else if (exec[4]) {
  332. val = exec[4];
  333. }
  334. val && params.push(val);
  335. }
  336. } while (exec);
  337. return params
  338. };
  339. String.prototype.readBracketedList = function () {
  340. var c, b = "\\[\\[([^\\]]+)\\]\\]",
  341. a = "[^\\s$]+",
  342. e = "(?:" + b + ")|(" + a + ")",
  343. d = new RegExp(e, "mg"),
  344. f = [];
  345. do {
  346. c = d.exec(this);
  347. if (c) {
  348. if (c[1]) {
  349. f.push(c[1])
  350. } else {
  351. if (c[2]) {
  352. f.push(c[2])
  353. }
  354. }
  355. }
  356. } while (c);
  357. return (f)
  358. };
  359. function rot13(s) {
  360. return s.replace(/[a-zA-Z]/g, function(c) {
  361. return String.fromCharCode((c<="Z" ? 90 : 122) >= (c=c.charCodeAt()+13) ? c : c-26);
  362. });
  363. }
  364. /*
  365. **
  366. ** Polyfills
  367. **
  368. */
  369. Object.create || (function() {
  370. var F = function(){};
  371. Object.create = function (o) {
  372. if (typeof o != 'object') throw TypeError();
  373. F.prototype = o;
  374. return new F();
  375. };
  376. })();
  377. String.prototype.trim || (String.prototype.trim = function () {
  378. return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
  379. });
  380. Array.isArray || (Array.isArray = function(arg) {
  381. return Object.prototype.toString.call(arg) === '[object Array]';
  382. });
  383. Array.prototype.indexOf || (Array.prototype.indexOf = function (b, d) {
  384. d = (d == null) ? 0 : d;
  385. var a = this.length;
  386. for (var c = d; c < a; c++) {
  387. if (this[c] == b) {
  388. return c
  389. }
  390. }
  391. return -1
  392. });
  393. Array.prototype.forEach || (Array.prototype.forEach=function(fun){if(this==null){throw TypeError()}var t=Object(this);var len=+t.length;if(typeof fun!="function"){throw TypeError()}var thisArg=arguments.length>=2?arguments[1]:void 0;for(var i=0;i<len;i++){if(i in t){fun.call(thisArg,t[i],i,t)}}});
  394. /* btoa/atob polyfill by github.com/davidchambers */
  395. (function(){function t(t){this.message=t}var e=window,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.prototype=Error(),t.prototype.name="InvalidCharacterError",e.btoa||(e.btoa=function(e){for(var o,n,a=0,i=r,c="";e.charAt(0|a)||(i="=",a%1);c+=i.charAt(63&o>>8-8*(a%1))){if(n=e.charCodeAt(a+=.75),n>255)throw new t();o=o<<8|n}return c}),e.atob||(e.atob=function(e){if(e=e.replace(/=+$/,""),1==e.length%4)throw new t();for(var o,n,a=0,i=0,c="";n=e.charAt(i++);~n&&(o=a%4?64*o+n:n,a++%4)?c+=String.fromCharCode(255&o>>(6&-2*a)):0)n=r.indexOf(n);return c})})();
  396. /* Polyfill for fade transition */
  397. var hasTransition = 'transition' in document.documentElement.style || '-webkit-transition' in document.documentElement.style;
  398. function fade(f, c) {
  399. var h;
  400. var e = f.cloneNode(true);
  401. var g = (c.fade == "in") ? 1 : -1;
  402. f.parentNode.replaceChild(e, f);
  403. if (c.fade == "in") {
  404. h = 0;
  405. e.style.visibility = "visible"
  406. } else {
  407. h = 1
  408. }
  409. b(e, h);
  410. var a = window.setInterval(d, 25);
  411. function d() {
  412. h += 0.05 * g;
  413. b(e, Math.easeInOut(h));
  414. if (((g == 1) && (h >= 1)) || ((g == -1) && (h <= 0))) {
  415. f.style.visibility = (c.fade == "in") ? "visible" : "hidden";
  416. if (e.parentNode) {
  417. e.parentNode.replaceChild(f, e);
  418. }
  419. window.clearInterval(a);
  420. if (c.onComplete) {
  421. c.onComplete.call(f)
  422. }
  423. }
  424. }
  425. function b(k, j) {
  426. var l = Math.floor(j * 100);
  427. k.style.zoom = 1;
  428. k.style.filter = "alpha(opacity=" + l + ")";
  429. k.style.opacity = j
  430. }
  431. }
  432. /*
  433. **
  434. ** History object
  435. **
  436. */
  437. function History() {
  438. this.history = [{
  439. passage: null,
  440. variables: {}
  441. }]
  442. // Unique identifier for this game session
  443. this.id = new Date().getTime()+'';
  444. // URL of the bookmark link
  445. this.hash = '';
  446. }
  447. History.prototype.encodeHistory = function(b, noVars) {
  448. var ret = ".", vars, type, hist = this.history[b],
  449. d = this.history[b+1] ? delta(this.history[b+1].variables, hist.variables) : hist.variables;
  450. function vtob(val) {
  451. try {
  452. return window.btoa(unescape(encodeURIComponent(JSON.stringify(decompile(val)))));
  453. } catch(e) {
  454. return "0";
  455. }
  456. }
  457. if (!hist.passage || hist.passage.id == null) {
  458. return ""
  459. }
  460. ret += hist.passage.id.toString(36);
  461. if (noVars) {
  462. return ret;
  463. }
  464. for (vars in d) {
  465. type = typeof d[vars];
  466. if (type != "undefined") {
  467. ret += "$" + vtob(vars) + "," + vtob(d[vars]);
  468. }
  469. }
  470. for (vars in hist.linkVars) {
  471. type = typeof hist.linkVars[vars];
  472. if (type != "function" && type != "undefined") {
  473. ret += "[" + vtob(vars) + "," + vtob(hist.linkVars[vars]);
  474. }
  475. }
  476. return ret
  477. };
  478. History.decodeHistory = function(str, prev) {
  479. var name, splits, variable, c, d,
  480. ret = { variables: clone(prev.variables) || {} },
  481. match = /([a-z0-9]+)((?:\$[A-Za-z0-9\+\/=]+,[A-Za-z0-9\+\/=]+)*)((?:\[[A-Za-z0-9\+\/=]+,[A-Za-z0-9\+\/=]+)*)/g.exec(str);
  482. function btov(str) {
  483. try {
  484. return recompile(JSON.parse(decodeURIComponent(escape(window.atob(str)))));
  485. } catch(e) {
  486. return 0;
  487. }
  488. }
  489. if (match) {
  490. name = parseInt(match[1], 36);
  491. if (!tale.has(name)) {
  492. return false
  493. }
  494. if (match[2]) {
  495. ret.variables || (ret.variables = {});
  496. splits = match[2].split('$');
  497. for (c = 0; c < splits.length; c++) {
  498. variable = splits[c].split(",");
  499. d = btov(variable[0]);
  500. if (d) {
  501. ret.variables[d]=btov(variable[1]);
  502. }
  503. }
  504. }
  505. if (match[3]) {
  506. ret.linkVars || (ret.linkVars = {});
  507. splits = match[3].split('[');
  508. for (c = 0; c < splits.length; c++) {
  509. variable = splits[c].split(",");
  510. d = btov(variable[0]);
  511. if (d) {
  512. ret.linkVars[d]=btov(variable[1]);
  513. }
  514. }
  515. }
  516. ret.passage = tale.get(name);
  517. return ret;
  518. }
  519. };
  520. History.prototype.save = function() {
  521. var hist, b, a = "";
  522. for (b = this.history.length - 1; b >= 0; b--) {
  523. hist = this.history[b];
  524. if (!hist) {
  525. break;
  526. }
  527. a += this.encodeHistory(b);
  528. }
  529. return "#" + a
  530. };
  531. History.prototype.restore = function () {
  532. var a, b, c, vars;
  533. try {
  534. if (!window.location.hash || (window.location.hash == "#")) {
  535. if (testplay) {
  536. if (tale.has("StoryInit")) {
  537. new Wikifier(insertElement(null, "span"), tale.get("StoryInit").text);
  538. }
  539. this.display(testplay, null, 'quietly');
  540. return true
  541. }
  542. return false
  543. }
  544. if (window.location.hash.substr(0, 2) == '#!') {
  545. c = window.location.hash.substr(2).split('_').join(' ');
  546. this.display(c, null, 'quietly');
  547. return true
  548. }
  549. a = window.location.hash.replace("#", "").split(".");
  550. for (b = 0; b < a.length; b++) {
  551. vars = History.decodeHistory(a[b], vars || {});
  552. if (vars) {
  553. if (b == a.length - 1) {
  554. vars.variables = clone(this.history[0].variables);
  555. for (c in this.history[0].linkVars) {
  556. vars.variables[c] = clone(this.history[0].linkVars[c]);
  557. }
  558. this.history.unshift(vars);
  559. this.display(vars.passage.title, null, "back");
  560. }
  561. else {
  562. this.history.unshift(vars);
  563. }
  564. }
  565. }
  566. return true
  567. } catch (d) {
  568. return false
  569. }
  570. };
  571. History.prototype.saveVariables = function(c, el, callback) {
  572. if (typeof callback == "function") {
  573. callback.call(el);
  574. }
  575. this.history.unshift({
  576. passage: c,
  577. variables: clone(this.history[0].variables)
  578. });
  579. };
  580. var restart = History.prototype.restart = function () {
  581. if (typeof window.history.replaceState == "function") {
  582. (typeof this.pushState == "function") && this.pushState(true, window.location.href.replace(/#.*$/,''));
  583. window.location.reload()
  584. }
  585. else {
  586. window.location.hash = "";
  587. }
  588. };
  589. /*
  590. **
  591. ** Macros
  592. **
  593. */
  594. var version = {
  595. major: 4,
  596. minor: 3,
  597. revision: 0,
  598. date: new Date("2014"),
  599. extensions: {}
  600. };
  601. var testplay, tale, state, prerender = {}, postrender = {}, macros = window.macros = {};
  602. version.extensions.displayMacro = {
  603. major: 2,
  604. minor: 0,
  605. revision: 0
  606. };
  607. macros.display = {
  608. // Used by parameter() and parameterValue()
  609. parameters: [],
  610. handler: function (place, macroName, params, parser) {
  611. var t, j, output, oldDisplayParams, name = parser.fullArgs();
  612. if (macroName != "display") {
  613. output = macroName;
  614. // Shorthand displays can have parameters
  615. params = parser.fullMatch().replace(/^\S*|>>$/g,'').readMacroParams(true);
  616. // The above line recreates params, with the quotes.
  617. try {
  618. for(j=0; j < params.length; j++) {
  619. params[j] = internalEval(Wikifier.parse(params[j]));
  620. }
  621. } catch (e) {
  622. throwError(place, parser.fullMatch() + " bad argument: " + params[j], parser.fullMatch());
  623. return
  624. }
  625. }
  626. else {
  627. try {
  628. output = internalEval(name);
  629. }
  630. catch(e) {
  631. }
  632. if (output == null) {
  633. // Last-ditch attempt
  634. if (tale.has(name)) {
  635. output = name;
  636. }
  637. }
  638. }
  639. if (!output) {
  640. throwError(place, '<<' + macroName + '>>: "' +name + "\" did not evaluate to a passage name", parser.fullMatch());
  641. } else if (!tale.has(output+"")) {
  642. throwError(place, '<<' + macroName + ">>: The \"" + output + "\" passage does not exist", parser.fullMatch());
  643. } else {
  644. oldDisplayParams = this.parameters;
  645. this.parameters = params;
  646. t = tale.get(output+"");
  647. if (t.tags.indexOf("script") > -1) {
  648. scriptEval(t);
  649. }
  650. else {
  651. new Wikifier(place, t.processText());
  652. }
  653. this.parameters = oldDisplayParams;
  654. }
  655. }
  656. };
  657. version.extensions.actionsMacro = {
  658. major: 1,
  659. minor: 2,
  660. revision: 0
  661. };
  662. macros.actions = {
  663. handler: function (a, f, g) {
  664. var v = state.history[0].variables, e = insertElement(a, "ul");
  665. if (!v["actions clicked"]) {
  666. v["actions clicked"] = {}
  667. }
  668. for (var b = 0; b < g.length; b++) {
  669. if (v["actions clicked"][g[b]]) {
  670. continue
  671. }
  672. var d = insertElement(e, "li");
  673. var c = Wikifier.createInternalLink(d, g[b], (function(link) {
  674. return function() { state.history[0].variables["actions clicked"][link] = true; }
  675. }(g[b])));
  676. insertText(c, g[b]);
  677. }
  678. }
  679. };
  680. version.extensions.printMacro = {
  681. major: 1,
  682. minor: 1,
  683. revision: 1
  684. };
  685. macros.print = {
  686. handler: function (place, macroName, params, parser) {
  687. var args = parser.fullArgs(macroName != "print"), output;
  688. try {
  689. // See comment within macros.display
  690. output = internalEval(args);
  691. if (output != null && (typeof output != "number" || !isNaN(output))) {
  692. new Wikifier(place, ''+output);
  693. }
  694. } catch (e) {
  695. throwError(place, "<<print>> bad expression: " + params.join(' '), parser.fullMatch());
  696. }
  697. }
  698. };
  699. version.extensions.setMacro = {
  700. major: 1,
  701. minor: 1,
  702. revision: 0
  703. };
  704. macros.set = {
  705. handler: function (a, b, c, parser) {
  706. macros.set.run(a, parser.fullArgs(), parser, c.join(' '))
  707. },
  708. run: function (a,expression, parser, original) {
  709. try {
  710. return internalEval(expression);
  711. } catch (e) {
  712. throwError(a, "bad expression: " + (original || expression), parser ? parser.fullMatch() : expression)
  713. }
  714. }
  715. };
  716. version.extensions.ifMacros = {
  717. major: 2,
  718. minor: 0,
  719. revision: 0
  720. };
  721. macros["if"] = {
  722. handler: function (place, macroName, params, parser) {
  723. var conditions = [],
  724. clauses = [],
  725. rawConds = [],
  726. srcOffset = parser.source.indexOf(">>", parser.matchStart) + 2,
  727. src = parser.source.slice(srcOffset),
  728. endPos = -1,
  729. rawCond = params.join(' '),
  730. currentCond = parser.fullArgs(),
  731. currentClause = "",
  732. t = 0,
  733. nesting = 0,
  734. i = 0;
  735. for (; i < src.length; i++) {
  736. if ((src.substr(i, 6) == "<<else") && !nesting) {
  737. rawConds.push(rawCond);
  738. conditions.push(currentCond.trim());
  739. clauses.push(currentClause);
  740. currentClause="";
  741. t = src.indexOf(">>",i+6);
  742. if(src.substr(i+6,4)==" if " || src.substr(i+6,3)=="if ") {
  743. rawCond = src.slice(i+9,t);
  744. currentCond = Wikifier.parse(rawCond);
  745. }
  746. else {
  747. rawCond = "";
  748. currentCond = "true";
  749. }
  750. i = t+2;
  751. }
  752. if (src.substr(i, 5) == "<<if ") {
  753. nesting++;
  754. }
  755. if (src.substr(i, 9) == "<<endif>>") {
  756. nesting--;
  757. if (nesting < 0) {
  758. endPos = srcOffset + i + 9;
  759. rawConds.push(rawCond);
  760. conditions.push(currentCond.trim());
  761. clauses.push(currentClause);
  762. break;
  763. }
  764. }
  765. currentClause += src.charAt(i);
  766. }
  767. if (endPos != -1) {
  768. parser.nextMatch = endPos;
  769. try {
  770. for(i=0;i<clauses.length;i++) {
  771. if (internalEval(conditions[i])) {
  772. new Wikifier(place, clauses[i]);
  773. break;
  774. }
  775. }
  776. } catch (e) {
  777. throwError(place, "<<" + (i ? "else " : "") + "if>> bad condition: " + rawConds[i], !i ? parser.fullMatch()
  778. : "<<else if " + rawConds[i] + ">>");
  779. }
  780. } else {
  781. throwError(place, "I can't find a matching <<endif>>", parser.fullMatch());
  782. }
  783. }
  784. };
  785. macros["else"] = macros.elseif = macros.endif = {
  786. handler: function () {}
  787. };
  788. version.extensions.rememberMacro = {
  789. major: 2,
  790. minor: 0,
  791. revision: 0
  792. };
  793. macros.remember = {
  794. handler: function (place, macroName, params, parser) {
  795. var variable, value, re, match,
  796. statement = params.join(" ");
  797. macros.set.run(place, parser.fullArgs(), null, params.join(' '));
  798. if (!window.localStorage) {
  799. throwError(place, "<<remember>> can't be used "
  800. + (window.location.protocol == "file:" ? " by local HTML files " : "") + " in this browser.",parser.fullMatch());
  801. return;
  802. }
  803. re = new RegExp(Wikifier.textPrimitives.variable, "g");
  804. while (match = re.exec(statement)) {
  805. variable = match[1];
  806. value = state.history[0].variables[variable];
  807. try {
  808. value = JSON.stringify(value);
  809. } catch (e) {
  810. throwError(place, "can't <<remember>> the variable $" + variable + " (" + (typeof value) + ")", parser.fullMatch());
  811. return;
  812. }
  813. window.localStorage[this.prefix + variable] = value;
  814. }
  815. },
  816. init: function () {
  817. var i, variable, value;
  818. this.prefix = "Twine." + tale.defaultTitle + ".";
  819. for (i in window.localStorage) {
  820. if (i.indexOf(this.prefix) == 0) {
  821. variable = i.substr(this.prefix.length);
  822. value = window.localStorage[i];
  823. try {
  824. value = JSON.parse(value);
  825. state.history[0].variables[variable]=value;
  826. } catch (e) {
  827. }
  828. }
  829. }
  830. },
  831. expire: null,
  832. prefix: null
  833. };
  834. version.extensions.forgetMacro = {
  835. major: 1,
  836. minor: 0,
  837. revision: 0
  838. };
  839. macros.forget = {
  840. handler: function (place, macroName, params) {
  841. var re, match, variable,
  842. statement = params.join(" ");
  843. re = new RegExp(Wikifier.textPrimitives.variable, "g");
  844. while (match = re.exec(statement)) {
  845. variable = match[1] + ""
  846. delete state.history[0].variables[variable];
  847. delete window.localStorage[macros.remember.prefix + variable];
  848. }
  849. }
  850. };
  851. version.extensions.SilentlyMacro = {
  852. major: 1,
  853. minor: 1,
  854. revision: 0
  855. };
  856. macros.nobr = macros.silently = {
  857. handler: function (place, macroName, f, parser) {
  858. var i, h = insertElement(null, 'div'),
  859. k = parser.source.indexOf('>>', parser.matchStart) + 2,
  860. a = parser.source.slice(k),
  861. d = -1,
  862. c = '',
  863. l = 0;
  864. for (i = 0; i < a.length; i++) {
  865. if (a.substr(i, macroName.length+7) == '<<end' + macroName + '>>') {
  866. if (l == 0) {
  867. d = k + i + macroName.length+7;
  868. break;
  869. } else {
  870. l--;
  871. }
  872. } else if (a.substr(i, macroName.length+4) == '<<' + macroName + '>>') {
  873. l++;
  874. }
  875. if (macroName == "nobr" && a.charAt(i) == '\n') {
  876. c += "\u200c"; // Zero-width space
  877. }
  878. else {
  879. c += a.charAt(i);
  880. }
  881. }
  882. if (d != -1) {
  883. new Wikifier(macroName == "nobr" ? place : h, c);
  884. parser.nextMatch = d;
  885. } else {
  886. throwError(place, "can't find matching <<end" + macroName+">>", parser.fullMatch());
  887. }
  888. }
  889. };
  890. macros.endsilently = {
  891. handler: function () {}
  892. };
  893. version.extensions.choiceMacro = {
  894. major: 2,
  895. minor: 0,
  896. revision: 0
  897. };
  898. macros.choice = {
  899. callback: function() {
  900. var i, other, passage = findPassageParent(this);
  901. if (passage) {
  902. other = passage.querySelectorAll(".choice");
  903. for (i = 0; i < other.length; i++) {
  904. other[i].outerHTML = "<span class=disabled>" + other[i].innerHTML + "</span>";
  905. }
  906. state.history[0].variables["choice clicked"][passage.id.replace(/\|[^\]]*$/,'')] = true;
  907. }
  908. },
  909. handler: function (A, C, D, parser) {
  910. var link, id, match,
  911. text = D[1] || D[0].split("|")[0],
  912. passage = findPassageParent(A);
  913. // Get ID of the "choice clicked" entry
  914. if (!passage) {
  915. throwError(A, "<<"+C+">> can't be used here.",parser.fullMatch());
  916. return;
  917. }
  918. id = (passage && passage.id.replace(/\|[^\]]*$/,''));
  919. if (id && (state.history[0].variables["choice clicked"] ||
  920. (state.history[0].variables["choice clicked"] = {}))[id]) {
  921. insertElement(A, "span", null, "disabled", text);
  922. }
  923. else {
  924. match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
  925. if (match) {
  926. link = Wikifier.linkFormatter.makeLink(A,match,this.callback);
  927. }
  928. else {
  929. link = Wikifier.linkFormatter.makeLink(A,[0,text,D[0]],this.callback);
  930. }
  931. link.className += " " + C;
  932. }
  933. }
  934. };
  935. version.extensions.backMacro = {
  936. major: 2,
  937. minor: 0,
  938. revision: 0
  939. };
  940. macros.back = {
  941. labeltext: '&#171; back',
  942. handler: function (a, b, e, parser) {
  943. var labelParam, c, el,
  944. labeltouse = this.labeltext,
  945. steps = 1,
  946. stepsParam = e.indexOf("steps"),
  947. stepsParam2 = "";
  948. // Steps parameter
  949. if(stepsParam > 0) {
  950. stepsParam2 = e[stepsParam - 1];
  951. if(stepsParam2[0] == '$') {
  952. try {
  953. stepsParam2 = internalEval(Wikifier.parse(stepsParam2));
  954. }
  955. catch(r) {
  956. throwError(a, parser.fullMatch() + " bad expression: " + r.message, parser.fullMatch())
  957. return;
  958. }
  959. }
  960. // Previously, trying to go back more steps than were present in the
  961. // history would silently revert to just 1 step.
  962. // Instead, let's just go back to the start.
  963. steps = +stepsParam2;
  964. if(steps >= state.history.length - 1) {
  965. steps = state.history.length - 2;
  966. }
  967. e.splice(stepsParam - 1, 2);
  968. }
  969. // Label parameter
  970. labelParam = e.indexOf("label");
  971. if(labelParam > -1) {
  972. if(!e[labelParam + 1]) {
  973. throwError(a, parser.fullMatch() + ": " + e[labelParam] + ' keyword needs an additional label parameter', parser.fullMatch());
  974. return;
  975. }
  976. labeltouse = e[labelParam + 1];
  977. e.splice(labelParam, 2);
  978. }
  979. // What's left is the passage name parameter
  980. if(stepsParam <= 0) {
  981. if(e[0]) {
  982. if(e[0].charAt(0) == '$') {
  983. try {
  984. e = internalEval(Wikifier.parse(e[0]));
  985. }
  986. catch(r) {
  987. throwError(a, parser.fullMatch() + " bad expression: " + r.message, parser.fullMatch())
  988. return;
  989. }
  990. }
  991. else {
  992. e = e[0];
  993. }
  994. if(!tale.has(e)) {
  995. throwError(a, "The \"" + e + "\" passage does not exist",parser.fullMatch());
  996. return;
  997. }
  998. for(c = 0; c < state.history.length; c++) {
  999. if(state.history[c].passage.title == e) {
  1000. steps = c;
  1001. break;
  1002. }
  1003. }
  1004. }
  1005. }
  1006. el = document.createElement("a");
  1007. el.className = b;
  1008. addClickHandler(el, (function(b) { return function () {
  1009. return macros.back.onclick(b == "back", steps, el)
  1010. }}(b)));
  1011. el.innerHTML = labeltouse;
  1012. a.appendChild(el);
  1013. }
  1014. };
  1015. version.extensions.returnMacro = {
  1016. major: 2,
  1017. minor: 0,
  1018. revision: 0
  1019. };
  1020. macros["return"] = {
  1021. labeltext: '&#171; return',
  1022. handler: function(a,b,e) {
  1023. macros.back.handler.call(this,a,b,e);
  1024. }
  1025. };
  1026. version.extensions.textInputMacro = {
  1027. major: 2,
  1028. minor: 0,
  1029. revision: 0
  1030. };
  1031. macros.checkbox = macros.radio = macros.textinput = {
  1032. handler: function (A, C, D, parser) {
  1033. var match,
  1034. class_ = C.replace('input','Input'),
  1035. q = A.querySelectorAll('input'),
  1036. id = class_ + "|" + ((q && q.length) || 0);
  1037. input = insertElement(null, 'input', id, class_);
  1038. input.name=D[0];
  1039. input.type=C.replace('input','');
  1040. // IE 8 support - delay insertion until now
  1041. A.appendChild(input);
  1042. if (C == "textinput" && D[1]) {
  1043. match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
  1044. if (match) {
  1045. Wikifier.linkFormatter.makeLink(A,match, macros.button.callback, 'button');
  1046. }
  1047. else {
  1048. Wikifier.linkFormatter.makeLink(A,[0,(D[2] || D[1]),D[1]], macros.button.callback, 'button');
  1049. }
  1050. }
  1051. else if ((C == "radio" || C == "checkbox") && D[1]) {
  1052. input.value = D[1];
  1053. insertElement(A, 'label','', '', D[1]).setAttribute('for',id);
  1054. if (D[2]) {
  1055. insertElement(A,'br');
  1056. D.splice(1,1);
  1057. macros[C].handler(A,C,D)
  1058. }
  1059. }
  1060. }
  1061. };
  1062. version.extensions.buttonMacro = {
  1063. major: 1,
  1064. minor: 0,
  1065. revision: 0
  1066. };
  1067. macros.button = {
  1068. callback: function() {
  1069. var el = findPassageParent(this);
  1070. if (el) {
  1071. var inputs = el.querySelectorAll("input");
  1072. for (i = 0; i < inputs.length; i++) {
  1073. if (inputs[i].type!="checkbox" && (inputs[i].type!="radio" || inputs[i].checked)) {
  1074. macros.set.run(null, Wikifier.parse(inputs[i].name+' = "'+inputs[i].value.replace(/"/g,'\\"')+'"'));
  1075. }
  1076. else if (inputs[i].type=="checkbox" && inputs[i].checked) {
  1077. macros.set.run(null, Wikifier.parse(
  1078. inputs[i].name+' = [].concat('+inputs[i].name+' || []);'));
  1079. macros.set.run(null, Wikifier.parse(
  1080. inputs[i].name+'.push("'+inputs[i].value.replace(/"/g,'\\"')+'")'));
  1081. }
  1082. }
  1083. }
  1084. },
  1085. handler: function (A, C, D, parser) {
  1086. var link,
  1087. match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
  1088. if (match) {
  1089. Wikifier.linkFormatter.makeLink(A, match, this.callback, 'button');
  1090. }
  1091. else {
  1092. Wikifier.linkFormatter.makeLink(A,[0,D[1] || D[0], D[0]], this.callback, 'button');
  1093. }
  1094. }
  1095. };
  1096. /*
  1097. **
  1098. ** Passage object
  1099. **
  1100. */
  1101. function Passage(c, b, a, ofunc) {
  1102. var t;
  1103. if (!this || this.constructor != Passage) {
  1104. throw new ReferenceError("passage() must be in lowercase");
  1105. }
  1106. this.title = c;
  1107. ofunc = typeof ofunc == 'function' && ofunc;
  1108. if (b) {
  1109. this.id = a;
  1110. // Load tags
  1111. this.tags = b.getAttribute("tags");
  1112. if (typeof this.tags == "string") {
  1113. if (ofunc) {
  1114. this.tags = ofunc(this.tags);
  1115. }
  1116. this.tags = this.tags.readBracketedList();
  1117. } else this.tags = [];
  1118. // Load text
  1119. t = b.firstChild ? b.firstChild.nodeValue : "";
  1120. if (ofunc && !this.isImage()) {
  1121. this.text = ofunc(Passage.unescapeLineBreaks(t));
  1122. } else {
  1123. this.text = Passage.unescapeLineBreaks(t);
  1124. }
  1125. // Preload linked images
  1126. if (!this.isImage()) {
  1127. this.preloadImages();
  1128. }
  1129. // Check for the .char selector, or the [data-char] selector
  1130. // False positives aren't a big issue.
  1131. if (/\.char\b|\[data\-char\b/.exec(this.text) && Wikifier.charSpanFormatter) {
  1132. Wikifier.formatters.push(Wikifier.charSpanFormatter);
  1133. delete Wikifier.charSpanFormatter;
  1134. }
  1135. } else {
  1136. this.text = '@@This passage does not exist: ' + c + '@@';
  1137. this.tags = [];
  1138. }
  1139. }
  1140. Passage.prototype.isImage = function() {
  1141. return !!~(this.tags.indexOf("Twine.image"));
  1142. };
  1143. Passage.prototype.preloadImages = function() {
  1144. // Don't preload URLs containing '$' - suspect that they are variables.
  1145. var u = "\\s*['\"]?([^\"'$]+\\.(jpe?g|a?png|gif|bmp|webp|svg))['\"]?\\s*",
  1146. k = function(c, e) {
  1147. var i,d;
  1148. do {
  1149. d = c.exec(this.text);
  1150. if(d) {
  1151. i = new Image();
  1152. i.src = d[e];
  1153. }
  1154. } while (d);
  1155. return k;
  1156. };
  1157. k.call(this, new RegExp(Wikifier.imageFormatter.lookahead.replace("[^\\[\\]\\|]+",u), "mg"), 4)
  1158. .call(this, new RegExp("url\\s*\\(" + u + "\\)", "mig"), 1)
  1159. .call(this, new RegExp("src\\s*=" + u, "mig"), 1);
  1160. };
  1161. Passage.unescapeLineBreaks = function (a) {
  1162. if (a && typeof a == "string") {
  1163. return a.replace(/\\n/mg, "\n").replace(/\\t/mg, "\t").replace(/\\s/mg, "\\").replace(/\\/mg, "\\").replace(/\r/mg, "")
  1164. } else {
  1165. return ""
  1166. }
  1167. };
  1168. Passage.prototype.setTags = function(b) {
  1169. var t = this.tags != null && this.tags.length ? this.tags.join(' ') : "";
  1170. if (t) {
  1171. b.setAttribute('data-tags', this.tags.join(' '));
  1172. }
  1173. document.body.setAttribute("data-tags", t);
  1174. };
  1175. Passage.prototype.processText = function() {
  1176. var ret = this.text;
  1177. if (~this.tags.indexOf("nobr")) {
  1178. ret = ret.replace(/\n/g,"\u200c");
  1179. }
  1180. if (this.isImage()) {
  1181. ret = "[img[" + ret + "]]"
  1182. }
  1183. return ret;
  1184. };
  1185. /*
  1186. **
  1187. ** Tale object
  1188. **
  1189. */
  1190. function Tale() {
  1191. var a,b,c,lines,i,kv,nsc,isImage,
  1192. settings = this.storysettings = {
  1193. lookup: function(a, dfault) {
  1194. // The two runtime settings (undo and bookmark) default to true.
  1195. if (!(a in this)) return dfault;
  1196. return (this[a]+"") != "off";
  1197. }
  1198. },
  1199. HTMLtitle = document.querySelector('title');
  1200. tiddlerTitle = '';
  1201. this.defaultTitle = (HTMLtitle && (HTMLtitle.textContent || HTMLtitle.innerText)) || "Untitled Story";
  1202. this.passages = {};
  1203. //Look for and load the StorySettings
  1204. if (document.normalize) document.normalize();
  1205. a = document.getElementById("storeArea").children;
  1206. for (b = 0; b < a.length; b++) {
  1207. c = a[b];
  1208. if (c.getAttribute && c.getAttribute("tiddler") == 'StorySettings') {
  1209. lines = new Passage('StorySettings', c, 0, null, null).text.split('\n');
  1210. for (i in lines) {
  1211. if (typeof lines[i] == "string" && lines[i].indexOf(':') > -1) {
  1212. kv = lines[i].toLowerCase().split(':');
  1213. kv[0] = kv[0].replace(/^\s+|\s+$/g, '');
  1214. kv[1] = kv[1].replace(/^\s+|\s+$/g, '');
  1215. if (kv[0] != "lookup") {
  1216. settings[kv[0]] = kv[1];
  1217. }
  1218. }
  1219. }
  1220. }
  1221. }
  1222. //Load in the passages
  1223. if (settings.obfuscate == 'rot13') {
  1224. for (b = 0; b < a.length; b++) {
  1225. c = a[b];
  1226. if (c.getAttribute && (tiddlerTitle = c.getAttribute("tiddler"))) {
  1227. isImage = (c.getAttribute("tags")+"").indexOf("Twine.image")>-1;
  1228. if (tiddlerTitle != 'StorySettings' && !isImage)
  1229. tiddlerTitle = rot13(tiddlerTitle);
  1230. this.passages[tiddlerTitle] = new Passage(tiddlerTitle, c, b+1, !isImage && rot13);
  1231. }
  1232. }
  1233. } else {
  1234. for (b = 0; b < a.length; b++) {
  1235. c = a[b];
  1236. if (c.getAttribute && (tiddlerTitle = c.getAttribute("tiddler"))) {
  1237. this.passages[tiddlerTitle] = new Passage(tiddlerTitle, c, b, null, null)
  1238. }
  1239. }
  1240. }
  1241. }
  1242. Tale.prototype.has = function (a) {
  1243. if (typeof a == "string") {
  1244. return (this.passages[a] != null)
  1245. } else {
  1246. for (var i in this.passages) {
  1247. if (this.passages[i].id == a) {
  1248. return true
  1249. }
  1250. }
  1251. return false
  1252. }
  1253. };
  1254. Tale.prototype.get = function (a) {
  1255. if (typeof a == "string") {
  1256. return this.passages[a] || new Passage(a)
  1257. } else {
  1258. for (var i in this.passages) {
  1259. if (this.passages[i].id == a) {
  1260. return this.passages[i]
  1261. }
  1262. }
  1263. }
  1264. };
  1265. Tale.prototype.lookup = function (h, g, a) {
  1266. var d = [];
  1267. for (var c in this.passages) {
  1268. var f = this.passages[c];
  1269. for (var b = 0; b < f[h].length; b++) {
  1270. if (f[h][b] == g) {
  1271. d.push(f)
  1272. }
  1273. }
  1274. }
  1275. if (!a) {
  1276. a = "title"
  1277. }
  1278. d.sort(function (k, j) {
  1279. if (k[a] == j[a]) {
  1280. return (0)
  1281. } else {
  1282. return (k[a] < j[a]) ? -1 : +1
  1283. }
  1284. });
  1285. return d
  1286. };
  1287. Tale.prototype.canUndo = function() {
  1288. return this.storysettings.lookup('undo',true);
  1289. };
  1290. Tale.prototype.identity = function () {
  1291. var meta = document.querySelector("meta[name='identity']"),
  1292. identity = meta ? meta.getAttribute("content") : "story";
  1293. return (Tale.prototype.identity = function() {
  1294. return identity;
  1295. })();
  1296. };
  1297. Tale.prototype.forEachStylesheet = function(tags, callback) {
  1298. var passage, i;
  1299. tags = tags || [];
  1300. if (typeof callback != "function")
  1301. return;
  1302. for (passage in this.passages) {
  1303. passage = tale.passages[passage];
  1304. if (passage && ~passage.tags.indexOf("stylesheet")) {
  1305. for (i = 0; i < tags.length; i++) {
  1306. if (~passage.tags.indexOf(tags[i])) {
  1307. callback(passage);
  1308. break;
  1309. }
  1310. }
  1311. }
  1312. }
  1313. };
  1314. Tale.prototype.setPageElements = function() {
  1315. var storyTitle;
  1316. setPageElement("storyTitle", "StoryTitle", this.defaultTitle);
  1317. storyTitle = document.getElementById("storyTitle");
  1318. document.title = this.title = (storyTitle && (storyTitle.textContent || storyTitle.innerText)) || this.defaultTitle;
  1319. setPageElement("storySubtitle", "StorySubtitle", "");
  1320. if (tale.has("StoryAuthor")) {
  1321. setPageElement("titleSeparator", null, "\n");
  1322. setPageElement("storyAuthor", "StoryAuthor", "");
  1323. }
  1324. if (tale.has("StoryMenu")) {
  1325. document.getElementById("storyMenu").setAttribute("style","");
  1326. setPageElement("storyMenu", "StoryMenu", "");
  1327. }
  1328. };
  1329. /*
  1330. **
  1331. ** Wikifier object
  1332. **
  1333. */
  1334. function Wikifier(place, source) {
  1335. this.source = source;
  1336. this.output = place;
  1337. this.nextMatch = 0;
  1338. this.assembleFormatterMatches(Wikifier.formatters);
  1339. this.subWikify(this.output);
  1340. }
  1341. Wikifier.textPrimitives = {
  1342. upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
  1343. lowerLetter: "[a-z\u00df-\u00ff_0-9\\-\u0151\u0171]",
  1344. anyLetter: "[A-Za-z\u00c0-\u00de\u00df-\u00ff_0-9\\-\u0150\u0170\u0151\u0171]"
  1345. }
  1346. Wikifier.textPrimitives.variable = "\\$((?:"+Wikifier.textPrimitives.anyLetter.replace("\\-", "")+"*"+
  1347. Wikifier.textPrimitives.anyLetter.replace("0-9\\-", "")+"+"+
  1348. Wikifier.textPrimitives.anyLetter.replace("\\-", "")+"*)+)";
  1349. Wikifier.textPrimitives.unquoted = "(?=(?:[^\"'\\\\]*(?:\\\\.|'(?:[^'\\\\]*\\\\.)*[^'\\\\]*'|\"(?:[^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^'\"]*$)";
  1350. Wikifier.prototype.assembleFormatterMatches = function (formatters) {
  1351. this.formatters = [];
  1352. var pattern = [];
  1353. for (var n = 0; n < formatters.length; n++) {
  1354. pattern.push("(" + formatters[n].match + ")");
  1355. this.formatters.push(formatters[n]);
  1356. };
  1357. this.formatterRegExp = new RegExp(pattern.join("|"), "mg");
  1358. };
  1359. Wikifier.prototype.subWikify = function (output, terminator) {
  1360. // Temporarily replace the output pointer
  1361. var terminatorMatch, formatterMatch, oldOutput = this.output;
  1362. this.output = output;
  1363. // Prepare the terminator RegExp
  1364. var terminatorRegExp = terminator ? new RegExp("(" + terminator + ")", "mg") : null;
  1365. do {
  1366. // Prepare the RegExp match positions
  1367. this.formatterRegExp.lastIndex = this.nextMatch;
  1368. if (terminatorRegExp) terminatorRegExp.lastIndex = this.nextMatch;
  1369. // Get the first matches
  1370. formatterMatch = this.formatterRegExp.exec(this.source);
  1371. terminatorMatch = terminatorRegExp ? terminatorRegExp.exec(this.source) : null;
  1372. // Check for a terminator match
  1373. if (terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
  1374. // Output any text before the match
  1375. if (terminatorMatch.index > this.nextMatch) this.outputText(this.output, this.nextMatch, terminatorMatch.index);
  1376. // Set the match parameters
  1377. this.matchStart = terminatorMatch.index;
  1378. this.matchLength = terminatorMatch[1].length;
  1379. this.matchText = terminatorMatch[1];
  1380. this.nextMatch = terminatorMatch.index + terminatorMatch[1].length;
  1381. // Restore the output pointer and exit
  1382. this.output = oldOutput;
  1383. return;
  1384. }
  1385. // Check for a formatter match
  1386. else if (formatterMatch) {
  1387. // Output any text before the match
  1388. if (formatterMatch.index > this.nextMatch) this.outputText(this.output, this.nextMatch, formatterMatch.index);
  1389. // Set the match parameters
  1390. this.matchStart = formatterMatch.index;
  1391. this.matchLength = formatterMatch[0].length;
  1392. this.matchText = formatterMatch[0];
  1393. this.nextMatch = this.formatterRegExp.lastIndex;
  1394. // Figure out which formatter matched
  1395. var matchingFormatter = -1;
  1396. for (var t = 1; t < formatterMatch.length; t++) {
  1397. if (formatterMatch[t]) {
  1398. matchingFormatter = t - 1;
  1399. break;
  1400. }
  1401. }
  1402. // Call the formatter
  1403. if (matchingFormatter != -1) { this.formatters[matchingFormatter].handler(this); }
  1404. }
  1405. }
  1406. while (terminatorMatch || formatterMatch);
  1407. // Output any text after the last match
  1408. if (this.nextMatch < this.source.length) {
  1409. this.outputText(this.output, this.nextMatch, this.source.length);
  1410. this.nextMatch = this.source.length;
  1411. }
  1412. // Restore the output pointer
  1413. this.output = oldOutput;
  1414. };
  1415. Wikifier.prototype.outputText = function (place, startPos, endPos) {
  1416. if (place) {
  1417. insertText(place, this.source.substring(startPos, endPos));
  1418. }
  1419. };
  1420. Wikifier.prototype.fullMatch = function() {
  1421. return this.source.slice(this.matchStart, this.source.indexOf('>>', this.matchStart)+2);
  1422. };
  1423. Wikifier.prototype.fullArgs = function (includeName) {
  1424. var source = this.source.replace(/\u200c/g," "),
  1425. endPos = this.nextMatch-2,
  1426. startPos = source.indexOf(includeName ? '<<' : ' ', this.matchStart);
  1427. if (!~startPos || !~endPos || endPos <= startPos) {
  1428. return "";
  1429. }
  1430. return Wikifier.parse(source.slice(startPos + (includeName ? 2 : 1), endPos).trim());
  1431. };
  1432. Wikifier.parse = function (input) {
  1433. var m, re, b = input, found = [],
  1434. g = Wikifier.textPrimitives.unquoted;
  1435. function alter(from,to) {
  1436. b = b.replace(new RegExp(from+g,"gim"),to);
  1437. return alter;
  1438. }
  1439. // Extract all the variables, and set them to 0 if undefined.
  1440. re = new RegExp(Wikifier.textPrimitives.variable+g,"gi");
  1441. while (m = re.exec(input)) {
  1442. if (!~found.indexOf(m[0])) {
  1443. // This deliberately contains a 'null or undefined' check
  1444. b = m[0]+" == null && ("+m[0]+" = 0);"+b;
  1445. found.push(m[0]);
  1446. }
  1447. }
  1448. alter(Wikifier.textPrimitives.variable, "state.history[0].variables.$1")
  1449. // Old operators
  1450. ("\\beq\\b", " == ")
  1451. ("\\bneq\\b", " != ")
  1452. ("\\bgt\\b", " > ")
  1453. ("\\bgte\\b", " >= ")
  1454. ("\\blt\\b", " < ")
  1455. ("\\blte\\b", " <= ")
  1456. ("\\band\\b", " && ")
  1457. ("\\bor\\b", " || ")
  1458. ("\\bnot\\b", " ! ")
  1459. // New operators
  1460. ("\\bis\\b", " == ")
  1461. ("\\bto\\b", " = ");
  1462. return b
  1463. };
  1464. Wikifier.formatHelpers = {
  1465. charFormatHelper: function (a) {
  1466. var b = insertElement(a.output, this.element);
  1467. a.subWikify(b, this.terminator)
  1468. },
  1469. inlineCssHelper: function (w) {
  1470. var s, v, lookaheadMatch, gotMatch,
  1471. styles = [],
  1472. lookahead = Wikifier.styleByCharFormatter.lookahead,
  1473. lookaheadRegExp = new RegExp(lookahead, "mg"),
  1474. hadStyle = false,
  1475. unDash = function (str) {
  1476. var s = str.split("-");
  1477. if (s.length > 1) for (var t = 1; t < s.length; t++)
  1478. s[t] = s[t].substr(0, 1).toUpperCase() + s[t].substr(1);
  1479. return s.join("");
  1480. };
  1481. styles.className = "";
  1482. do {
  1483. lookaheadRegExp.lastIndex = w.nextMatch;
  1484. lookaheadMatch = lookaheadRegExp.exec(w.source);
  1485. gotMatch = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
  1486. if (gotMatch) {
  1487. hadStyle = true;
  1488. if (lookaheadMatch[5]) {
  1489. styles.className += lookaheadMatch[5].replace(/\./g," ") + " ";
  1490. } else if (lookaheadMatch[1]) {
  1491. s = unDash(lookaheadMatch[1]);
  1492. v = lookaheadMatch[2];
  1493. } else {
  1494. s = unDash(lookaheadMatch[3]);
  1495. v = lookaheadMatch[4];
  1496. }
  1497. switch (s) {
  1498. case "bgcolor":
  1499. s = "backgroundColor";
  1500. break;
  1501. case "float":
  1502. s = "cssFloat";
  1503. break
  1504. }
  1505. styles.push({
  1506. style: s,
  1507. value: v
  1508. });
  1509. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1510. }
  1511. } while (gotMatch);
  1512. return styles;
  1513. },
  1514. monospacedByLineHelper: function (w) {
  1515. var lookaheadRegExp = new RegExp(this.lookahead, "mg");
  1516. lookaheadRegExp.lastIndex = w.matchStart;
  1517. var lookaheadMatch = lookaheadRegExp.exec(w.source);
  1518. if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
  1519. insertElement(w.output, "pre", null, null, lookaheadMatch[1]);
  1520. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1521. }
  1522. }
  1523. };
  1524. Wikifier.formatters = [
  1525. {
  1526. name: "table",
  1527. match: "^\\|(?:[^\\n]*)\\|(?:[fhc]?)$",
  1528. lookahead: "^\\|([^\\n]*)\\|([fhc]?)$",
  1529. rowTerminator: "\\|(?:[fhc]?)$\\n?",
  1530. cellPattern: "(?:\\|([^\\n\\|]*)\\|)|(\\|[fhc]?$\\n?)",
  1531. cellTerminator: "(?:\\x20*)\\|",
  1532. rowTypes: {
  1533. "c": "caption",
  1534. "h": "thead",
  1535. "": "tbody",
  1536. "f": "tfoot"
  1537. },
  1538. handler: function (w) {
  1539. var rowContainer, rowElement,lookaheadMatch, matched,
  1540. table = insertElement(w.output, "table"),
  1541. lookaheadRegExp = new RegExp(this.lookahead, "mg"),
  1542. currRowType = null,
  1543. nextRowType,
  1544. prevColumns = [],
  1545. rowCount = 0;
  1546. w.nextMatch = w.matchStart;
  1547. do {
  1548. lookaheadRegExp.lastIndex = w.nextMatch;
  1549. lookaheadMatch = lookaheadRegExp.exec(w.source),
  1550. matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
  1551. if (matched) {
  1552. nextRowType = lookaheadMatch[2];
  1553. if (nextRowType != currRowType) rowContainer = insertElement(table, this.rowTypes[nextRowType]);
  1554. currRowType = nextRowType;
  1555. if (currRowType == "c") {
  1556. if (rowCount == 0) rowContainer.setAttribute("align", "top");
  1557. else rowContainer.setAttribute("align", "bottom");
  1558. w.nextMatch = w.nextMatch + 1;
  1559. w.subWikify(rowContainer, this.rowTerminator);
  1560. } else {
  1561. rowElement = insertElement(rowContainer, "tr");
  1562. this.rowHandler(w, rowElement, prevColumns);
  1563. }
  1564. rowCount++;
  1565. }
  1566. } while (matched);
  1567. },
  1568. rowHandler: function (w, e, prevColumns) {
  1569. var cellMatch, matched, col = 0,
  1570. currColCount = 1,
  1571. cellRegExp = new RegExp(this.cellPattern, "mg");
  1572. do {
  1573. cellRegExp.lastIndex = w.nextMatch;
  1574. cellMatch = cellRegExp.exec(w.source);
  1575. matched = cellMatch && cellMatch.index == w.nextMatch;
  1576. if (matched) {
  1577. if (cellMatch[1] == "~") {
  1578. var last = prevColumns[col];
  1579. if (last) {
  1580. last.rowCount++;
  1581. last.element.setAttribute("rowSpan", last.rowCount);
  1582. last.element.setAttribute("rowspan", last.rowCount);
  1583. last.element.valign = "center";
  1584. }
  1585. w.nextMatch = cellMatch.index + cellMatch[0].length - 1;
  1586. } else if (cellMatch[1] == ">") {
  1587. currColCount++;
  1588. w.nextMatch = cellMatch.index + cellMatch[0].length - 1;
  1589. } else if (cellMatch[2]) {
  1590. w.nextMatch = cellMatch.index + cellMatch[0].length;
  1591. break;
  1592. } else {
  1593. var spaceLeft = false,
  1594. spaceRight = false,
  1595. lastColCount, lastColElement, styles, cell, t;
  1596. w.nextMatch++;
  1597. styles = Wikifier.formatHelpers.inlineCssHelper(w);
  1598. while (w.source.substr(w.nextMatch, 1) == " ") {
  1599. spaceLeft = true;
  1600. w.nextMatch++;
  1601. }
  1602. if (w.source.substr(w.nextMatch, 1) == "!") {
  1603. cell = insertElement(e, "th");
  1604. w.nextMatch++;
  1605. } else cell = insertElement(e, "td");
  1606. prevColumns[col] = {
  1607. rowCount: 1,
  1608. element: cell
  1609. };
  1610. lastColCount = 1;
  1611. lastColElement = cell;
  1612. if (currColCount > 1) {
  1613. cell.setAttribute("colSpan", currColCount);
  1614. cell.setAttribute("colspan", currColCount);
  1615. currColCount = 1;
  1616. }
  1617. for (t = 0; t < styles.length; t++)
  1618. cell.style[styles[t].style] = styles[t].value;
  1619. w.subWikify(cell, this.cellTerminator);
  1620. if (w.matchText.substr(w.matchText.length - 2, 1) == " ") spaceRight = true;
  1621. if (spaceLeft && spaceRight) cell.align = "center";
  1622. else if (spaceLeft) cell.align = "right";
  1623. else if (spaceRight) cell.align = "left";
  1624. w.nextMatch = w.nextMatch - 1;
  1625. }
  1626. col++;
  1627. }
  1628. } while (matched);
  1629. }
  1630. },
  1631. {
  1632. name: "rule",
  1633. match: "^----$\\n?",
  1634. handler: function (w) {
  1635. insertElement(w.output, "hr");
  1636. }
  1637. },
  1638. {
  1639. name: "emdash",
  1640. match: "--",
  1641. becomes: String.fromCharCode(8212),
  1642. handler: function (a) {
  1643. insertElement(a.output, "span", null, "char", this.becomes).setAttribute("data-char","emdash");
  1644. }
  1645. },
  1646. {
  1647. name: "heading",
  1648. match: "^!{1,5}",
  1649. terminator: "\\n",
  1650. handler: function (w) {
  1651. var e = insertElement(w.output, "h" + w.matchLength);
  1652. w.subWikify(e, this.terminator);
  1653. }
  1654. },
  1655. {
  1656. name: "monospacedByLine",
  1657. match: "^\\{\\{\\{\\n",
  1658. lookahead: "^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)",
  1659. handler: Wikifier.formatHelpers.monospacedByLineHelper
  1660. },
  1661. {
  1662. name: "quoteByBlock",
  1663. match: "^<<<\\n",
  1664. terminator: "^<<<\\n",
  1665. handler: function (w) {
  1666. var e = insertElement(w.output, "blockquote");
  1667. w.subWikify(e, this.terminator);
  1668. }
  1669. },
  1670. {
  1671. name: "list",
  1672. match: "^(?:(?:\\*+)|(?:#+))",
  1673. lookahead: "^(?:(\\*+)|(#+))",
  1674. terminator: "\\n",
  1675. handler: function (w) {
  1676. var newType, newLevel, t, len, bulletType, lookaheadMatch, matched,
  1677. lookaheadRegExp = new RegExp(this.lookahead, "mg"),
  1678. placeStack = [w.output],
  1679. currType = null,
  1680. currLevel = 0;
  1681. w.nextMatch = w.matchStart;
  1682. do {
  1683. lookaheadRegExp.lastIndex = w.nextMatch;
  1684. lookaheadMatch = lookaheadRegExp.exec(w.source);
  1685. matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
  1686. if (matched) {
  1687. newLevel = lookaheadMatch[0].length;
  1688. if (lookaheadMatch[1]) {
  1689. bulletType = lookaheadMatch[1].slice(-1);
  1690. newType = "ul";
  1691. }
  1692. else if (lookaheadMatch[2]) {
  1693. newType = "ol";
  1694. }
  1695. w.nextMatch += newLevel;
  1696. if (newLevel > currLevel) {
  1697. for (t = currLevel; t < newLevel; t++) {
  1698. placeStack.push(insertElement(placeStack[placeStack.length - 1], newType));
  1699. }
  1700. } else if (newLevel < currLevel) {
  1701. for (t = currLevel; t > newLevel; t--)
  1702. placeStack.pop();
  1703. } else if (newLevel == currLevel && newType != currType) {
  1704. placeStack.pop();
  1705. placeStack.push(insertElement(placeStack[placeStack.length - 1], newType));
  1706. }
  1707. currLevel = newLevel;
  1708. currType = newType;
  1709. t = insertElement(placeStack[placeStack.length - 1], "li");
  1710. // Currently unused
  1711. if (bulletType && bulletType != "*") {
  1712. t.setAttribute("data-bullet", bulletType);
  1713. }
  1714. w.subWikify(t, this.terminator);
  1715. }
  1716. } while (matched);
  1717. }
  1718. },
  1719. (Wikifier.urlFormatter = {
  1720. name: "urlLink",
  1721. match: "(?:https?|mailto|javascript|ftp|data):[^\\s'\"]+(?:/|\\b)",
  1722. handler: function (w) {
  1723. var e = Wikifier.createExternalLink(w.output, w.matchText);
  1724. w.outputText(e, w.matchStart, w.nextMatch);
  1725. }
  1726. }),
  1727. (Wikifier.linkFormatter = {
  1728. name: "prettyLink",
  1729. match: "\\[\\[",
  1730. lookahead: "\\[\\[([^\\|]*?)(?:\\|(.*?))?\\](?:\\[(.*?)\])?\\]",
  1731. makeInternalOrExternal: function(out,title,callback,type) {
  1732. if (title && !tale.has(title) && (title.match(Wikifier.urlFormatter.match,"g") || ~title.search(/[\.\\\/#]/)))
  1733. return Wikifier.createExternalLink(out, title, callback, type);
  1734. else
  1735. return Wikifier.createInternalLink(out, title, callback, type);
  1736. },
  1737. // This base callback executes the code in a setter
  1738. makeCallback: function(code,callback) {
  1739. return function() {
  1740. macros.set.run(null, Wikifier.parse(code), null, code);
  1741. typeof callback == "function" && callback.call(this);
  1742. }
  1743. },
  1744. makeLink: function(out, match, callback2, type) {
  1745. var link, title, callback;
  1746. if (match[3]) { // Code
  1747. callback = this.makeCallback(match[3],callback2);
  1748. }
  1749. else {
  1750. typeof callback2 == "function" && (callback = callback2);
  1751. }
  1752. title = Wikifier.parsePassageTitle(match[2] || match[1]);
  1753. link = this.makeInternalOrExternal(out,title,callback, type);
  1754. setPageElement(link, null, match[2] ? match[1] : title);
  1755. return link;
  1756. },
  1757. handler: function (w) {
  1758. var lookaheadRegExp = new RegExp(this.lookahead, "mg");
  1759. lookaheadRegExp.lastIndex = w.matchStart;
  1760. var lookaheadMatch = lookaheadRegExp.exec(w.source)
  1761. if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
  1762. this.makeLink(w.output, lookaheadMatch)
  1763. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1764. }
  1765. }
  1766. }),
  1767. (Wikifier.imageFormatter = {
  1768. name: "image",
  1769. match: "\\[(?:[<]{0,1})(?:[>]{0,1})[Ii][Mm][Gg]\\[",
  1770. lookahead: "\\[([<]?)(>?)img\\[(?:([^\\|\\]]+)\\|)?([^\\[\\]\\|]+)\\](?:\\[([^\\]]*)\\]?)?(\\])",
  1771. importedImage: function(img, passageName) {
  1772. var imgPassages, imgname;
  1773. // Try to parse it as a variable
  1774. try {
  1775. imgname = internalEval(Wikifier.parse(passageName));
  1776. }
  1777. catch(e) {
  1778. }
  1779. if (!imgname) {
  1780. imgname = passageName;
  1781. }
  1782. // Base64 passage transclusion
  1783. imgPassages = tale.lookup("tags", "Twine.image");
  1784. for (j = 0; j < imgPassages.length; j++) {
  1785. if (imgPassages[j].title == imgname) {
  1786. img.src = imgPassages[j].text;
  1787. break;
  1788. }
  1789. }
  1790. },
  1791. handler: function (w) {
  1792. var e, img, j, lookaheadMatch,
  1793. lookaheadRegExp = new RegExp(this.lookahead, "mig");
  1794. lookaheadRegExp.lastIndex = w.matchStart;
  1795. lookaheadMatch = lookaheadRegExp.exec(w.source);
  1796. if (lookaheadMatch && lookaheadMatch.index == w.matchStart)
  1797. {
  1798. e = w.output, title = Wikifier.parsePassageTitle(lookaheadMatch[5])
  1799. if (title) {
  1800. e = Wikifier.linkFormatter.makeInternalOrExternal(w.output, title);
  1801. }
  1802. img = insertElement(e, "img");
  1803. if (lookaheadMatch[1]) img.align = "left";
  1804. else if (lookaheadMatch[2]) img.align = "right";
  1805. if (lookaheadMatch[3]) img.title = lookaheadMatch[3];
  1806. // Setup the image if it's referencing an image passage.
  1807. this.importedImage(img,lookaheadMatch[4]);
  1808. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1809. }
  1810. }
  1811. }),
  1812. {
  1813. name: "macro",
  1814. match: "<<",
  1815. lookahead: /<<([^>\s]+)(?:\s*)((?:\\.|'(?:[^'\\]*\\.)*[^'\\]*'|"(?:[^"\\]*\\.)*[^"\\]*"|[^'"\\>]|>(?!>))*)>>/mg,
  1816. handler: function (w) {
  1817. var lookaheadRegExp = new RegExp(this.lookahead);
  1818. lookaheadRegExp.lastIndex = w.matchStart;
  1819. var lookaheadMatch = lookaheadRegExp.exec(w.source.replace(/\u200c/g,'\n'));
  1820. if (lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
  1821. var params = lookaheadMatch[2].readMacroParams();
  1822. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1823. var name = lookaheadMatch[1];
  1824. try {
  1825. var macro = macros[name];
  1826. if (macro && typeof macro == "object" && macro.handler) {
  1827. macro.handler(w.output, name, params, w);
  1828. }
  1829. // Variable?
  1830. else if (name[0] == '$') {
  1831. macros.print.handler(w.output, name, [name].concat(params), w);
  1832. }
  1833. // Passage
  1834. else if (tale.has(name)) {
  1835. macros.display.handler(w.output, name, [name].concat(params), w);
  1836. }
  1837. else throwError(w.output, 'No macro or passage called "' + name + '"', w.fullMatch());
  1838. } catch (e) {
  1839. throwError(w.output, 'Error executing macro ' + name + ': ' + e.toString(), w.fullMatch());
  1840. }
  1841. }
  1842. }
  1843. },
  1844. {
  1845. name: "html",
  1846. match: "<html>",
  1847. lookahead: "<html>((?:.|\\n)*?)</html>",
  1848. handler: function (w) {
  1849. var lookaheadRegExp = new RegExp(this.lookahead, "mg");
  1850. lookaheadRegExp.lastIndex = w.matchStart;
  1851. var lookaheadMatch = lookaheadRegExp.exec(w.source)
  1852. if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
  1853. var e = insertElement(w.output, "span");
  1854. e.innerHTML = lookaheadMatch[1];
  1855. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1856. }
  1857. }
  1858. },
  1859. {
  1860. name: "commentByBlock",
  1861. match: "/%",
  1862. lookahead: "/%((?:.|\\n)*?)%/",
  1863. handler: function (w) {
  1864. var lookaheadRegExp = new RegExp(this.lookahead, "mg");
  1865. lookaheadRegExp.lastIndex = w.matchStart;
  1866. var lookaheadMatch = lookaheadRegExp.exec(w.source)
  1867. if (lookaheadMatch && lookaheadMatch.index == w.matchStart) w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1868. }
  1869. },
  1870. {
  1871. name: "boldByChar",
  1872. match: "''",
  1873. terminator: "''",
  1874. element: "strong",
  1875. handler: Wikifier.formatHelpers.charFormatHelper
  1876. },
  1877. {
  1878. name: "strikeByChar",
  1879. match: "==",
  1880. terminator: "==",
  1881. element: "strike",
  1882. handler: Wikifier.formatHelpers.charFormatHelper
  1883. },
  1884. {
  1885. name: "underlineByChar",
  1886. match: "__",
  1887. terminator: "__",
  1888. element: "u",
  1889. handler: Wikifier.formatHelpers.charFormatHelper
  1890. },
  1891. {
  1892. name: "italicByChar",
  1893. match: "//",
  1894. terminator: "//",
  1895. element: "em",
  1896. handler: Wikifier.formatHelpers.charFormatHelper
  1897. },
  1898. {
  1899. name: "subscriptByChar",
  1900. match: "~~",
  1901. terminator: "~~",
  1902. element: "sub",
  1903. handler: Wikifier.formatHelpers.charFormatHelper
  1904. },
  1905. {
  1906. name: "superscriptByChar",
  1907. match: "\\^\\^",
  1908. terminator: "\\^\\^",
  1909. element: "sup",
  1910. handler: Wikifier.formatHelpers.charFormatHelper
  1911. },
  1912. {
  1913. name: "monospacedByChar",
  1914. match: "\\{\\{\\{",
  1915. lookahead: "\\{\\{\\{((?:.|\\n)*?)\\}\\}\\}",
  1916. handler: function (w) {
  1917. var lookaheadRegExp = new RegExp(this.lookahead, "mg");
  1918. lookaheadRegExp.lastIndex = w.matchStart;
  1919. var lookaheadMatch = lookaheadRegExp.exec(w.source)
  1920. if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
  1921. var e = insertElement(w.output, "code", null, null, lookaheadMatch[1]);
  1922. w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  1923. }
  1924. }
  1925. },
  1926. (Wikifier.styleByCharFormatter = {
  1927. name: "styleByChar",
  1928. match: "@@",
  1929. terminator: "@@",
  1930. lookahead: "(?:([^\\(@]+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:([^\\.:@]+):([^;\\|\\n]+);)|(?:\\.([^;\\|\\n]+);)",
  1931. handler: function (w) {
  1932. var e = insertElement(w.output, "span", null, null, null);
  1933. var styles = Wikifier.formatHelpers.inlineCssHelper(w);
  1934. if (styles.length == 0) {
  1935. e.className = "marked";
  1936. }
  1937. else {
  1938. for (var t = 0; t < styles.length; t++) {
  1939. e.style[styles[t].style] = styles[t].value;
  1940. }
  1941. if (typeof styles.className == "string") {
  1942. e.className = styles.className;
  1943. }
  1944. }
  1945. w.subWikify(e, this.terminator);
  1946. }
  1947. }),
  1948. {
  1949. name: "lineBreak",
  1950. match: "\\n",
  1951. handler: function (w) {
  1952. insertElement(w.output, "br");
  1953. }
  1954. },
  1955. {
  1956. name: "continuedLine",
  1957. match: "\\\\\\s*?\\n",
  1958. handler: function(a) {
  1959. a.nextMatch = a.matchStart+2;
  1960. }
  1961. },
  1962. {
  1963. name: "htmlCharacterReference",
  1964. match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)",
  1965. handler: function(w)
  1966. {
  1967. var el = document.createElement("div");
  1968. el.innerHTML = w.matchText;
  1969. insertText(w.output, el.textContent);
  1970. }
  1971. },
  1972. {
  1973. name: "htmltag",
  1974. match: "<(?:\\/?[\\w\\-]+|[\\w\\-]+(?:(?:\\s+[\\w\\-]+(?:\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)\\/?)>",
  1975. tagname: "<(\\w+)",
  1976. voids: ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"],
  1977. tableElems: ["table","thead","tbody","tfoot","th","tr","td","colgroup","col","caption","figcaption"],
  1978. cleanupTables: function (e) {
  1979. var i, name, elems = [].slice.call(e.children);
  1980. // Remove non-table child elements that aren't children of <td>s
  1981. for (i = 0; i < elems.length; i++) {
  1982. if (elems[i].tagName) {
  1983. name = elems[i].tagName.toLowerCase();
  1984. if (this.tableElems.indexOf(name)==-1) {
  1985. elems[i].outerHTML = '';
  1986. }
  1987. else if (['col','caption','figcaption','td','th'].indexOf(name)==-1) {
  1988. this.cleanupTables.call(this,elems[i]);
  1989. }
  1990. }
  1991. }
  1992. },
  1993. handler: function (a) {
  1994. var tmp, passage, setter, e, isvoid, isstyle, lookaheadRegExp, lookaheadMatch, lookahead,
  1995. re = new RegExp(this.tagname).exec(a.matchText),
  1996. tn = re && re[1] && re[1].toLowerCase();
  1997. if(tn && tn != "html") {
  1998. lookahead = "<\\/\\s*" + tn + "\\s*>";
  1999. isvoid = (this.voids.indexOf(tn) != -1);
  2000. isstyle = tn == "style" || tn == "script";
  2001. lookaheadRegExp = new RegExp(lookahead, "mg");
  2002. lookaheadRegExp.lastIndex = a.matchStart;
  2003. lookaheadMatch = lookaheadRegExp.exec(a.source);
  2004. if (lookaheadMatch || isvoid) {
  2005. if (isstyle) {
  2006. e = document.createElement(tn);
  2007. e.type = "text/css"; // IE8 compatibility
  2008. tmp = a.source.slice(a.nextMatch, lookaheadMatch.index);
  2009. e.styleSheet ? (e.styleSheet.cssText = tmp) : (e.innerHTML = tmp);
  2010. a.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
  2011. }
  2012. else {
  2013. // Creating a loose <tr> element creates it wrapped inside a <tbody> element.
  2014. e = document.createElement(a.output.tagName);
  2015. e.innerHTML = a.matchText;
  2016. while(e.firstChild) {
  2017. e = e.firstChild;
  2018. }
  2019. if (!isvoid) {
  2020. a.subWikify(e, lookahead);
  2021. }
  2022. }
  2023. if (e.tagName.toLowerCase() == 'table') {
  2024. this.cleanupTables.call(this,e);
  2025. }
  2026. // Special data-setter attribute
  2027. if (setter = e.getAttribute("data-setter")) {
  2028. setter = Wikifier.linkFormatter.makeCallback(setter);
  2029. }
  2030. // Special data-passage attribute
  2031. if (passage = e.getAttribute("data-passage")) {
  2032. if (tn != "img") {
  2033. addClickHandler(e, Wikifier.linkFunction(Wikifier.parsePassageTitle(passage), e, setter));
  2034. if (tn == "area" || tn == "a") {
  2035. e.setAttribute("href", "javascript:;");
  2036. }
  2037. }
  2038. else {
  2039. Wikifier.imageFormatter.importedImage(e, passage);
  2040. }
  2041. }
  2042. a.output.appendChild(e);
  2043. } else {
  2044. throwError(a.output,"HTML tag '"+tn+"' wasn't closed.", a.matchText);
  2045. }
  2046. }
  2047. }
  2048. }];
  2049. // Optional - included if the ".char" selector appears anywhere in the story.
  2050. Wikifier.charSpanFormatter = {
  2051. name: "char",
  2052. match: "[^\n]",
  2053. handler: function (a) {
  2054. // Line breaks do NOT get their own charspans
  2055. insertElement(a.output, "span", null, "char", a.matchText).setAttribute("data-char", a.matchText == " " ? "space" :
  2056. a.matchText == "\t" ? "tab" : a.matchText);
  2057. }
  2058. };
  2059. Wikifier.parsePassageTitle = function(title) {
  2060. if (title && !tale.has(title)) {
  2061. try {
  2062. title = (internalEval(this.parse(title)) || title)+"";
  2063. }
  2064. catch(e) {}
  2065. }
  2066. return title;
  2067. };
  2068. Wikifier.linkFunction = function(title, el, callback) {
  2069. return function() {
  2070. if (state.rewindTo) {
  2071. var passage = findPassageParent(el);
  2072. if (passage && passage.parentNode.lastChild != passage) {
  2073. state.rewindTo(passage, true);
  2074. }
  2075. }
  2076. if (title) {
  2077. state.display(title, el, null, callback);
  2078. }
  2079. else if (typeof callback == "function") {
  2080. callback();
  2081. }
  2082. };
  2083. };
  2084. Wikifier.createInternalLink = function (place, title, callback, type) {
  2085. var tag = (type == "button" ? 'button' : 'a'),
  2086. suffix = (type == "button" ? "Button" : "Link"),
  2087. el = insertElement(place, tag);
  2088. if (tale.has(title)) {
  2089. el.className = 'internal'+suffix;
  2090. if (visited(title)) el.className += ' visited'+suffix;
  2091. }
  2092. else el.className = 'broken'+suffix;
  2093. addClickHandler(el, Wikifier.linkFunction(title, el, callback));
  2094. if (place) place.appendChild(el);
  2095. return el;
  2096. };
  2097. Wikifier.createExternalLink = function (place, url, callback, type) {
  2098. var tag = (type == "button" ? 'button' : 'a'),
  2099. el = insertElement(place, tag);
  2100. el.href = url;
  2101. el.className = "external"+(type == "button" ? "Button" : "Link");
  2102. el.target = "_blank";
  2103. if (typeof callback == "function") {
  2104. addClickHandler(el,callback);
  2105. }
  2106. if (place) place.appendChild(el);
  2107. return el;
  2108. };
  2109. // Functions usable solely by custom scripts
  2110. // This includes restart(), above.
  2111. function visited(e) {
  2112. var ret = 0, i = 0;
  2113. if (!state) {
  2114. return 0;
  2115. }
  2116. e = e || state.history[0].passage.title;
  2117. if (arguments.length > 1) {
  2118. for (ret = state.history.length; i<arguments.length; i++) {
  2119. ret = Math.min(ret, visited(arguments[i]));
  2120. }
  2121. }
  2122. else for(; i<state.history.length && state.history[i].passage; i++) {
  2123. if(e == state.history[i].passage.title) {
  2124. ret++;
  2125. }
  2126. }
  2127. return ret;
  2128. }
  2129. // Returns the number of times the player visited passages containing ALL
  2130. // provided tags.
  2131. // The tags can be several strings, or a single string with space-delimited tags.
  2132. function visitedTag() {
  2133. var i, j, sh, ret = 0, tags = Array.prototype.slice.call(arguments);
  2134. if (tags.length == 1 && typeof tags[0] == "string") {
  2135. tags = tags.split(" ");
  2136. }
  2137. if (!state) {
  2138. return 0;
  2139. }
  2140. sh = state.history;
  2141. for(i = 0; i<sh.length && sh[i].passage; i++) {
  2142. for (j = 0; j < tags.length || void ret++; j++) {
  2143. if(sh[i].passage.tags.indexOf(tags[j])==-1) {
  2144. break;
  2145. }
  2146. }
  2147. }
  2148. return ret;
  2149. }
  2150. var visitedTags = visitedTag;
  2151. function turns() {
  2152. return state.history.length-1;
  2153. }
  2154. function passage() {
  2155. return state.history[0].passage.title
  2156. }
  2157. function tags(e) {
  2158. var ret = [], i = 0;
  2159. if (!state) {
  2160. return 0;
  2161. }
  2162. e = e || state.history[0].passage.title;
  2163. if (arguments.length > 1) {
  2164. for (i = arguments.length-1; i >= 1; i--) {
  2165. ret = ret.concat(tags(arguments[i]));
  2166. }
  2167. }
  2168. ret = ret.concat(tale.get(e).tags);
  2169. return ret;
  2170. }
  2171. function previous() {
  2172. if (state && state.history[1]) {
  2173. for (var d = 1; d < state.history.length && state.history[d].passage; d++) {
  2174. if (state.history[d].passage.title != state.history[0].passage.title) {
  2175. return state.history[d].passage.title
  2176. }
  2177. }
  2178. }
  2179. return ""
  2180. }
  2181. // A random integer function
  2182. // 1 argument: random int from 0 to a inclusive
  2183. // 2 arguments: random int from a to b inclusive (order irrelevant)
  2184. function random(a, b) {
  2185. var from, to;
  2186. if (!b) {
  2187. from = 0;
  2188. to = a;
  2189. } else {
  2190. from = Math.min(a, b);
  2191. to = Math.max(a, b);
  2192. }
  2193. to += 1;
  2194. return ~~((Math.random() * (to - from))) + from;
  2195. }
  2196. function either() {
  2197. if (Array.isArray(arguments[0]) && arguments.length == 1) {
  2198. return either.apply(this,arguments[0]);
  2199. }
  2200. return arguments[~~(Math.random()*arguments.length)];
  2201. }
  2202. function parameter(n) {
  2203. n = n || 0;
  2204. if (macros.display.parameters[n]) {
  2205. return macros.display.parameters[n];
  2206. }
  2207. return 0
  2208. }
  2209. function bookmark() {
  2210. return state.hash || "#";
  2211. }
  2212. /* Used to eval within Twine, but outside the context of a particular function */
  2213. function internalEval(s) {
  2214. // eval("{x:1,y:1}") fails miserably unless the obj. literal
  2215. // is not the first expression in the line (hence, "0,").
  2216. return eval("0,"+s);
  2217. }
  2218. /* Used to execute script passages */
  2219. function scriptEval(s) {
  2220. try {
  2221. eval(s.text);
  2222. } catch (e) {
  2223. alert("There is a technical problem with this " + tale.identity() + " (" + s.title + ": " + e.message + ")."+softErrorMessage);
  2224. }
  2225. }
  2226. /* Unload prompt */
  2227. window.onbeforeunload = function() {
  2228. if (tale && tale.storysettings.lookup("exitprompt",false) && state && state.history.length > 1) {
  2229. return "You are about to end this " + tale.identity() + ".";
  2230. }
  2231. };
  2232. /* Error reporting */
  2233. var oldOnError = window.onerror || null,
  2234. softErrorMessage = "You may be able to continue playing, but some parts may not work properly.";
  2235. window.onerror = function (msg, a, b, c, error) {
  2236. var s = (error && (".\n\n" + error.stack.replace(/\([^\)]+\)/g,'') + "\n\n")) || (" (" + msg + ").\n");
  2237. alert("Sorry to interrupt, but this " + ((tale && tale.identity && tale.identity()) || "page") + "'s code has got itself in a mess" + s + softErrorMessage.slice(1));
  2238. // Restore previous onerror
  2239. window.onerror = oldOnError;
  2240. if (typeof window.onerror == "function") {
  2241. window.onerror(msg, a, b, c, error);
  2242. }
  2243. };
  2244. /* Init function */
  2245. var $;
  2246. function main() {
  2247. // Used by old custom scripts.
  2248. // Cedes to jQuery if it exists.
  2249. // Since jQuery is inserted after this script element,
  2250. // wait until main() is called before doing this.
  2251. $ = window.$ || function(a) {
  2252. return (typeof a == "string" ? document.getElementById(a) : a);
  2253. }
  2254. var imgs, scripts, macro, style, i,
  2255. styleText = "",
  2256. passages = document.getElementById("passages");
  2257. // Run checks after custom macros are installed
  2258. function sanityCheck(thing) {
  2259. var i, j, s = "NOTE: The " + thing,
  2260. checks = { prerender: prerender, postrender: postrender, macros: macros };
  2261. // Warn if prerender/postrender/macros aren't objects, and
  2262. // prerender/postrender's own properties aren't functions.
  2263. for (i in checks) {
  2264. if (Object.prototype.hasOwnProperty.call(checks, i) && !sanityCheck[i]) {
  2265. if (!checks[i] || typeof checks[i] != "object") {
  2266. alert(s + " seems to have corrupted the " + i + " object."+softErrorMessage);
  2267. sanityCheck[i] = true;
  2268. continue;
  2269. }
  2270. if (i != "macros") {
  2271. for (j in checks[i]) {
  2272. if (Object.prototype.hasOwnProperty.call(checks[i], j)
  2273. && typeof checks[i][j] != "function") {
  2274. alert(s + " added a property '" + j + "' to " + i + ", "
  2275. +"which is a "+typeof checks[i][j]+", not a function."+softErrorMessage);
  2276. sanityCheck[i] = true;
  2277. break;
  2278. }
  2279. }
  2280. }
  2281. }
  2282. }
  2283. }
  2284. // Check for basic compatibility
  2285. if (!window.JSON || !document.querySelector) {
  2286. return (passages.innerHTML = "This " + tale.identity() + " requires a newer web browser. Sorry.");
  2287. } else {
  2288. passages.innerHTML = "";
  2289. }
  2290. // Initialise Tale
  2291. tale = window.tale = new Tale();
  2292. // Check for IE8 image compatibility
  2293. if (~document.documentElement.className.indexOf("lt-ie9")) {
  2294. imgs = tale.lookup("tags", "Twine.image");
  2295. for (i = 0; i < imgs.length; i++) {
  2296. if (imgs[i].text.length >= 32768) {
  2297. alert("NOTE: This " + tale.identity() + "'s HTML file contains embedded images that may be too large for this browser to display."+softErrorMessage);
  2298. break;
  2299. }
  2300. }
  2301. }
  2302. // Run all script passages
  2303. scripts = tale.lookup("tags", "script");
  2304. for (i = 0; i < scripts.length; i++) {
  2305. scriptEval(scripts[i]);
  2306. sanityCheck('script passage "'+scripts[i].title+'"');
  2307. }
  2308. // Need to create state now, since it's used by remember.init()
  2309. state = window.state = new History();
  2310. for (i in macros) {
  2311. macro = macros[i];
  2312. if (typeof macro.init == "function") {
  2313. macro.init();
  2314. sanityCheck('init() of the custom macro "'+i+'"');
  2315. }
  2316. }
  2317. // Run all stylesheet passages
  2318. style = document.getElementById("storyCSS");
  2319. for (i in tale.passages) {
  2320. i = tale.passages[i];
  2321. if (i.tags.indexOf("stylesheet")==-1) {
  2322. continue;
  2323. }
  2324. // No other tags?
  2325. if (i.tags + "" == "stylesheet") {
  2326. styleText += i.text;
  2327. }
  2328. else if (i.tags.length == 2 && i.tags.indexOf("transition") >-1) {
  2329. setTransitionCSS(i.text);
  2330. }
  2331. }
  2332. styleText = alterCSS(styleText);
  2333. style.styleSheet ? (style.styleSheet.cssText = styleText) : (style.innerHTML = styleText);
  2334. state.init();
  2335. }
  2336. setTimeout(function f() {
  2337. var size, bar = document.getElementById("loadingbar"), store = document.getElementById("storeArea");
  2338. if (!bar) {
  2339. return;
  2340. }
  2341. if (store) {
  2342. size = store.getAttribute("data-size");
  2343. if (store.children.length <= size && !tale) {
  2344. // +1 so that the bar can reach the end
  2345. bar.style.width = ~~((store.children.length+1)/size*100)+"%";
  2346. }
  2347. else {
  2348. bar.outerHTML = "";
  2349. return;
  2350. }
  2351. }
  2352. setTimeout(f,5);
  2353. },5);
  2354. /*
  2355. **
  2356. ** Sugarcane/Responsive specific code follows
  2357. **
  2358. */
  2359. var hasPushState = !!window.history && (typeof window.history.pushState == "function") && (function(a) {
  2360. // iOS Safari: setItem throws in private mode
  2361. try {
  2362. a.setItem("test", '1');
  2363. a.removeItem("test");
  2364. return true;
  2365. } catch (e) {
  2366. return false;
  2367. }
  2368. }(window.sessionStorage));
  2369. Tale.prototype.canBookmark = function() {
  2370. return this.canUndo() && !this.storysettings.lookup('hash') && (this.storysettings.lookup('bookmark',true) || !hasPushState);
  2371. };
  2372. History.prototype.init = function () {
  2373. var a = this;
  2374. if (!this.restore()) {
  2375. if (tale.has("StoryInit")) {
  2376. new Wikifier(insertElement(null, "span"), tale.get("StoryInit").text);
  2377. }
  2378. this.display("Start", null)
  2379. }
  2380. if (!hasPushState) {
  2381. this.hash = window.location.hash;
  2382. this.interval = window.setInterval(function () {
  2383. a.watchHash()
  2384. }, 250)
  2385. }
  2386. };
  2387. hasPushState && (History.prototype.pushState = function(replace, uri) {
  2388. window.history[replace ? "replaceState" : "pushState"]({ id: this.id, length: this.history.length }, document.title, uri);
  2389. });
  2390. History.prototype.display = function (title, source, type, callback) {
  2391. var i, e, q, bookmark, hash, c = tale.get(title), p = document.getElementById("passages");
  2392. if (c==null) {
  2393. return;
  2394. }
  2395. if (type != "back") {
  2396. this.saveVariables(c, source, callback);
  2397. hash = (tale.storysettings.lookup('hash') && this.save()) || "";
  2398. if (hasPushState && tale.canUndo()) {
  2399. try {
  2400. sessionStorage.setItem("Twine.History"+this.id, JSON.stringify(decompile(this.history)));
  2401. this.pushState(this.history.length <= 2 && window.history.state == "", hash);
  2402. } catch(e) {
  2403. alert("Your browser couldn't save the state of the " + tale.identity() +".\n"+
  2404. "You may continue playing, but it will no longer be possible to undo moves from here on in.");
  2405. tale.storysettings.undo="off";
  2406. }
  2407. }
  2408. }
  2409. this.hash = hash || this.save();
  2410. e = c.render();
  2411. if (type != "quietly") {
  2412. if (hasTransition) {
  2413. for(i = 0; i < p.childNodes.length; i += 1) {
  2414. q = p.childNodes[i];
  2415. q.classList.add("transition-out");
  2416. setTimeout((function(a) { return function () {
  2417. if(a.parentNode) a.parentNode.removeChild(a);
  2418. }}(q)), 1000);
  2419. }
  2420. e.classList.add("transition-in");
  2421. setTimeout(function () { e.classList.remove("transition-in"); }, 1);
  2422. e.style.visibility = "visible";
  2423. p.appendChild(e);
  2424. } else {
  2425. removeChildren(p);
  2426. p.appendChild(e);
  2427. fade(e, {
  2428. fade: "in"
  2429. })
  2430. }
  2431. }
  2432. else {
  2433. p.appendChild(e);
  2434. e.style.visibility = "visible"
  2435. }
  2436. tale.setPageElements();
  2437. if (tale.canUndo()) {
  2438. if (!hasPushState && type != "back") {
  2439. window.location.hash = this.hash;
  2440. } else if (tale.canBookmark()) {
  2441. bookmark = document.getElementById("bookmark");
  2442. bookmark && (bookmark.href = this.hash);
  2443. }
  2444. }
  2445. window.scroll(0, 0)
  2446. return e
  2447. };
  2448. History.prototype.watchHash = function () {
  2449. if (window.location.hash != this.hash) {
  2450. if (window.location.hash && (window.location.hash != "#")) {
  2451. this.history = [{
  2452. passage: null,
  2453. variables: {}
  2454. }];
  2455. removeChildren(document.getElementById("passages"));
  2456. if (!this.restore()) {
  2457. alert("The passage you had previously visited could not be found.")
  2458. }
  2459. } else {
  2460. window.location.reload()
  2461. }
  2462. this.hash = window.location.hash
  2463. }
  2464. };
  2465. History.prototype.loadLinkVars = function() {
  2466. for (var c in this.history[0].linkVars) {
  2467. this.history[0].variables[c] = clone(this.history[0].linkVars[c]);
  2468. }
  2469. };
  2470. Passage.prototype.render = function () {
  2471. var b = insertElement(null, 'div', 'passage' + this.title, 'passage');
  2472. b.style.visibility = 'hidden';
  2473. this.setTags(b);
  2474. this.setCSS();
  2475. insertElement(b, 'div', '', 'header');
  2476. var a = insertElement(b, 'div', '', 'body content');
  2477. for (var i in prerender) {
  2478. (typeof prerender[i] == "function") && prerender[i].call(this,a);
  2479. }
  2480. new Wikifier(a, this.processText());
  2481. insertElement(b, 'div', '', 'footer');
  2482. for (i in postrender) {
  2483. (typeof postrender[i] == "function") && postrender[i].call(this,a);
  2484. }
  2485. return b;
  2486. };
  2487. Passage.prototype.excerpt = function () {
  2488. var b = this.text.replace(/<<.*?>>/g, "");
  2489. b = b.replace(/!.*?\n/g, "");
  2490. b = b.replace(/[\[\]\/]/g, "");
  2491. var a = b.split("\n");
  2492. while (a.length && a[0].length == 0) a.shift();
  2493. var c = '';
  2494. if (a.length == 0 || a[0].length == 0) c = this.title;
  2495. else c = a[0].substr(0, 30) + '...';
  2496. return c;
  2497. };
  2498. Passage.transitionCache = "";
  2499. Passage.prototype.setCSS = function() {
  2500. var trans = false, text = "", tags = this.tags || [],
  2501. c = document.getElementById('tagCSS'),
  2502. c2 = document.getElementById('transitionCSS');
  2503. if (c && c.getAttribute('data-tags') != tags.join(' ')) {
  2504. tale.forEachStylesheet(tags, function(passage) {
  2505. if (~passage.tags.indexOf("transition")) {
  2506. if (!Passage.transitionCache && c2)
  2507. Passage.transitionCache = c2.innerHTML;
  2508. setTransitionCSS(passage.text);
  2509. trans = true;
  2510. }
  2511. else text += alterCSS(passage.text);
  2512. });
  2513. if (!trans && Passage.transitionCache && c2) {
  2514. setTransitionCSS(Passage.transitionCache);
  2515. trans = false;
  2516. Passage.transitionCache = "";
  2517. }
  2518. c.styleSheet ? (c.styleSheet.cssText = text) : (c.innerHTML = text);
  2519. c.setAttribute('data-tags', tags.join(' '));
  2520. }
  2521. };
  2522. var Interface = {
  2523. init: function () {
  2524. var snapback = document.getElementById("snapback"),
  2525. restart = document.getElementById("restart"),
  2526. bookmark = document.getElementById("bookmark");
  2527. main();
  2528. if (!tale) {
  2529. return;
  2530. }
  2531. if (snapback) {
  2532. if (!tale.lookup("tags", "bookmark").length) {
  2533. snapback.parentNode.removeChild(snapback);
  2534. } else addClickHandler(snapback, Interface.showSnapback);
  2535. }
  2536. if (bookmark && (!tale.canBookmark() || !hasPushState)) {
  2537. bookmark.parentNode.removeChild(bookmark);
  2538. }
  2539. restart && addClickHandler(restart, Interface.restart);
  2540. },
  2541. restart: function () {
  2542. if (confirm("Are you sure you want to restart this " + tale.identity() + "?")) {
  2543. state.restart()
  2544. }
  2545. },
  2546. showSnapback: function (a) {
  2547. Interface.hideAllMenus();
  2548. Interface.buildSnapback();
  2549. Interface.showMenu(a, document.getElementById("snapbackMenu"))
  2550. },
  2551. buildSnapback: function () {
  2552. var b, c = false,
  2553. menuelem = document.getElementById("snapbackMenu");
  2554. while (menuelem.hasChildNodes()) {
  2555. menuelem.removeChild(menuelem.firstChild)
  2556. }
  2557. for(var a = state.history.length - 1; a >= 0; a--) {
  2558. if(state.history[a].passage && state.history[a].passage.tags.indexOf("bookmark") != -1) {
  2559. b = document.createElement("div");
  2560. b.pos = a;
  2561. addClickHandler(b, function () {
  2562. return macros.back.onclick(true, this.pos);
  2563. });
  2564. b.innerHTML = state.history[a].passage.excerpt();
  2565. menuelem.appendChild(b);
  2566. c = true
  2567. }
  2568. }
  2569. b = null
  2570. if(!c) {
  2571. b = document.createElement("div");
  2572. b.innerHTML = "<i>No passages available</i>";
  2573. document.getElementById("snapbackMenu").appendChild(b)
  2574. }
  2575. },
  2576. hideAllMenus: function () {
  2577. document.getElementById("snapbackMenu").style.display = "none"
  2578. },
  2579. showMenu: function (b, a) {
  2580. if (!b) {
  2581. b = window.event
  2582. }
  2583. var c = {
  2584. x: 0,
  2585. y: 0
  2586. };
  2587. if (b.pageX || b.pageY) {
  2588. c.x = b.pageX;
  2589. c.y = b.pageY
  2590. } else {
  2591. if (b.clientX || b.clientY) {
  2592. c.x = b.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  2593. c.y = b.clientY + document.body.scrollTop + document.documentElement.scrollTop
  2594. }
  2595. }
  2596. a.style.top = c.y + "px";
  2597. a.style.left = c.x + "px";
  2598. a.style.display = "block";
  2599. addClickHandler(document, Interface.hideAllMenus);
  2600. b.cancelBubble = true;
  2601. if (b.stopPropagation) {
  2602. b.stopPropagation()
  2603. }
  2604. }
  2605. };
  2606. window.onload = Interface.init;
  2607. macros.back.onclick = function(back, steps) {
  2608. var title;
  2609. if (back) {
  2610. if (tale.canUndo()) {
  2611. window.history.go(-steps);
  2612. return;
  2613. }
  2614. while(steps-- >= 0 && state.history.length>1) {
  2615. title = state.history[0].passage.title;
  2616. state.history.shift();
  2617. }
  2618. state.loadLinkVars();
  2619. state.saveVariables(tale.get(title));
  2620. state.display(title, null, "back");
  2621. }
  2622. else {
  2623. state.display(state.history[steps].passage.title);
  2624. }
  2625. };
  2626. window.onpopstate = function(e) {
  2627. var title, hist, steps, i, s = e && e.state;
  2628. if (s && s.id && s.length != null) {
  2629. hist = recompile(JSON.parse(sessionStorage.getItem("Twine.History"+s.id)));
  2630. if (hist) {
  2631. steps = hist.length-s.length;
  2632. }
  2633. }
  2634. if (steps != null) {
  2635. state.history = hist;
  2636. // Shift the position of history to match how far back we've gone
  2637. while(steps-- >= 0 && state.history.length>1) {
  2638. title = state.history[0].passage.title;
  2639. state.history.shift();
  2640. }
  2641. state.loadLinkVars();
  2642. state.saveVariables(tale.get(title));
  2643. state.display(title, null, "back");
  2644. }
  2645. };
  2646. testplay = "";
  2647. }());
  2648. </script>
  2649. <script title="modules">
  2650. </script>
  2651. <style id="baseCSS">
  2652. /* Sidebar */
  2653. #sidebar {
  2654. position: fixed;
  2655. list-style: none;
  2656. width: 12em;
  2657. }
  2658. #sidebar #title, #sidebar #credits {
  2659. cursor: auto;
  2660. }
  2661. #sidebar #storySubtitle, #sidebar #storyMenu {
  2662. display: block;
  2663. }
  2664. #sidebar hr {
  2665. width: 5em;
  2666. margin-left: 8em;
  2667. height: 2px;
  2668. color: #c0c0c0
  2669. border: none;
  2670. background: #c0c0c0;
  2671. }
  2672. .menu {
  2673. position: absolute;
  2674. display: none;
  2675. z-index: 5;
  2676. }
  2677. /* Passages container */
  2678. #passages {
  2679. margin-left: 18.2em;
  2680. position:relative;
  2681. }
  2682. /* Links */
  2683. .passage a {
  2684. color: #4d6ad8;
  2685. }
  2686. a.internalLink, a.externalLink, a.back, a.return, [data-passage], .menu div {
  2687. cursor: pointer;
  2688. }
  2689. a.brokenLink {
  2690. background-color: red;
  2691. color: #000;
  2692. }
  2693. .marked {
  2694. background-color: #f66;
  2695. color: #000;
  2696. }
  2697. .marked[title] {
  2698. cursor: help;
  2699. }
  2700. .passage li[data-bullet] {
  2701. list-style-type: none;
  2702. }
  2703. .passage li[data-bullet]:before {
  2704. content: attr(data-bullet);
  2705. position: relative;
  2706. left: -1em;
  2707. }
  2708. #storeArea {
  2709. display: none;
  2710. }
  2711. #noscript {
  2712. margin-left: 18.2em;
  2713. font-size: 1.2em;
  2714. font-weight: bold;
  2715. }
  2716. /* HTML4 compatibility */
  2717. img {
  2718. vertical-align:bottom;
  2719. }
  2720. @media screen and (max-width: 640px) {
  2721. #sidebar {
  2722. position: static;
  2723. margin: 0 auto;
  2724. padding: 0;
  2725. }
  2726. body #sidebar li {
  2727. text-align: center;
  2728. }
  2729. #passages {
  2730. min-height: 100vh;
  2731. margin-left: 0em;
  2732. }
  2733. }
  2734. #loadingbar {
  2735. position:fixed;
  2736. top:0;
  2737. left:0;
  2738. border-top: solid #4d6ad8 6px;
  2739. transition: width 0.5s;
  2740. }
  2741. </style>
  2742. <style id="defaultCSS">
  2743. body {
  2744. background-color: #000;
  2745. color: #fff;
  2746. font-family: Verdana,sans-serif;
  2747. font-size: 62.5%;
  2748. margin: 4em 15% 5% 5em;
  2749. }
  2750. #sidebar {
  2751. left: 7.5em;
  2752. margin: 0;
  2753. padding: 0 1em 0 0;
  2754. font: bold 1.1em Verdana,sans-serif;
  2755. }
  2756. #sidebar ul {
  2757. padding: 0;
  2758. }
  2759. #sidebar li {
  2760. color: #333;
  2761. text-align: right;
  2762. background-repeat: no-repeat;
  2763. margin-bottom: 1em;
  2764. line-height: 1.4em;
  2765. list-style: none;
  2766. }
  2767. #sidebar li a {
  2768. color: #333;
  2769. text-decoration: none;
  2770. }
  2771. #sidebar li a:hover, #sidebar #title a:hover, #snapback:hover, #restart:hover {
  2772. color: #fff;
  2773. cursor: pointer;
  2774. text-decoration: none;
  2775. }
  2776. #sidebar #title {
  2777. font-size: 150%;
  2778. }
  2779. #sidebar #title, #sidebar #title:hover, #sidebar #title a {
  2780. color: #999;
  2781. }
  2782. #sidebar #storySubtitle {
  2783. font-size: 75%;
  2784. }
  2785. #storyAuthor {
  2786. font-size: 50%;
  2787. }
  2788. #sidebar #storyMenu {
  2789. line-height: 2.5em;
  2790. margin-bottom: .5em;
  2791. color: #999;
  2792. cursor: auto;
  2793. }
  2794. #sidebar #credits {
  2795. padding-top: 2em;
  2796. font-weight: normal;
  2797. font-size: 80%;
  2798. }
  2799. #sidebar #credits:hover {
  2800. color: #333;
  2801. }
  2802. #sidebar #credits a {
  2803. text-decoration: none;
  2804. }
  2805. #passages {
  2806. border-left: 1px solid #333;
  2807. padding-left: 1.5em;
  2808. }
  2809. .menu {
  2810. background-color: #343434;
  2811. color: #fff;
  2812. opacity: .9;
  2813. border: 1px solid #fff;
  2814. text-align: left;
  2815. font: 1.1em Verdana;
  2816. line-height: 2em;
  2817. }
  2818. .menu div {
  2819. padding: 0 .4em;
  2820. }
  2821. .menu div:hover {
  2822. cursor: pointer;
  2823. background-color: #fff;
  2824. color: #343434;
  2825. }
  2826. .passage {
  2827. font-size: 1.2em;
  2828. line-height: 175%;
  2829. margin-bottom: 2em;
  2830. text-align: left;
  2831. }
  2832. .passage a {
  2833. font-weight: bold;
  2834. text-decoration: none;
  2835. }
  2836. .passage a:hover {
  2837. color: #8ea6ff;
  2838. text-decoration: underline;
  2839. }
  2840. .content > ul {
  2841. padding-top: 1.3em;
  2842. }
  2843. .passage ul, .passage ol {
  2844. margin-left: .5em;
  2845. padding-left: 1.5em;
  2846. }
  2847. .passage li {
  2848. margin-right: 6em;
  2849. }
  2850. .passage table {
  2851. border-collapse: collapse;
  2852. font-size: 100%;
  2853. margin: .8em 1.0em;
  2854. }
  2855. .passage th,.passage td,.passage tr,.passage caption {
  2856. padding: 3px;
  2857. }
  2858. .passage hr {
  2859. height: 1px;
  2860. }
  2861. .passage center {
  2862. max-width:50%;
  2863. margin:auto;
  2864. }
  2865. .marked {
  2866. margin-right: 12px;
  2867. padding: 3px;
  2868. }
  2869. .disabled {
  2870. font-weight: bold;
  2871. color: #333;
  2872. }
  2873. @media screen and (max-width: 640px) {
  2874. body {
  2875. margin: 5%;
  2876. }
  2877. #sidebar {
  2878. width:100%;
  2879. margin: 0;
  2880. border-bottom: 1px solid #333;
  2881. }
  2882. #passages {
  2883. padding-top: 2em;
  2884. border-left: 0;
  2885. }
  2886. }
  2887. </style>
  2888. <style id="transitionCSS">
  2889. .transition-in {
  2890. opacity:0;
  2891. position:absolute;
  2892. }
  2893. .passage:not(.transition-out) {
  2894. transition: 1s;
  2895. -webkit-transition: 1s;
  2896. }
  2897. .transition-out {
  2898. opacity:0 !important;
  2899. position:absolute;
  2900. }
  2901. </style>
  2902. <style id="storyCSS"></style>
  2903. <style id="tagCSS"></style>
  2904. </head>
  2905. <body>
  2906. <div id="loadingbar"></div>
  2907. <ul id="sidebar">
  2908. <li id="title" class="storyElement"><span id="storyTitle" class="storyElement"></span><span id="storySubtitle" class="storyElement"></span><span id="titleSeparator"></span><span id="storyAuthor" class="storyElement"></span></li>
  2909. <li id="storyMenu" class="storyElement" style="display:none"></li>
  2910. <li>---</li>
  2911. <li><a href="javascript:;" id="restart">start over</a></li>
  2912. </ul>
  2913. <div id="snapbackMenu" class="menu"></div>
  2914. <div id="passages">
  2915. <noscript><div id="noscript">Please enable Javascript to play this story!</div></noscript>
  2916. <style>
  2917. #sidebar{display:none;}
  2918. </style>
  2919. </div>
  2920. <div id="storeArea" data-size="18" hidden><div tiddler="deck of cards" tags="thing" twine-position="0,0">&lt;&lt;set $link = &quot;[[&quot;+passage()+&quot;]]&quot;&gt;&gt;\n//examining...//\n''a deck of cards'', dog-eared and held together with a rubber band that's about to snap\n\n&lt;&lt;if $loc is &quot;edge of the cliff&quot;&gt;&gt;[[chuck it|goodbye][$inv.splice($inv.indexOf($link), 1)]]&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if previous() is &quot;inv&quot;&gt;&gt;[[close|inv]] &lt;&lt;else&gt;&gt;[[take it|previous()][$inv.push($link); $pile.splice($pile.indexOf($link), 1);$new++]] or [[leave it|previous()]]&lt;&lt;endif&gt;&gt;</div><div tiddler="lost keys" tags="thing" twine-position="0,0">&lt;&lt;set $link = &quot;[[&quot;+passage()+&quot;]]&quot;&gt;&gt;\n//examining...//\n''a set of lost keys'', worn and shiny\n\n&lt;&lt;if $loc is &quot;edge of the cliff&quot;&gt;&gt;[[chuck it|goodbye][$inv.splice($inv.indexOf($link), 1); $trash.push($link)]]&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if previous() is &quot;inv&quot;&gt;&gt;[[close|inv]] &lt;&lt;else&gt;&gt;[[take it|previous()][$inv.push($link); $pile.splice($pile.indexOf($link), 1);$new++]] or [[leave it|previous()]]&lt;&lt;endif&gt;&gt;</div><div tiddler="crumpled bill" tags="thing" twine-position="0,0">&lt;&lt;set $link = &quot;[[&quot;+passage()+&quot;]]&quot;&gt;&gt;\n//examining...//\n''some crumpled bills'', that might be enough to get you a cup of coffee\n\n&lt;&lt;if $loc is &quot;edge of the cliff&quot;&gt;&gt;[[chuck it|goodbye][$inv.splice($inv.indexOf($link), 1)]]&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if previous() is &quot;inv&quot;&gt;&gt;[[close|inv]] &lt;&lt;else&gt;&gt;[[take it|previous()][$inv.push($link); $pile.splice($pile.indexOf($link), 1);$new++]] or [[leave it|previous()]]&lt;&lt;endif&gt;&gt;</div><div tiddler="broken watch" tags="thing" twine-position="0,0">&lt;&lt;set $link = &quot;[[&quot;+passage()+&quot;]]&quot;&gt;&gt;\n//examining...//\n''a broken watch'', that doesn't really tell you anything\n\n&lt;&lt;if $loc is &quot;edge of the cliff&quot;&gt;&gt;[[chuck it|goodbye][$inv.splice($inv.indexOf($link), 1)]]&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if previous() is &quot;inv&quot;&gt;&gt;[[close|inv]] &lt;&lt;else&gt;&gt;[[take it|previous()][$inv.push($link); $pile.splice($pile.indexOf($link), 1);$new++]] or [[leave it|previous()]]&lt;&lt;endif&gt;&gt;</div><div tiddler="fruit" tags="thing" twine-position="0,0">&lt;&lt;set $link = &quot;[[&quot;+passage()+&quot;]]&quot;&gt;&gt;\n&lt;&lt;if $fruit == 0&gt;&gt;&lt;&lt;$inv.splice($inv.indexOf($link),1 )&gt;&gt;&lt;&lt;endif&gt;&gt;\n//examining...//\n''&lt;&lt;if $fruit == 1&gt;&gt;a piece&lt;&lt;else&gt;&gt;&lt;&lt;$fruit&gt;&gt; pieces&lt;&lt;endif&gt;&gt; of fruit'', from the tree that grows a little ways back from the cliff\n\n&lt;&lt;if $loc is &quot;edge of the cliff&quot;&gt;&gt;[[chuck some|goodbye][$fruit--]]&lt;&lt;endif&gt;&gt;\n\n[[close|inv]]</div><div tiddler="StoryTitle" twine-position="0,0">a sandbox</div><div tiddler="StorySubtitle" twine-position="0,0">//--[[about]]//</div><div tiddler="StoryAuthor" twine-position="0,0">[[@modgethanc|http://twitter.com/modgethanc]]</div><div tiddler="StoryMenu" twine-position="0,0">[[inventory|inv]]&lt;&lt;if $new &gt; 0&gt;&gt;(+&lt;&lt;$new&gt;&gt;)&lt;&lt;endif&gt;&gt;\n&lt;&lt;if not visited(&quot;under the tree&quot;)&gt;&gt;&lt;&lt;else&gt;&gt;location: \n[[$loc]]&lt;&lt;endif&gt;&gt;</div><div tiddler="StoryIncludes" twine-position="0,0">movement_test_items.tw</div><div tiddler="css" tags="stylesheet" twine-position="0,0">body, nav, #sidebar, #storyTitle, #storySubtitle {\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n cursor: default;\n}</div><div tiddler="about" twine-position="0,0">this is a sandbox for building a world model in twee. \n\nmain features are inventory, location tracking, object interactions.\n\nobjects have context-sensitive actions that depend on your location.\n\n[[close|previous()]]</div><div tiddler="inv" tags="container" twine-position="0,0">&lt;&lt;silently&gt;&gt;\n&lt;&lt;set $new = 0&gt;&gt;\n&lt;&lt;if ($fruit &lt; 1) and ($inv.indexOf(&quot;[[fruit]]&quot;) != -1)&gt;&gt;&lt;&lt;$inv.splice($inv.indexOf(&quot;[[fruit]]&quot;),1 )&gt;&gt;&lt;&lt;endif&gt;&gt;\n&lt;&lt;endsilently&gt;&gt;\n\n//inventory://\n&lt;&lt;if $inv.length == 0&gt;&gt;nothing.&lt;&lt;endif&gt;&gt;\s\n&lt;&lt;print $inv.join(&quot;, &quot;)&gt;&gt;\n\n[[close|$loc]]</div><div tiddler="Start" tags="place" twine-position="0,0">&lt;&lt;nobr&gt;&gt;\n&lt;&lt;set $inv = []&gt;&gt;\n&lt;&lt;set $loc = passage()&gt;&gt;\n&lt;&lt;set $pile = [&quot;[[deck of cards]]&quot;, &quot;[[lost keys]]&quot;, &quot;[[crumpled bill]]&quot;, &quot;[[broken watch]]&quot;]&gt;&gt;\n&lt;&lt;set $fruit = 0&gt;&gt;\n&lt;&lt;set $fruitRemaining = 8&gt;&gt;\n&lt;&lt;endnobr&gt;&gt;\nhey, you.\n\nlet's try something[[...|under the tree]]</div><div tiddler="under the tree" tags="place" twine-position="0,0">&lt;&lt;set $loc = passage()&gt;&gt;\n&lt;&lt;if $pile.length &gt; 0&gt;&gt;under the fruit tree, there is a [[pile of objects]]. &lt;&lt;else&gt;&gt;it's neat and tidy under the fruit tree.&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if $fruitRemaining &gt; 0&gt;&gt;&lt;&lt;set $item = &quot;[[fruit]]&quot;&gt;&gt;if you want, you can &lt;&lt;if $fruit == 0&gt;&gt;[[pick some fruit|passage()][$inv.push($item);$fruit++;$fruitRemaining--;$new++]].&lt;&lt;else&gt;&gt;[[pick some fruit|passage()][$fruit++;$fruitRemaining--;$new++]].&lt;&lt;endif&gt;&gt;&lt;&lt;else&gt;&gt;the fruit tree is bare.&lt;&lt;endif&gt;&gt;\n\nthese sorts of things might prepare you to go to the [[edge of the cliff]].</div><div tiddler="pile of objects" tags="container" twine-position="0,0">&lt;&lt;if $pile.length &gt; 0&gt;&gt; //examining...//\n\na pile of objects:\n&lt;&lt;set $item= &quot;[[deck of cards]]&quot;&gt;&gt;&lt;&lt;if $pile.indexOf($item) != -1&gt;&gt;* &lt;&lt;print $item&gt;&gt;&lt;&lt;endif&gt;&gt;\s\n&lt;&lt;set $item= &quot;[[lost keys]]&quot;&gt;&gt;&lt;&lt;if $pile.indexOf($item) != -1&gt;&gt;* &lt;&lt;print $item&gt;&gt;&lt;&lt;endif&gt;&gt;\s\n&lt;&lt;set $item= &quot;[[crumpled bill]]&quot;&gt;&gt;&lt;&lt;if $pile.indexOf($item) != -1&gt;&gt;* &lt;&lt;print $item&gt;&gt;&lt;&lt;endif&gt;&gt;\s\n&lt;&lt;set $item= &quot;[[broken watch]]&quot;&gt;&gt;&lt;&lt;if $pile.indexOf($item) != -1&gt;&gt;* &lt;&lt;print $item&gt;&gt;&lt;&lt;endif&gt;&gt;\s\n&lt;&lt;else&gt;&gt;//not much to do here...//&lt;&lt;endif&gt;&gt;\n[[close|$loc]]</div><div tiddler="edge of the cliff" tags="place" twine-position="0,0">&lt;&lt;set $trash = []&gt;&gt;\n&lt;&lt;set $loc = passage()&gt;&gt;\nthere's nothing of great interest at the edge of the cliff, so you might as well go back to standing [[under the tree]].\n\nif there's anything you want to throw away, though, this is a good place to [[do it|inv]].</div><div tiddler="goodbye" tags="messages" twine-position="0,0">&lt;&lt;if $inv.length &gt; 1&gt;&gt;out into the abyss it goes. hope you weren't planning on getting it back.&lt;&lt;else&gt;&gt;with a quiet sigh of relief, you pitch the last thing you're holding into the abyss.&lt;&lt;endif&gt;&gt;\n\n&lt;&lt;if (previous() == &quot;fruit&quot;) and ($fruit &gt; 0)&gt;&gt;[[...|fruit]]&lt;&lt;else&gt;&gt;[[...|inv]]&lt;&lt;endif&gt;&gt;</div></div></body></html>