### Sugarcube Newmeter Macro Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/meter-macros.md Examples demonstrating the setup of various meters using the <> macro, including health bars, experience bars, and timers. ```sugarcube <><><><> ``` ```sugarcube <><><> <> ``` ```sugarcube <><> <> ``` -------------------------------- ### First Macro Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/first-macro.md Demonstrates various ways to use the <>, <>, and <> macros to control content display based on passage visit history. Includes examples for first visits, subsequent visits, and multiple conditional displays. ```sugarcube <>Show only on first visit.<> ``` ```sugarcube <><>Show me on every visit except the first.<> ``` ```sugarcube <>\n\tFirst visit text.\n<>\n\tSecond visit text.\n<>\n\tThird visit text.\n<> ``` ```sugarcube <>\n\tFirst visit text.\n<>\n\tSecond visit text.\n<>\n\tThird visit and subsequent visits text.\n<> ``` -------------------------------- ### SugarCube 2 Installation Guide Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/README.md Instructions for installing custom SugarCube 2 macros across different Twine versions (Twine 2, Twine 1, Twee2, Tweego) and IDEs. It details how to include JavaScript and CSS files, and mentions configuration for VSCode extensions. ```javascript // For Twine 2 users: copy and paste the JavaScript code into your story JavaScript area. // Some macros may also require CSS code, which goes in your story stylesheet. ``` ```javascript // For Twine 1, you'll need to copy and paste the JavaScript portions into a script-tagged passage, // and the CSS portions (if any) into a stylesheet-tagged passage. ``` ```javascript // For Twee2, refer to its documentation for how to create the tagged passages you need. ``` ```javascript // For Tweego, simply place the code in .js and .css files as appropriate and include them // in your directory / command line options just as you would any other code files. ``` -------------------------------- ### Mouseover Macro Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/mouseover-macro.md Provides examples of using the mouseover macro for creating tooltips, playing sounds on hover, and navigating on mouseover. ```twine /% create a link with a "tooltip" %/ <> <><> <> <>: Do the thing.> <> <><> <> @@#tooltip;@@ /% play a sound on mouseover %/ <>Spooooooooky<><> /% navigate on mouseover %/ Watch the <>[[pit!|fell in a pit]]<><><> ``` -------------------------------- ### Troubleshooting SugarCube 2 Macro Installation Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/README.md Provides solutions for common installation issues with custom SugarCube 2 macros, such as using the 'raw' GitHub button for code and using text editors instead of word processors. ```javascript <> ``` ```javascript <> ``` -------------------------------- ### SugarCube 2 Macro Options Configuration Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/v1-docs.md Demonstrates how to configure options for SugarCube 2 macros, such as setting story variable names and tags for systems like the Cycles System. This is typically done via a setup object. ```javascript setup.cycSystem.options = { storyVar : 'cycles', resetTag : 'resetcycles', pauseTag : 'pausecycles', menuTag : 'menupause', runNew : true, tryGlobal : true }; ``` -------------------------------- ### Instance Method Usage Example Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/meter-macros.md Shows how to access and use instance methods like val() on a meter object obtained via Meter.get() or the return value of Meter.add(). ```javascript var meter = Meter.add('myMeter', {}, 0.6); meter.val(); // 0.6 Meter.get('myMeter').val(); // 0.6 ``` -------------------------------- ### Dialog Management Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Handles dialog opening, closing, and resizing events. It also includes a deprecated setup function. ```javascript Dialog = function() { var Dialog = { close: function() { triggerEvent(":dialogclose", document.body); triggerEvent(":dialogclosed", document.body); }, resize: { value: function(options) { return onResize("object" === typeof options ? options.top : undefined); } }, wiki: { value: wiki }, wikiPassage: { value: function(name) { return wiki(Story.get(name).processText()); } }, setup: { value: function(title, classNames) { return console.warn("[DEPRECATED] Dialog.setup() is deprecated."), create(title, classNames), getBody(); } } }; return Dialog; }(); ``` -------------------------------- ### AJAX Helper Functions (get, post, getJSON, getScript) Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Provides convenient wrapper functions for common AJAX request types. These functions simplify making GET, POST, JSONP, and script requests by abstracting away much of the configuration details. ```javascript getJSON:function(e,t,n){return ce.get(e,t,n,"json")},getScript:function(e,t){return ce.get(e,void 0,t,"script")}}),ce.each(["get","post"],function(e,i){ce[i]=function(e,t,n,r){return v(t)&&(r=r||n,n=t,t=void 0),ce.ajax(ce.extend({url:e,type:i,dataType:r,data:t,success:n},ce.isPlainObject(e)&&e))}}),ce.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),ce._evalUrl=function(e,t,n){return ce.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){ce.globalEval(e,t,n)}})} ``` -------------------------------- ### Resetswap Macro Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/swap-macro-set.md Demonstrates how to use the `<>` macro to create buttons or links that reset all swappable elements on the page to their initial state. ```sugarcube <> <> ``` -------------------------------- ### Meter.add() Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/meter-macros.md Demonstrates how to use the Meter.add() method to create new meter instances with different configurations. ```javascript Meter.add('healthBar', {}, 1); Meter.add('timer', { animate : 10000 }, 0); Meter.add('xpBar'); ``` -------------------------------- ### SugarCube UI Macro Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/ui-macro.md Demonstrates practical usage of the SugarCube UI macro for creating custom buttons, managing UI bar visibility, and updating displayed information. ```sugarcube :: PassageHeader <> <> :: some passage You took 15 damage! <> <> ``` -------------------------------- ### Sugarcube Continue Macro (`<>`) Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/continue-macro.md Demonstrates various uses of the `<>` macro, including basic continuation, triggering on keypress, appending content, and nesting for sequential interaction. ```sugarcube <><><> ``` ```sugarcube <>The ghost is behind you!<> ``` ```sugarcube <> <> <> However, all but King Arthur fell in the ensuing battle. <> <> <> King Arthur swore revenge, and set out from his castle! <> <> <> <> ``` -------------------------------- ### SugarCube 2 Initialization and Event Handling Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Handles the initial setup of the SugarCube 2 engine, including DOM manipulation, event triggering, and state management. It also manages the saving and restoring of game states. ```javascript function(){if(null==Config.passages.start)throw new Error("starting passage not selected");if(!Story.has(Config.passages.start))throw new Error('starting passage ("'.concat(Config.passages.start,'") not found'));if(_state=States.Idle,document.documentElement.focus(),State.restore())engineShow();else{var autoloadType=_typeof(Config.saves._internal) ``` -------------------------------- ### <> Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/cycles-system.md Illustrates various ways to use the <> macro, including setting periods, increments, suspending cycles, and defining phases across multiple <> tags. ```sugarcube /* create a cycle for time that changes phases every 4 turns */ <> <> <> ``` ```sugarcube /* create a cycle for a turn-based game */ <> <> <> <> ``` ```sugarcube /* another example, with period and increment set to give an effective period of 4 turns */ <> <> <> ``` ```sugarcube /* you can use multiple child tags and multiple phaseNames per child tag, the order will still be preserved */ <> <> <> <> <> ``` -------------------------------- ### Swap Macro Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/swap-macro-set.md Demonstrates various ways to use the `<>` macro, including simple text swapping, using the `<>` block with conditional logic and DOM manipulation, and creating interactive puzzles. ```sugarcube /% simple example %/ He picked up the <>picture<> the victim kept on the nightstand, next to the <>alarm clock<>. The <>pillow<> on the bed was cocked at an odd angle--the <>blanket<> was on the floor. /% example using <> %/ <> I <>own<> <> <> <> <> <> <>_catsPred<> <> cats. _catsPred I <>love<> dogs. I <>hate<> birds, though. /% a puzzle %/ <> Enter the code: <>1<> <> <> <> <> <> <> <>2<> <> <> <> <> <> <> <>3<> <> <> <> <> <> <> @@#not-right;@@ <> <> ``` -------------------------------- ### JavaScript Meter API - Overview Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/meter-macros.md The Meter API provides JavaScript methods to interact with meters, whether created via the API or the <> macro. It is accessible on the `setup` and `window` objects. ```javascript Meter.create(name, options) Meter.get(name) Meter.destroy(name) ``` -------------------------------- ### Configuring Default Link Text for Message Macro Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/message-macro.md Demonstrates how to configure the default link text for the 'message' macro using SugarCube's setup object in JavaScript. ```javascript setup.messageMacro.default = 'YAY'; // <> macros without linkText arguments will display `YAY`: ``` -------------------------------- ### meter.val() Usage Examples Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/meter-macros.md Provides examples of using the meter.val() method to get the current value and to set a new value, which also triggers animation. ```javascript Meter.add('blah', {}, 0.4); Meter.get('blah').val(); // 0.4 Meter.get('blah').val(0.1); // sets the meter to 0.1, and returns 0.1 ``` -------------------------------- ### Sugarcube 2 setup.preload() Function Usage Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/preload.md Shows how to use the `setup.preload()` JavaScript function in Sugarcube 2 to preload image resources, similar to the `<>` macro. ```javascript setup.preload('assets/lisa/jpg', 'assets/bob.jgp'); // you can also provide the arguments as an array if you prefer setup.preload(['assets/lisa/jpg', 'assets/bob.jgp']) ``` -------------------------------- ### _getCodePointStartAndEnd - Get Unicode Character Start and End Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html A utility function to determine the start and end positions of a Unicode character at a given position in a string. It correctly handles surrogate pairs for characters outside the Basic Multilingual Plane. ```javascript function _getCodePointStartAndEnd(str, pos) { var code = str.charCodeAt(pos); if (Number.isNaN(code)) return { char: "", start: -1, end: -1 }; if (code < 55296 || code > 57343) return { char: str.charAt(pos), start: pos, end: pos }; if (code >= 55296 && code <= 56319) { var nextPos = pos + 1; if (nextPos >= str.length) throw new Error("high surrogate without trailing low surrogate"); var nextCode = str.charCodeAt(nextPos); if (nextCode < 56320 || nextCode > 57343) throw new Error("high surrogate without trailing low surrogate"); return { char: str.charAt(pos) + str.charAt(nextPos), start: pos, end: nextPos } } if (0 === pos) throw new Error("low surrogate without leading high surrogate"); var prevPos = pos - 1, prevCode = str.charCodeAt(prevPos); if (prevCode < 55296 || prevCode > 56319) throw new Error("low surrogate without leading high surrogate"); return { char: str.charAt(prevPos) + str.charAt(pos), start: prevPos, end: pos } } ``` -------------------------------- ### SugarCube Initialization and Core Components Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html This snippet outlines the initialization process of SugarCube, including locking the load screen, initializing the engine, setting up storage (session and persistent), and the core structure of the SugarCube object exposed to the window. ```javascript TempState = {}, session = null, settings = Setting.create(), setup = {}, storage = null, macros = {}, postdisplay = {}, postrender = {}, predisplay = {}, prehistory = {}, prerender = {}; Object.defineProperty(window, "SugarCube", { value: Object.seal(Object.assign(Object.create(null), { Browser: Browser, Config: Config, Dialog: Dialog, Engine: Engine, Fullscreen: Fullscreen, Has: Has, L10n: L10n, Macro: Macro, Passage: Passage, Save: Save, Scripting: Scripting, Setting: Setting, SimpleAudio: SimpleAudio, State: State, Story: Story, UI: UI, UIBar: UIBar, DebugBar: DebugBar, Util: Util, Visibility: Visibility, Wikifier: Wikifier, session: session, settings: settings, setup: setup, storage: storage, version: version })) }); jQuery((function() { var lockId = LoadScreen.lock(); LoadScreen.init(); document.normalize && document.normalize(); new Promise((function(resolve) { Story.init(); try { SugarCube.storage = storage = SimpleStore.create(Story.id, !0); SugarCube.session = session = SimpleStore.create(Story.id, !1) } catch (ex) { throw new Error(L10n.get("warningNoStorage")) } Dialog.in ``` -------------------------------- ### Defining Meters with SugarCube's newmeter Macro Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Provides an example of defining custom meters (health bars, timers, progress bars) using the `newmeter` macro and its child tags like `colors`, `sizing`, `animation`, and `label`. Meters must be defined before the story starts. ```sugarcube <> <> <> <> <> <> <> <> <><> ``` -------------------------------- ### Wikifier Class Initialization and Core Functionality Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Demonstrates the initialization of the Wikifier class, including setting source text, options, and handling output destinations. It also covers the core `subWikify` method for processing text based on defined parsers and terminators, and the `outputText` method for appending text nodes to the DOM. ```javascript Object.defineProperties(this, { source: { value: String(source) }, options: { writable: !0, value: Object.assign({ profile: "all" }, options) }, nextMatch: { writable: !0, value: 0 }, output: { writable: !0, value: null }, _rawArgs: { writable: !0, value: "" } }); this.output = null == destination ? document.createDocumentFragment() : destination instanceof jQuery ? destination[0] : destination; try { ++_callDepth; this.subWikify(this.output); 1 === _callDepth && (Object.hasOwn(this.options, "cleanup") && null != this.options.cleanup ? this.options.cleanup : Config.cleanupWikifierOutput) && convertBreaks(this.output); } finally { --_callDepth; } return _createClass(Wikifier, [{ key: "subWikify", value: function(output, terminator, options) { var newOptions, oldOptions, oldOutput = this.output; this.output = output; Wikifier.Option.length > 0 && (newOptions = Object.assign(newOptions || {}, Wikifier.Option.options)); null !== options && "object" === _typeof(options) && (newOptions = Object.assign(newOptions || {}, options)); newOptions && (oldOptions = this.options, this.options = Object.assign({}, this.options, newOptions)); var terminatorMatch, parserMatch, parsersProfile = Wikifier.Parser.Profile.get(this.options.profile), terminatorRegExp = terminator ? new RegExp("(?:" + terminator + ")", this.options.ignoreTerminatorCase ? "gim" : "gm") : null; do { if (parsersProfile.parserRegExp.lastIndex = this.nextMatch, terminatorRegExp && (terminatorRegExp.lastIndex = this.nextMatch), parserMatch = parsersProfile.parserRegExp.exec(this.source), (terminatorMatch = terminatorRegExp ? terminatorRegExp.exec(this.source) : null) && (!parserMatch || terminatorMatch.index <= parserMatch.index)) return terminatorMatch.index > this.nextMatch && this.outputText(this.output, this.nextMatch, terminatorMatch.index), this.matchStart = terminatorMatch.index, this.matchLength = terminatorMatch[0].length, this.matchText = terminatorMatch[0], this.nextMatch = terminatorRegExp.lastIndex, this.output = oldOutput, void (oldOptions && (this.options = oldOptions)); if (parserMatch) { parserMatch.index > this.nextMatch && this.outputText(this.output, this.nextMatch, parserMatch.index), this.matchStart = parserMatch.index, this.matchLength = parserMatch[0].length, this.matchText = parserMatch[0], this.nextMatch = parsersProfile.parserRegExp.lastIndex; for (var matchingParser = void 0, i = 1, iend = parserMatch.length; i < iend; ++i) if (parserMatch[i]) { matchingParser = i - 1; break; } parsersProfile.parsers[matchingParser].handler(this), null != TempState.break && break; } } while (terminatorMatch || parserMatch); null == TempState.break ? this.nextMatch < this.source.length && (this.outputText(this.output, this.nextMatch, this.source.length), this.nextMatch = this.source.length) : this.output.lastChild && this.output.lastChild.nodeType === Node.ELEMENT_NODE && "BR" === this.output.lastChild.nodeName.toUpperCase() && jQuery(this.output.lastChild).remove(); this.output = oldOutput, oldOptions && (this.options = oldOptions); } }, { key: "outputText", value: function(destination, startPos, endPos) { jQuery(destination).append(document.createTextNode(this.source.substring(startPos, endPos))); } }, { key: "rawArgs", value: function() { return console.warn("[DEPRECATED] Wikifier.rawArgs() is deprecated."), this._rawArgs; } }, { key: "fullArgs", value: function() { return console.warn("[DEPRECATED] Wikifier.fullArgs() is deprecated."), Scripting.desugar(this._rawArgs); } }], [{ key: "wikifyEval", value: function(text) { var output = document.createDocumentFragment(); new Wikifier(output, text); var errors = output.querySelector(".error"); if (null !== errors) throw new Error(errors.textContent.replace(errorPrologRegExp, "")); return output; } }, { key: "createInternalLink", value: function(destination, passage, text, callback) { var $link = jQuery(document.createElement("a")); return null != passage && ($link.attr("data-passage", passage), Story.has(passage) ? ($link.addClass("link-internal"), Config.addVisitedLinkClass && State.hasPlayed(passage) && $link.addClass("link-visited")) : $link.addClass("link-broken"), $link.ariaClick({ one: !0 }, (function() { "function" == typeof callback && callback(); Engine.play(passage); }))), text && $link.append(document.createTextNode(text)), destination && $link.appendTo(destination), $link[0]; } }, { key: "createExternalLink", value: function(destination, url, text) { var $link = jQuery(document.createElement("a")).attr("target", "_blank").addClass("link-external").text(text).appendTo(destination); return null != url && $link.attr({ href: url, tabindex: 0 }), $link[0]; } }]); ``` -------------------------------- ### Blockquote and Line Quote Handlers Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Implements two blockquote handlers: 'quoteByBlock' for multi-line blockquotes starting and ending with '<<<', and 'quoteByLine' for blockquotes indicated by one or more '>' characters at the start of a line. These handlers manage nested blockquotes and line breaks appropriately. ```javascript Wikifier.Parser.add({name:"quoteByBlock",profiles:["block"],match:"^<<<\\n",terminator:"^<<<\\n",handler:function(w){Wikifier.helpers.hasBlockContext(w.output.childNodes)?w.subWikify(jQuery(document.createElement("blockquote")).appendTo(w.output).get(0),this.terminator):jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:"quoteByLine",profiles:["block"],match:"^>+",lookahead:/^>+/gm,terminator:"\\n",handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){var matched,i,destStack=[w.output],curLevel=0,newLevel=w.matchLength;do{if(newLevel>curLevel)for(i=curLevel;inewLevel;--i)destStack.pop();curLevel=newLevel,w.subWikify(destStack[destStack.length-1],this.terminator),jQuery(document.createElement("br")).appendTo(destStack[destStack.length-1]),this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);(matched=match&&match.index===w.nextMatch)&&(newLevel=match[0].length,w.nextMatch+=match[0].length)}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))}}) ``` -------------------------------- ### UI Bar Initialization and Interaction Setup Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Configures the initial state of the UI bar, including stowing it based on window width and setting up click handlers for the toggle button. It also configures navigation buttons (backward, jumpto, forward) and updates story elements based on game events. ```javascript start:{value:function(){if(_$uiBar){("boolean"==typeof Config.ui.stowBarInitially?Config.ui.stowBarInitially:jQuery(window).width()<=Config.ui.stowBarInitially)&&stow(!0),jQuery("#ui-bar-toggle").ariaClick({label:L10n.get("uiBarLabelToggle")},(function(){return _$uiBar.toggleClass("stowed")})),Config.history.controls?(jQuery("#history-backward").ariaDisabled(State.length<2).ariaClick({label:L10n.get("uiBarLabelBackward")},(function(){return Engine.backward()})),Story.filter((function(passage){return passage.tags.includes("bookmark")})).length>0?jQuery("#history-jumpto").ariaClick({label:L10n.get("uiBarLabelJumpto")},(function(){return UI.jumpto()})):jQuery("#history-jumpto").remove(),jQuery("#history-forward").ariaDisabled(State.length===State.size).ariaClick({label:L10n.get("uiBarLabelForward")},(function(){return Engine.forward()}))):jQuery("#ui-bar-history").remove();var storyTitleHandler,addUiUpdateHandler=function(handler){return jQuery(document)[Config.ui.updateStoryElements?"on":"one"](":uiupdate".concat(".ui-bar"),handler)},addUpdaterOrRemove=function(selector,passageName){var $el=jQuery(selector);Story.has(passageName)?addUiUpdateHandler((function(){var frag=document.createDocumentFragment();new Wikifier(frag,Story.get(passageName).processText().trim()),$el.empty().append(frag)})):$el.remove()};if(storyTitleHandler=Story.has("StoryDisplayTitle")?function(){return setDisplayTitle(Story.get("StoryDisplayTitle").processText())}:function(){return setDisplayTitle(Story.name,!0)},addUiUpdateHandler(storyTitleHandler),addUpdaterOrRemove("#story-banner","StoryBanner"),addUpdaterOrRemove("#story-subtitle","StorySubtitle"),addUpdaterOrRemove("#story-author","StoryAuthor"),addUpdaterOrRemove("#story-caption","StoryCaption"),Story.has("StoryMenu")){var $menuStory=jQuery("#menu-story");jQuery(document).on(":uiupdate".concat(".ui-bar"),(function(){try{var frag=UI.assembleLinkList("StoryMenu",document.createDocumentFragment());$menuStory.empty().append(frag)}catch(ex){console.error(ex),Alert.error("StoryMenu",ex.message)}}))}else jQuery("#menu-story").remove();Save.browser.size>0?(jQuery("#menu-item-continue a").ariaClick({role:"button"},(function(ev){ev.preventDefault(),Save.browser.continue().then((function(){jQuery(document).off(".menu-item-continue"),jQuery("#menu-item-continue").remove(),Engine.show()}),(function(ex){return UI.alert("".concat(ex.message.toUpperFirst(),".

