JavaScript must be enabled to play.
Browser lacks capabilities required to play.
Upgrade or switch to another browser.
Loading…
<<ctp>> <<character "you">> <p>Why did you wear this scarf <<ctpLink "today">>?</p> <<ctpNext>> <<background "carriage">> <p>It's your best, but you knew you had to ride the mouse train, <<ctpLink "didn't you?">></p> <<ctpNext>> <<background "platform" "show" "sliding">> <p>You are so forgetful. You don't even remember where you are going.</p> <p>Also, you forgot your ticket …</p> <p>And the conductor looks so //<<link [[scary!|Conductor Appears]]>><</link>>//</p> <</ctp>> <!-- 42 -->
(function () { "use strict"; $(document).on(":passageinit", () => { CTP.Logs.forEach((_, id) => { if (!CTP.Repository.get(id)?.persist) CTP.Logs.delete(id); }); CTP.Repository.forEach(({ persist }, id) => { if (!persist) CTP.Repository.delete(id); }); }); window.CTP = class CTP { constructor(id, persist = false) { this.stack = []; this.clears = []; this.options = {}; if (!id?.trim()) throw new Error(`No ID specified!`); this.id = State.temporary.ctp = id; this.persist = persist; CTP.Repository.set(id, this); } static get Options() { return ['clear','id','next','t8n','persist','transition','wait','advance','back','redo','append']; } static get Repository() { if (!setup["@CTP/Repository"]) setup["@CTP/Repository"] = new Map(); return setup["@CTP/Repository"]; } static get Logs() { if (!variables()["@CTP/Logs"]) variables()["@CTP/Logs"] = new Map(); return variables()["@CTP/Logs"]; } get log() { if (!CTP.Logs.get(this.id)) CTP.Logs.set(this.id, { lastClear: -1, index: -1, seen: [] }); return CTP.Logs.get(this.id); } static getCTP(id) { return CTP.Repository.get(id); } static nextArgs(args) { const parsed = {}; const single = ['clear','t8n','transition','wait','redo','append']; for (let i = 0; i < args.length; i += 1) { if (single.includes(args[i])) { parsed[args[i]] = true; } else { parsed[args[i].replace(/[^a-zA-Z0-9_]/g,'')] = args[i+1]; i++; } } if (parsed.t8n) { parsed.transition = true; } if (parsed.append) { parsed.clear = false; } return parsed; } add(content, options = {}) { options = { ...this.options, ...options }; if (options.clear) this.clears.push(this.stack.length); this.stack.push({ options, content, index: this.stack.length, element: $() }); return this; } print(index) { const { content, options: iOpts } = this.stack[index]; const options = { ...this.options, ...iOpts }; const element = $(document.createElement(options.element || "span")) .addClass("--macro-ctp-hidden") .attr({ "data-macro-ctp-id": this.id, "data-macro-ctp-next-id": options.id, "data-macro-ctp-index": index, }) .on("update-internal.macro-ctp", (event, firstTime) => { if ($(event.target).is(element)) { if (index === this.log.index) { if (firstTime || options.redo) { if (options.redo) element.empty(); if (typeof content === "string") element.wiki(content); else element.append(content); element.addClass(options.transition ? "--macro-ctp-t8n" : ""); } if (options.back && !options.wait && index > 0) { element.append($(`<button>${options.back}</button>`) .addClass("ctp-auto-button") .ariaClick(() => { this.back(); })); } if (options.advance && !options.wait && index !== (this.stack.length -1)) { element.append($(`<button>${options.advance}</button>`) .addClass("ctp-auto-button") .ariaClick(() => { this.advance(); })); } element.removeClass("--macro-ctp-hidden"); } else { if (this.log.seen.includes(index)) element.removeClass("--macro-ctp-t8n"); element.toggleClass("--macro-ctp-hidden", index > this.log.index || index < this.log.lastClear); element.toggleClass("--macro-ctp-older", index < this.log.index); } } }); this.stack[index].element = element; return element; } output() { const wrapper = document.createDocumentFragment(); for (let i = 0; i < this.stack.length; i++) { this.print(i).appendTo(wrapper); } return wrapper; } advance() { if (this.stack[this.log.index] && this.stack[this.log.index].options.next) { return this.goto(this.stack[this.log.index].options.next); } if (this.log.index < this.stack.length - 1) { this.log.index++; const firstTime = !this.log.seen.includes(this.log.index); this.log.seen.pushUnique(this.log.index); // = Math.max(this.log.seen, this.log.index); this.log.lastClear = this.clears.slice().reverse().find(el => el <= this.log.index) ?? -1; $(document).trigger("update.macro-ctp", ["advance", this.id, this.log.index]); this.stack.forEach(({ element }) => element.trigger("update-internal.macro-ctp", [firstTime, "advance", this.id, this.log.index])); } return this; } goto(id) { const index = this.stack.findIndex((item) => item.options.id == id); if (index) { console.log("going to index",index); this.log.index = index; const firstTime = !this.log.seen.includes(this.log.index); this.log.seen.pushUnique(this.log.index); // = Math.max(this.log.seen, this.log.index); this.log.lastClear = this.clears.slice().reverse().find(el => el <= this.log.index) ?? -1; $(document).trigger("update.macro-ctp", ["goto", this.id, this.log.index]); this.stack.forEach(({ element }) => element.trigger("update-internal.macro-ctp", [firstTime, "goto", this.id, this.log.index])); } return this; } back() { if (this.log.index > 0) { this.log.index--; this.log.lastClear = this.clears.slice().reverse().find(el => el <= this.log.index) ?? -1; const firstTime = !this.log.seen.includes(this.log.index); this.log.seen.pushUnique(this.log.index); // = Math.max(this.log.seen, this.log.index); $(document).trigger("update.macro-ctp", ["back", this.id, this.log.index]); this.stack.forEach(({ element }) => element.trigger("update-internal.macro-ctp", [firstTime, "back", this.id, this.log.index])); } return this; } } Macro.add("ctp", { tags: ["ctpNext"], handler() { if (!setup["@CTP/Options"]) setup["@CTP/Options"] = {}; const id = CTP.Options.includes(this.args[0]) ? "main" : (this.args[0] ?? "main"); const persist = this.args.slice(1).includes("persist"); const ctp = new CTP(id, persist); const _passage = passage(); this.payload.forEach(({ args, name, contents }) => { const options = CTP.nextArgs(args); if (name === "ctp") ctp.options = { ...setup["@CTP/Options"], ...options }; if (contents.trim().length) ctp.add(contents, options); }); console.log(ctp); $(this.output).append(ctp.output()); $(document).one(":passagedisplay", () => { if (_passage === passage()) { const i = Math.max(ctp.log.index, 0); ctp.log.index = -1; ctp.log.seen = []; while (ctp.log.index < i) ctp.advance(); } }); } }); Macro.add("ctpAdvance", { handler() { const id = this.args.length == 1 ? this.args[0] : "main"; if (id) { const ctp = CTP.getCTP(id); if (ctp) ctp.advance(); else throw new Error(`No CTP with ID '${id}' found!`); } else throw new Error(`No ID specified!`); } }); Macro.add("ctpGoto", { handler() { const id = this.args.length == 2 ? this.args[1] : "main"; if (id) { const ctp = CTP.getCTP(id); if (ctp) ctp.goto(this.args[0]); else throw new Error(`No CTP with ID '${id}' found!`); } else throw new Error(`No ID specified!`); } }); Macro.add("ctpLink", { handler() { const text = this.args[0]; const target = this.args[1] ?? "ctpAdvance"; const id = this.args.length == 3 ? this.args[2] : "main"; const $link = $("<a class='ctp-internal-link'>"); $link.ariaClick({ one : false }, this.createShadowWrapper( () => { if (id) { const ctp = CTP.getCTP(id); if (ctp) { if (target == "ctpAdvance") { ctp.advance(); } else { ctp.goto(target); } } else throw new Error(`No CTP with ID '${id}' found!`); } else throw new Error(`No ID specified!`); } )).html(text); $link.appendTo(this.output); } }); Macro.add("ctpBack", { handler() { const id = this.args.length == 1 ? this.args[0] : "main"; if (id) { const ctp = CTP.getCTP(id); if (ctp) ctp.back(); else throw new Error(`No CTP with ID '${id}' found!`); } else throw new Error(`No ID specified!`); } }); Macro.add("ctpSetNext", { handler() { const id = this.args.length == 2 ? this.args[1] : "main"; if (id) { const ctp = CTP.getCTP(id); if (ctp) { ctp.stack[ctp.log.index].options.next = this.args[0]; } else { throw new Error(`No CTP with ID '${id}' found!`); } } else throw new Error(`No ID specified!`); } }); })();
<!-- <<button "Restart">><<run UI.restart()>><</button>> -->
<<removeclass "#platform" "sliding">> <<background "carriage" "show" "open">> <<timed 0.3s>><<background "platform">><</timed>> <<character "you" hide>> <<station true>> <p> <<if State.passages[State.passages.length -2] == "Platform" || State.passages[State.passages.length -2] == "Conductor">> The train prepares to pull out. <<else>> <<if visited() == 1>> You hide until the conductor moves on, at which point the <<else>> The<</if>> train pulls into $station<<arrive>> <</if>></p> <<if Story.has($station)>> <<include $station>> <<else>> <<= setup.station_descriptions[$station] ?? "">> <<if !$ticket && $alighted.length && random(1,5) <= $conductor>> <<set _conductor = true>> <<character "scary">> <<if $current_conductor>> <p>Is … is he //watching// you?</p> <<else>> <p>You are just considering what to do, when the conductor appears!</p> <</if>> <<set $conductor = 1>> <<set $current_conductor = true>> <<else>> <<if !$ticket && $alighted.length>> <<set $conductor ++>> <</if>> <<set _conductor = false>> <<set $current_conductor = false>> <</if>> <<nextStation>> <</if>>
<<widget end>> <div class="end"><<link "END">> <<run Dialog.setup("Credits"); Dialog.wiki('<<include "credits">>'); Dialog.open();>> <</link>></div> <</widget>> <<widget background>> <<set _hide = _args[1] ?? "show">> <<if _hide == "hide">> <<addclass `'#' + _args[0]` "hidden">> <<else>> <<removeclass `'#' + _args[0]` "hidden">> <</if>> <<if _args[2]>> <<addclass `'#' + _args[0]` _args[2]>> <</if>> <</widget>> <<widget station>> <<removeclass "#sign" "next">> <<replace "#this_station">>$station<</replace>> <<replace "#platform-sign">>$station<</replace>> <<if _args[0]>> <<replace "#next_station">><<=setup.nextStation()>><</replace>> <<addclass "#sign" "scroll">> <<else>> <<removeclass "#sign" "scroll">> <</if>> <</widget>> <<widget nextSign>> <<removeclass "#sign" "scroll">> <<addclass "#sign" "next">> <<replace "#this_station">>Placeholder<</replace>> <<replace "#next_station">>_args[0]<</replace>> <</widget>> <<widget sign>> <<replace "#this_station">>_args[0]<</replace>> <<replace "#platform-sign">>_args[0]<</replace>> <</widget>> <<widget "nextStation">> <<set _dest = setup.nextStation()>> <ul> <<if !$ticket && _conductor>> <li><span class="fake-link"><<linkappend "Approach the Conductor">> … <<linkappend "are you sure??">> … <<link "really sure?" "Conductor">><</link>> <</linkappend>> <</linkappend>> </span></li><</if>> <li><<link "Alight here" "Platform">> <<set $alighted.pushUnique($station)>> <</link>></li> <li><<link "Stay on">> <<goToNextStation>> <</link>></li> </ul> <</widget>> <<widget "arrive">><<print either( ".", " with a squeal of brakes.", ". Half the carriage seems to scurry off.", " and a half-dozen mice squeeze onboard.", " and judders to a stop.", ". When the doors open, the platform is empty.", " and one mouse gets off." )>><</widget>>
<<character "scary">> <<character "you" hide>> <<background "carriage">> <<timed 0.3s>><<background "platform" "show" "sliding">><</timed>> <<nextSign "Soggy Bottom">> <p>"All tickets! All tickets from Honeydrop!"</p> <ul> <li><span class="fake-link"><<linkappend "Fess up">> … <<linkappend "What? Really?">> … are you [[insane??|Conductor]] <</linkappend>> <</linkappend>></span></li> <li><<link [[Hide|Track]]>> <<character "scary" hide>> <</link>></li> </ul>
<<preload `setup.images + 'platform.png'` `setup.images + 'platform_door.png'` `setup.images + 'memory_palace.png'` `setup.images + 'train.png'` `setup.images + 'train_open.png'` `setup.images + 'conductor.png'` `setup.images + 'scarf.png'` `setup.images + 'scary.png'` `setup.images + 'ticket.png'`>> <<set setup.stations = [ "Soggy Bottom", "Cinderblock", "Cheesehouse", "Memory Palace", "Woolwitch", "Bathhouse", "Honeydrop" ]>> <<set $station = "Soggy Bottom">> <<set $ticket = false>> <<set $alighted = []>> <<set $conductor = 1>> <<set $current_conductor = false>> <<set setup.station_descriptions = { "Soggy Bottom": "<p>You peer through the windows, it looks wet out there — unsurprisingly.</p>", "Cinderblock": "<p>It's very red outside. You can't think why you'd want to get off here.</p>", "Cheesehouse": "<p>The scent of sharp chedder fills the air. Is this where you were going?</p>", "Woolwitch": "<p>On the platform is a lot of wool, naturally. Did you come here for another scarf?</p>", "Bathhouse": "<p>The train windows steam up instantly.</p>", "Honeydrop": "<p>\"Get your honey here!\" Is this where you were going?</p>" }>> <<set setup.platform_descriptions = { "Soggy Bottom": "<p>The platform is covered in pools of water.</p>", "Cinderblock": "<p>Everything smells of ash around here.</p>", "Cheesehouse": "<p>\"Cheddar! Cheddar two pennies a pawful!\" If only you'd remembered money …</p>", "Woolwitch": "<p>The platform is tangled up with wool, which makes it hard to move.</p>", "Bathhouse": "<p>You can't see <i>anything</i> for the steam!</p>", "Honeydrop": "<p>A bee buzzes past, bumping into the walls.</p>" }>>
<div id="story"> <div id="picture"> <div id="platform" class="background hidden"> <div id="platform-image"></div> <div id="platform-cover"></div> <div id="platform-sign"></div> </div> <div id="carriage" class="background hidden"> <div id="sign"> <div id="content"> <div id="this"> <div id="this_title">This Station</div> <div id="this_station"></div> </div> <div id="next"> <div id="next_title">Next Station</div> <div id="next_station"></div> </div> </div> </div> </div> <div id="you" class="character hidden"></div> <div id="scary" class="character hidden"></div> <div id="conductor" class="character hidden"></div> </div> <div id="passages"></div> <div id="ticket-holder" data-passage="ticket"></div> </div>
<p>Of course! A memory palace! Surely that's the best place to remember what you wanted to do!</p> <<if !$ticket>> <<ctp>> <<character "scary">> <<set $conductor = 0>> <p>You are about to leap off the train, when the ominous shape of the conductor appears at the <<ctpLink "door">>!</p> <<ctpNext>> <p>//<<ctpLink "Eeeep!">>//</p> <<ctpNext>> <p>You hide under your seat until the conductor goes <<link "away">> <<goToNextStation>> <</link>>.</p> <</ctp>> <<else>> <<nextStation>> <</if>>
<<ctp>> <<background "carriage">> <<timed 0.3s>><<background "platform">><</timed>> <<character "scary">> <<character "you" grey>> <p>"Excuse me … I don't have a ti— <<ctpLink "ticket …">>"</p> <<ctpNext>> <<character "scary" hide>> <<character "conductor">> <p>"Don't worry, it's <<ctpLink "free ticket day">>!"</p> <<ctpNext>> <<character "conductor" grey>> <<character "you">> <p>Oh! So it is!</p> <ul> <li><<link [[Relax|Track]]>> <<set $ticket = true>> <<character "conductor" hide>> <<character "you" hide>> <</link>></li> </ul> <</ctp>>
<<background "platform">> <<background "carriage" "hide">> <<character "scary" "hide">> <<character "you">> <<station>> <p>You alight at $station.</p> <<if Story.has($station + " - platform")>> <<include `$station + " - platform"`>> <<else>> <<= setup.platform_descriptions[$station] ?? "">> <<if !$ticket>> <p>You look at the exit, and realise you can't leave without a ticket!</p> <</if>> <ul> <<if $ticket>> <li>[[Leave the station|Leave]]</li> <</if>> <li>[[Reboard the train|Track]]</li> </ul> <</if>>
<<background "platform" "hide">> <<character "you">> <<station true>> <<set _phrase = `You leave at $station`>> <<if $alighted.includes("Memory Palace") && $station == "Woolwitch">> <<ctp>> <p>_phrase.</p> <p>You spend the day watching the witch lengthen your scarf! Job well <<ctpLink "done!">></p> <<ctpNext>> <p>Afterwards, it's even <<ctpLink "better">> than before!</p> <<ctpNext>> <p>Maybe you should ride the train again … if you remember.</p> <<end>> <</ctp>> <<else>> <<switch $station>> <<case "Soggy Bottom">> <p>_phrase, it's pretty wet, and not that pretty.</p> <<case "Cinderblock">> <p>_phrase.</p> <p>You spend the day, and get very dirty.</p> <<case "Cheesehouse">> <p>_phrase.</p> <p>If you had pennies, this would be heaven. You //do// get free samples though.</p> <<case "Woolwitch">> <p>_phrase.</p> <p>You don't have an appointment to see the witch (do you?), so you just browse.</p> <<case "Bathhouse">> <p>_phrase.</p> <p>The steam makes your fur poof.</p> <<case "Honeydrop">> <p>_phrase.</p> <p>You get menaced by bees. //BEES!//</p> <</switch>> <p>You don't think you meant to come here. You might have to take the train again tomorrow.</p> <<end>> <</if>>
<ul> <li>Testing — Victoria, SjoerdHekking, SandroWalach</lI> <li><code><<preload>></code> macro — by Chapel</li> <li><code><<CTP>></code> — by Cyrus Firheir</li> </ul> <div id="buttons"><<button "RESTART">><<run Engine.restart()>><</button>></div>
<<ctp>> <p>The walls of Memory Palace are covered in <<ctpLink "notes">>.</p> <<ctpNext>> <p>Oh! This one is <<ctpLink "yours">>.</p> <<ctpNext>> <p>It says: "COULD BE <<ctpLink "LONGER">>."</p> <<ctpNext>> <p>Of course! You have an appointment with the wool-witch to get your scarf lengthened!</p> <ul> <li>[[Reboard|Track]]</li> </ul> <</ctp>>
// preload.min.js, for SugarCube 2, by Chapel // v1.0.0, 2022-07-21, 3bdbdfbe5ae47a46e4f4e52766d78701939ae9a6 ;!function(){"use strict";function t(t,r){var e=0,o=t.length;if(0===o)throw new Error("No URLs to preload!");t.forEach((function(t){!function(t,r){var e=new Image;e.onload=r,e.onerror=r,e.src=t}(t,(function(){++e===o&&r()}))}))}function r(){return State.length<=0}function e(){var e=[].slice.call(arguments).flatten(),o=!!r()&&LoadScreen.lock();return t(e,(function(){o&&LoadScreen.unlock(o)})),o}setup.preload=e,setup.preload.force=!1,Macro.add("preload",{handler:function(){if(!r()&&!setup.preload.force)return this.error("Attempting to preload images outside of `StoryInit` or similar can cause performance issues. Set `setup.preload.force` to `true` if you want to do it anyway.");e(this.args.flatten().filter((function(t){return"string"==typeof t})))}})}(); // end preload.min.js
<<if $ticket>> <div id="ticket"></div> <</if>>