").concat(L10n.get("textAborting"),"."))}))})).text(L10n.get("continueTitle")),jQuery(document).on(":passagestart.menu-item-continue",(function(){State.turns>1&&(jQuery(document).off(".menu-item-continue"),jQuery("#menu-item-continue").remove())}))):jQuery("#menu-it" ``` -------------------------------- ### Playtime Macro Usage Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/playtime-system.md Demonstrates the different ways to use the <> macro, including basic display, formatted display, and automatic updates using <>. ```sugarcube <> <> <> <> <> ``` -------------------------------- ### String.prototype.substr Polyfill Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Provides a polyfill for String.prototype.substr to ensure correct handling of negative start indices. ```javascript var xr=f.substr;var Or="".substr&&"0b".substr(-1)!=="b";P(f,{substr:function substr(t,r){var e=t;if(t<0){e=w(this.length+t,0)}return xr.call(this,e,r)}},Or); ``` -------------------------------- ### <> Macro Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/v1-docs.md Resumes a cycle that was suspended by `<>`, allowing it to start collecting turns again. ```javascript <> // Example 1: Resumes the cycle named 'timeOfDay', if it is suspended. <> // Example 2: Resumes the cycles 'day' and 'seasons', if either is suspended. <> ``` -------------------------------- ### SugarCube 2 Audio Runner Initialization Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Initializes the AudioRunner with track descriptions, handling properties like sources, own, and volume. Includes error handling for invalid inputs and initialization failures. Supports cloning tracks and setting initial volumes. ```javascript function _newTrack(sources) { // Placeholder for actual track initialization logic console.log('Initializing new track with sources:', sources); return { hasSource: () => true, clone: () => ({ ...this }), volume: () => 1.0, loop: () => false, mute: () => false, isPaused: () => false, isPlaying: () => true, isStopped: () => false }; } function _runnerParseSelector(selector) { // Placeholder for selector parsing logic console.log('Parsing selector:', selector); return [{ id: selector }]; } function masterMute(mute) { // Placeholder for master mute logic console.log('Master mute called with:', mute); return mute; } function publish(event, value) { // Placeholder for publish logic console.log('Publish event:', event, 'with value:', value); } const Config = { debug: true }; const Visibility = { isEnabled: () => true, changeEvent: 'visibilitychange', isHidden: () => false }; const jQuery = { off: () => {}, on: () => {} }; const momentCreate = () => ({}); const clone = (obj) => JSON.parse(JSON.stringify(obj)); const historyDeltaEncode = (history) => history; const _tracks = new Map(); _tracks.set('track1', { hasSource: () => true, clone: () => ({ ...this }), volume: () => 0.8, loop: () => false, mute: () => false, isPaused: () => false, isPlaying: () => true, isStopped: () => false }); _tracks.set('track2', { hasSource: () => true, clone: () => ({ ...this }), volume: () => 0.5, loop: () => true, mute: () => true, isPaused: () => true, isPlaying: () => false, isStopped: () => false }); const _lists = new Map(); const _groups = new Map(); let _masterMuteOnHidden = false; let _masterRate = 1.0; let _masterVolume = 0.9; // Example usage within a larger structure const AudioRunner = function(trackIds) { this.trackIds = trackIds; console.log('AudioRunner created with trackIds:', this.trackIds); }; const SimpleAudio = Object.defineProperties({}, { playlists: { value: Object.defineProperties({}, { add: { value: function(id, desc) { let track = null; let own = null; let volume = null; const what = 'playlist'; try { if (Object.hasOwn(desc, 'racks')) { track = _tracks.get(desc.racks.get(desc.id)); } else if (Object.hasOwn(desc, 'sources')) { if (!Array.isArray(desc.sources) || desc.sources.length === 0) { throw new Error('"sources" property must be a non-empty array'); } if (Object.hasOwn(desc, 'own')) { throw new Error('"own" property is not allowed with the "sources" property'); } try { track = _newTrack(desc.sources); own = true; } catch (ex) { throw new Error("error during track initialization: ".concat(ex.message)); } } if (Object.hasOwn(desc, 'own')) { if (typeof desc.own !== 'boolean') { throw new Error('"own" property must be a boolean'); } own = desc.own; if (own) { track = track.clone(); } } if (Object.hasOwn(desc, 'volume')) { if (typeof desc.volume !== 'number' || Number.isNaN(desc.volume) || !Number.isFinite(desc.volume) || desc.volume < 0) { throw new Error('"volume" property must be a non-negative finite number'); } volume = desc.volume; } _lists.has(id) && _lists.get(id)._destroy(); _lists.set(id, { _destroy: () => console.log('Destroying playlist:', id), own: null != own && own, track: track, volume: null != volume ? volume : track.volume() }); } catch (ex) { throw new Error("".concat(what, ": error during playlist initialization: ").concat(ex.message)); } } }, delete: { value: function(id) { _lists.has(id) && _lists.get(id)._destroy(); return _lists.delete(id); } }, clear: { value: function() { _lists.forEach(function(list) { return list._destroy(); }); _lists.clear(); } }, has: { value: function(id) { return _lists.has(id); } }, get: { value: function(id) { return _lists.get(id) || null; } } }) }, select: { value: function() { if (arguments.length === 0) { throw new Error("no track selector specified"); } var selector = String(arguments[0]).trim(); var trackIds = new Set(); try { var renderIds = function renderIds(idObj) { var ids, id = idObj.id; switch (id) { case ":all": ids = Array.from(_tracks.keys()); break; case ":looped": ids = Array.from(_tracks.keys()).filter(function(id) { return _tracks.get(id).loop(); }); break; case ":muted": ids = Array.from(_tracks.keys()).filter(function(id) { return _tracks.get(id).mute(); }); break; case ":paused": ids = Array.from(_tracks.keys()).filter(function(id) { return _tracks.get(id).isPaused(); }); break; case ":playing": ids = Array.from(_tracks.keys()).filter(function(id) { return _tracks.get(id).isPlaying(); }); break; case ":stopped": ids = Array.from(_tracks.keys()).filter(function(id) { return _tracks.get(id).isStopped(); }); break; default: if (id.startsWith(":")) { var group = _groups.get(id); if (!group) { throw new Error('group "'.concat(id, '" does not exist')); } ids = group; } else { ids = [id]; } } if (Object.hasOwn(idObj, "not")) { var negated = idObj.not.map(function(idObj) { return renderIds(idObj); }).flat(1 / 0); ids = ids.filter(function(id) { return !negated.includes(id); }); } return ids; }; var allIds = Array.from(_tracks.keys()); _runnerParseSelector(selector).forEach(function(idObj) { return renderIds(idObj).forEach(function(id) { if (!_tracks.has(id)) { throw new Error('track "'.concat(id, '" does not exist')); } trackIds.add(id); }); }); } catch (ex) { throw new Error("error during runner initialization: ".concat(ex.message)); } return new AudioRunner(trackIds); } }, load: { value: function() { publish("load"); } }, loadWithScreen: { value: function() { publish("loadwithscreen"); } }, mute: { value: masterMute }, muteOnHidden: { value: function(mute) { if (!Visibility.isEnabled()) { return false; } if (null == mute) { return _masterMuteOnHidden; } var namespace = ".SimpleAudio_masterMuteOnHidden"; if (_masterMuteOnHidden = !!mute) { var visibilityChange = "".concat(Visibility.changeEvent).concat(namespace); jQuery(document).off(namespace).on(visibilityChange, function() { return masterMute(Visibility.isHidden()); }); Visibility.isHidden() && masterMute(true); } else { jQuery(document).off(namespace); } } }, rate: { value: function(rate) { if (null == rate) { return _masterRate; } if (typeof rate !== 'number' || Number.isNaN(rate) || !Number.isFinite(rate)) { throw new Error("rate must be a finite number"); } publish("rate", _masterRate = Math.clamp(rate, 0.2, 5)); } }, stop: { value: function() { publish("stop"); } }, unload: { value: function() { publish("unload"); } }, volume: { value: function(volume) { if (null == volume) { return _masterVolume; } if (typeof volume !== 'number' || Number.isNaN(volume) || !Number.isFinite(volume)) { throw new Error("volume must be a finite number"); } publish("volume", _masterVolume = Math.clamp(volume, 0, 1)); } } }); // Math.clamp polyfill if not available if (!Math.clamp) { Math.clamp = function(value, min, max) { return Math.min(Math.max(value, min), max); }; } ``` -------------------------------- ### String.prototype.count Source: https://github.com/chapelr/custom-macros-for-sugarcube-2/blob/master/docs/demo/index.html Counts the number of non-overlapping occurrences of a substring within the string. An optional starting position can be provided. ```javascript Object.defineProperty(String.prototype, "count", { configurable: !0, writable: !0, value: function() { if (null == this) throw new TypeError("String.prototype.count called on null or undefined"); var needle = String(arguments[0] || ""); if ("" === needle) return 0; for (var indexOf = String.prototype.indexOf, step = needle.length, pos = Number(arguments[1]) || 0, count = 0; -1 !== (pos = indexOf.call(this, needle, pos));) ++count, pos += step; return count; } }); ```