├── .gitignore
├── plugin_diceroller.sfile.md
├── mythicgme_ui.sfile.md
├── system.sfile.md
├── rpgtools.sfile.md
├── files.sfile.md
├── clips_ui.sfile.md
├── clips.sfile.md
├── mythicv2_ui.sfile.md
├── arrows.sfile.md
├── notepick.sfile.md
├── lipsum.sfile.md
├── une_ui.sfile.md
├── state.sfile.md
├── notevars.sfile.md
├── une.sfile.md
├── cards_pileviewer.sfile.md
├── lists_ui.sfile.md
├── mythicgme.sfile.md
├── lists.sfile.md
├── cards_ui.sfile.md
└── mythicv2.sfile.md
/.gitignore:
--------------------------------------------------------------------------------
1 | Ξ_state.data.md
2 | Ξ_libraryVersion.md
3 |
--------------------------------------------------------------------------------
/plugin_diceroller.sfile.md:
--------------------------------------------------------------------------------
1 | A shortcut to run commands through the Dice Roller plugin.
2 |
3 | __
4 | ```
5 | ^diceroller (.+)$
6 | ```
7 | __
8 | ```js
9 | // Get the diceroller plugin (or exit if not available)
10 | const diceRollerPlugin = app.plugins.getPlugin("obsidian-dice-roller");
11 | if (!diceRollerPlugin)
12 | {
13 | return expText("Command not run. Dice Roller plugin not available.");
14 | }
15 |
16 | // Roll the command parameter with the plugin
17 | const diceRoller = await diceRollerPlugin.getRoller($1);
18 | return await diceRoller.roll();
19 | ```
20 | __
21 | diceroller {command: text} - Runs {command} through the Dice Roller plugin and expands to the result.
22 |
--------------------------------------------------------------------------------
/mythicgme_ui.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | An extension to __mythicgme.sfile__ that provides graphical ui versions of shortcuts.
6 |
7 |
8 | __
9 | ```
10 | ^sfile setup$
11 | ```
12 | __
13 | ```js
14 | // Block loading if mythicv2 is already setup (incompatible shortcut-files)
15 | if (_inlineScripts.inlineScripts.sfileIndices["mythicv2"])
16 | {
17 | return true;
18 | }
19 | ```
20 | __
21 | Sets up this shortcut-file
22 |
23 |
24 | __
25 | ```
26 | ^ui fate$
27 | ```
28 | __
29 | ```js
30 | // Data
31 | const odds =
32 | [
33 | "Impossible", "No way", "Very unlikely",
34 | "Unlikely", "50 / 50", "Somewhat likely",
35 | "Likely", "Very likely", "Near sure thing",
36 | "Sure thing", "Has to be"
37 | ];
38 |
39 | // Choose the odds to roll fate with
40 | const pick = popups.pick("Choose the odds", odds, 4, 11);
41 | if (pick === null) { return null; }
42 |
43 | // Roll fate with the chosen odds
44 | return expand("fate " + (pick - 4));
45 | ```
46 | __
47 | ui fate - Asks the user to choose the odds for this fate check.
48 | Displays the answer to a yes/no question, possibly with a random event attached.
49 |
50 |
51 | __
52 | ```
53 | ^ui scene$
54 | ```
55 | __
56 | ```js
57 | // Choose chaos adjustment for the scene advancement
58 | const pick =
59 | popups.pick("How was the previous scene?", [ "More controlled", "More chaotic" ],
60 | 1, 2);
61 | if (pick === null) { return null; }
62 |
63 | // Run the scene with the chosen chaos adjustments
64 | return expand("scene " + (pick * 2 - 1));
65 | ```
66 | __
67 | ui scene - Asks the user to choose whether chaos increased or decreased last scene.
68 | Starts a new scene with the chosen chaos value adjustment.
69 |
--------------------------------------------------------------------------------
/system.sfile.md:
--------------------------------------------------------------------------------
1 | Shortcuts to work with the system: Obsidian, JS, InlineScripts backend, etc.
2 |
3 |
4 | __
5 | ```
6 | ^sfile setup$
7 | ```
8 | __
9 | ```js
10 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
11 |
12 | // Setup session state
13 | confirmObjectPath("_inlineScripts.system.lastError", "NONE");
14 |
15 | // Event callback - inlineScripts.onError
16 | confirmObjectPath(
17 | "_inlineScripts.inlineScripts.listeners.onError.system",
18 | (errorMessage) =>
19 | {
20 | _inlineScripts.system.lastError = errorMessage;
21 | });
22 | ```
23 | __
24 | Sets up this shortcut-file
25 |
26 |
27 | __
28 | ```
29 | ^sfile shutdown$
30 | ```
31 | __
32 | ```js
33 | // Event callback removal
34 | delete _inlineScripts.inlineScripts?.listeners?.onError?.system;
35 |
36 | // Session state removal
37 | delete _inlineScripts.system;
38 | ```
39 | __
40 | Shuts down this shortcut-file
41 |
42 |
43 | __
44 | ```
45 | ^sys lasterror$
46 | ```
47 | __
48 | ```js
49 | return expFormat(
50 | "Last error:\n> " + _inlineScripts.system.lastError.replaceAll("\n", "\n> "));
51 | ```
52 | __
53 | sys lasterror - Expands to the last expansion error that was posted to the console. This does not include errors that were created by a shortcut, only those created by __Inline Scripts__ itself, i.e. those the trigger the message: `ERROR: Shortcut expansion issues. (see console for details)`.
54 |
55 |
56 | __
57 | ```
58 | ^sys runjs (.*)$
59 | ```
60 | __
61 | ```js
62 | return eval($1);
63 | ```
64 | __
65 | sys runjs {code: text} - Run the javascript in {code}. Can run multiple statements separated with newline characters (`\\n`).
66 |
67 |
68 | __
69 | ```
70 | ^sys triggererror$
71 | ```
72 | __
73 | ```js
74 | bad code here!
75 | ```
76 | __
77 | sys triggererror - Create an expansion error. Useful for testing the `sys lasterror` shortcut.
78 |
--------------------------------------------------------------------------------
/rpgtools.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts to help in playing tabletop rpgs, either group or solo.
6 |
7 |
8 | __
9 | __
10 | ```js
11 | // Make a roll from 1 to max.
12 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
13 |
14 | // Pick an item from array a.
15 | function aPick(a) { return a[roll(a.length)-1]; }
16 |
17 | // Pick an item from array a, weighted by element wIndex of the item. If theRoll is
18 | // passed, use that as the roll.
19 | function aPickWeight(a, wIndex, theRoll)
20 | {
21 | wIndex = wIndex || 1;
22 | theRoll = theRoll || roll(a.last()[wIndex]);
23 | for (let i = 0; i < a.length; i++)
24 | {
25 | if (a[i][wIndex] >= theRoll)
26 | {
27 | return a[i];
28 | }
29 | }
30 | return a.last();
31 | }
32 | ```
33 | __
34 | Helper scripts
35 |
36 |
37 | __
38 | ```
39 | ^tbl twist$
40 | ```
41 | __
42 | ```js
43 | // Data
44 | let target = ["ENVIRONMENT","ALLY","ENEMY","UNKNOWN","SOMETHING","INFORMATION"];
45 | let circumstance = ["MISSING / GONE / LOST / FORGOTTEN","EXPLOSIVE / DANGEROUS / SENSITIVE","BLOCKED / HIDDEN / TRAPPED","ARRIVED / RECENT / NEW","DEAD / DESTROYED / UNRECOVERABLE","BROKEN / DAMAGED / INJURED","FLOODED / OVERFLOWED / EXCEEDED","SABOTAGED / CAPTURED / TAKEN","CORRUPTED / TWISTED / INSANE","FALSE / FAKE / LYING / TREASON","MISTAKEN / ERROR / FALSE / WRONG","LEAKED / DISCOVERED / KNOWN","ERRATIC / UNRELIABLE / FAILING / INSUFFICIENT","DISRUPTED / MODIFIED / ARTIFICIAL","SURPRISING / ALTERED / UNEXPECTED","YOUNGER / OLDER / NEWER / OBSOLETE","SICK / HAZARDOUS / POISON / TOXIC","PREPARED / ARMED / EXPECTING","UNPREPARED / UNARMED / UNEXPECTED","DELAYED / TIMED / WAIT"];
46 |
47 | // Roll from the data and return the result. The final empty string allows
48 | // formatting to be removed by the caller.
49 | return expFormat([ "Twist:\n", aPick(target), " - ", aPick(circumstance), "" ]);
50 |
51 | ```
52 | __
53 | tbl twist - Rolls a plot twist from random tables. Source.
54 |
--------------------------------------------------------------------------------
/files.sfile.md:
--------------------------------------------------------------------------------
1 | Shortcuts for working with files in the vault.
2 |
3 |
4 | __
5 | ```
6 | ^files extensionchange ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ?([^ \t\\:*?"<>|]+|)$
7 | ```
8 | __
9 | ```js
10 | // File path parameter - remove quotes
11 | $1 = $1.replaceAll(/^\"|\"$/g, "");
12 |
13 | // Extension parameter - default to "md"
14 | $2 ||= "md";
15 |
16 | // Confirm file path parameter is valid
17 | if (!app.vault.fileMap[$1])
18 | {
19 | return expFormat("Extension not changed. File __" + $1 + "__ not found.");
20 | }
21 |
22 | // Get the current extension position
23 | let extensionIndex = $1.lastIndexOf(".");
24 | if (extensionIndex == -1) { extensionIndex = $1.length; }
25 |
26 | // Add new extension
27 | const newPath = $1.slice(0, extensionIndex) + "." + $2;
28 | app.vault.adapter.rename($1, newPath);
29 |
30 | return expFormat("File __" + $1 + "__ is now __" + newPath + "__.");
31 | ```
32 | __
33 | files extensionchange {file name: path text} {extension: nospace text, default: "md"} - Changes the extension of file {file name} to {extension}.
34 |
35 |
36 | __
37 | ```
38 | ^files? shortcutbatch ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) (.*)$
39 | ```
40 | __
41 | ```js
42 | // Remove quotes around path parameter
43 | $1 = $1.replaceAll(/^\"|\"$/g, "");
44 |
45 | // Confirm that the path is a valid folder, early out if not
46 | const folder = app.vault.fileMap[$1];
47 | if (!folder)
48 | {
49 | return expFormat("Extension not changed. Folder __" + $1 + "__ not found.");
50 | }
51 | if (!folder.children)
52 | {
53 | return expFormat("Extension not changed. __" + $1 + "__ is not a folder.");
54 | }
55 |
56 | // Find an unused list name (to use "lists shortcutbatch" shortcut)
57 | let tmpListName = 0;
58 | while (_inlineScripts.state.sessionState.lists["L" + tmpListName])
59 | {
60 | tmpList++;
61 | }
62 | tmpListName = "L" + tmpListName;
63 |
64 | // Popuplate list with filenames
65 | for (const child of folder.children)
66 | {
67 | expand("lists add " + tmpListName + " " + child.path);
68 | }
69 |
70 | // Run shortcutbatch on the list
71 | let result = expand("lists shortcutbatch " + tmpList + " " + $2);
72 |
73 | // Fux the resulting expansion - "for folder" instead of "for list"
74 | result = result.replace("for list __" + tmpList, "for folder __" + $1);
75 |
76 | // Remove the list now that we're done with it
77 | expand("lists removelist " + tmpListName);
78 |
79 | // Return the result
80 | return result;
81 | ```
82 | __
83 | files shortcutbatch {folder name: path text} {shortcut: text} - Runs shortcut {shortcut} once for each file in folder {folder name}, replacing "%1" in {shortcut} with the file's name.
84 |
--------------------------------------------------------------------------------
/clips_ui.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | An extension to __clips.sfile__ that provides graphical ui versions of shortcuts.
6 |
7 |
8 | __
9 | ```
10 | ^ui clips? reset$
11 | ```
12 | __
13 | ```js
14 | return expand("clips reset");
15 | ```
16 | __
17 | ui clips reset - Removes all clips.
18 |
19 |
20 | __
21 | ```
22 | ^ui clips?$
23 | ```
24 | __
25 | ```js
26 | return expand("clips");
27 | ```
28 | __
29 | ui clips - Lists all stored clips.
30 |
31 |
32 | __
33 | ```
34 | ^ui clips? set$
35 | ```
36 | __
37 | ```js
38 | // Choose the clip name
39 | const clipNames = Object.keys(_inlineScripts.state.sessionState.clips).sort();
40 | let name = popups.input("Enter a clip name to set", "", clipNames);
41 | if (!name) { return null; }
42 | name = name.replaceAll(" ", "_");
43 |
44 | // Choose the clip value
45 | const value = popups.input("Enter a value for clip " + name + "");
46 | if (value === null) { return null; }
47 |
48 | return expand("clips set " + name + " " + value);
49 | ```
50 | __
51 | ui clips set - User enters a clip name and a clip value. The value is then assigned to the clip name.
52 |
53 |
54 | __
55 | ```
56 | ^ui (?:clips? get|cg)$
57 | ```
58 | __
59 | ```js
60 | // Choose the clip from the list of clips
61 | const clipNames = Object.keys(_inlineScripts.state.sessionState.clips).sort();
62 | if (!clipNames.length)
63 | {
64 | return "";
65 | }
66 | const pick = popups.pick("Choose a clip to get", clipNames, 0, "adaptive");
67 | if (pick === null) { return null; }
68 |
69 | return expand("clips get " + clipNames[pick]);
70 | ```
71 | __
72 | ui clips get - User chooses a clip, which is then expanded into the note.
73 | - Alternative: __ui cg__
74 |
75 |
76 | __
77 | ```
78 | ^ui clips? expansion$
79 | ```
80 | __
81 | ```js
82 | // Choose the clip name
83 | const clipNames = Object.keys(_inlineScripts.state.sessionState.clips).sort();
84 | let name = popups.input("Enter a clip name for the prior expansion", "", clipNames);
85 | if (!name) { return null; }
86 | name = name.replaceAll(" ", "_");
87 |
88 | return expand("clips expansion " + name);
89 | ```
90 | __
91 | ui clips expansion - User enters a clip name. The prior expansion's text is then assigned to the clip name.
92 |
93 |
94 | __
95 | ```
96 | ^ui clips? remove$
97 | ```
98 | __
99 | ```js
100 | // Choose the clip name
101 | const clipNames = Object.keys(_inlineScripts.state.sessionState.clips).sort();
102 | if (!clipNames.length)
103 | {
104 | return expFormat("Clip not removed. No clips to remove.");
105 | }
106 | const pick = await popups.pick("Choose a clip to remove", clipNames, 0, "adaptive");
107 | if (pick === null) { return null; }
108 |
109 | return expand("clips remove " + clipNames[pick]);
110 | ```
111 | __
112 | ui clips remove - User chooses a clip, which is then removed.
113 |
--------------------------------------------------------------------------------
/clips.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts that let the user manage clips of text. A "clip" is a bit of named text that can be quickly added to your note. Similar to a shortcut, but simplistic and tied to the session state.
6 |
7 | Uses __state.sfile__ shortcut-file (optional).
8 | It uses this to save & load the clips.
9 |
10 | This shortcut-file has supplementary shortcut files:
11 | __clips_ui.sfile__ - Graphical UI versions of some of these shortcuts.
12 |
13 |
14 | __
15 | ```
16 | ^sfile setup$
17 | ```
18 | __
19 | ```js
20 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
21 |
22 | // Make sure state and "pirorExpansion" value are setup
23 | confirmObjectPath("_inlineScripts.state.sessionState.clips");
24 | confirmObjectPath("_inlineScripts.clips.priorExpansion", "");
25 |
26 | // Event callback - state.onReset
27 | confirmObjectPath(
28 | "_inlineScripts.state.listeners.onReset.clips",
29 | function()
30 | {
31 | expand("clips reset noconfirm");
32 | });
33 |
34 | // Event callback - inlineScripts.onExapsion
35 | confirmObjectPath(
36 | "_inlineScripts.inlineScripts.listeners.onExpansion.clips",
37 | (expansionInfo) =>
38 | {
39 | _inlineScripts.clips.priorExpansion =
40 | expansionInfo.expansionText;
41 | });
42 | ```
43 | __
44 | Sets up this shortcut-file
45 |
46 |
47 | __
48 | ```
49 | ^sfile shutdown$
50 | ```
51 | __
52 | ```js
53 | // Event callback removal
54 | delete _inlineScripts.inlineScripts?.listeners?.onExpansion?.clips;
55 |
56 | // State removal
57 | delete _inlineScripts.state?.listeners?.onReset?.clips;
58 | ```
59 | __
60 | Shuts down this shortcut-file
61 |
62 |
63 | __
64 | ```
65 | ^clips reset?$
66 | ```
67 | __
68 | ```js
69 | // Confirm
70 | if (!popups.confirm("Confirm resetting the Clips system")) { return null; }
71 |
72 | // Reset
73 | expand("clips reset noconfirm");
74 |
75 | return expFormat("All clips cleared.");
76 | ```
77 | __
78 | clips reset - Removes all clips.
79 | ***
80 |
81 |
82 | __
83 | ```
84 | ^clips? reset noconfirm$
85 | ```
86 | __
87 | ```js
88 | // Reset state object
89 | _inlineScripts.state.sessionState.clips = {};
90 | ```
91 | __
92 | hidden - No-confirm reset
93 |
94 |
95 | __
96 | ```
97 | ^clips?$
98 | ```
99 | __
100 | ```js
101 | // Expand to a list of clip names
102 | const clipNames = Object.keys(_inlineScripts.state.sessionState.clips);
103 | const result = "Clips:\n. " + (clipNames.length ? clipNames.join("\n. ") : "NONE");
104 |
105 | return expFormat(result);
106 | ```
107 | __
108 | clips - Lists all stored clips.
109 |
110 |
111 | __
112 | ```
113 | ^clips? set ([_a-zA-Z][_a-zA-Z0-9]*) (.+)$
114 | ```
115 | __
116 | ```js
117 | // Make clip titles case-insensitive
118 | $1 = $1.toLowerCase();
119 |
120 | // Assign the clip contents to the clip name
121 | _inlineScripts.state.sessionState.clips[$1] = $2;
122 |
123 | return expFormat("Clip __" + $1 + "__ set to:\n" + $2);
124 | ```
125 | __
126 | clips set {name: name text} {value: text} - Creates / Sets a clip named {name} to the string {value}.
127 |
128 |
129 | __
130 | ```
131 | ^(?:clips? get|cg) ([_a-zA-Z][_a-zA-Z0-9]*)$
132 | ```
133 | __
134 | ```js
135 | // Make clip titles case-insensitive
136 | $1 = $1.toLowerCase();
137 |
138 | // Expand to the text associated with the clip name (or empty string for invalid name)
139 | return _inlineScripts.state.sessionState.clips[$1] || "";
140 | ```
141 | __
142 | clips get {name: name text} - Expands to the value stored in clip {name}.
143 | - Alternative: __cg {name: name text}__
144 |
145 |
146 | __
147 | ```
148 | ^clips? expansion ([_a-zA-Z][_a-zA-Z0-9]*)$
149 | ```
150 | __
151 | ```js
152 | // Make clip titles case-insensitive
153 | $1 = $1.toLowerCase();
154 |
155 | // Assign prior expansion text to the clip name
156 | _inlineScripts.state.sessionState.clips[$1] = _inlineScripts.clips.priorExpansion;
157 |
158 | return expFormat(
159 | "Clip __" + $1 + "__ set to:\n" + _inlineScripts.clips.priorExpansion);
160 | ```
161 | __
162 | clips expansion {name: name text} - Creates a clip named {name} that stores the previous expansion.
163 |
164 |
165 | __
166 | ```
167 | ^clips? remove ([_a-zA-Z][_a-zA-Z0-9]*)$
168 | ```
169 | __
170 | ```js
171 | // Make clip titles case-insensitive
172 | $1 = $1.toLowerCase();
173 |
174 | // If clip exists, remove it and notify user
175 | if (_inlineScripts.state.sessionState.clips[$1])
176 | {
177 | delete _inlineScripts.state.sessionState.clips[$1];
178 | return expFormat("Clip __" + $1 + "__ removed.");
179 | }
180 |
181 | // If clip doesn't exist, just notify user
182 | else
183 | {
184 | return expFormat("Clip __" + $1 + "__ not removed. Does not exist.");
185 | }
186 | ```
187 | __
188 | clips remove {name: name text} - Removes the clip named {name}.
189 |
--------------------------------------------------------------------------------
/mythicv2_ui.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | An extension to __mythicv2.sfile__ that provides graphical ui versions of shortcuts.
6 |
7 |
8 | __
9 | ```
10 | ^sfile setup$
11 | ```
12 | __
13 | ```js
14 | // Block loading if mythicgme is already setup (incompatible shortcut-files)
15 | if (_inlineScripts.inlineScripts.sfileIndices["mythicgme"])
16 | {
17 | return true;
18 | }
19 | ```
20 | __
21 | Sets up this shortcut-file
22 |
23 |
24 | __
25 | __
26 | ```js
27 | // Reset the details if the current shortcut is user-triggered (i.e. a new action)
28 | function clearDetailsIfUserTriggered()
29 | {
30 | if (expansionInfo.isUserTriggered)
31 | {
32 | _inlineScripts.mythicv2.details = [];
33 | }
34 | }
35 | ```
36 | __
37 | Helper scripts
38 |
39 |
40 | __
41 | ```
42 | ^ui mythicv2 details$
43 | ```
44 | __
45 | ```js
46 | // Start a new details list
47 | clearDetailsIfUserTriggered();
48 |
49 | // Get the current showDetails flag state
50 | const currentState = _inlineScripts.state.sessionState.mythicv2.showDetails;
51 |
52 | // Choose a new showDetails flag value
53 | const pick = popups.pick(
54 | "Set 'details' mode",
55 | [ "Disable", "Enable" ], (currentState ? 1 : 0), 2);
56 | if (pick === null) { return null; }
57 |
58 | return expand("mythicv2 details " + (pick ? "y" : "n"));
59 | ```
60 | __
61 | ui mythicv2 details - Asks the user whether to enable or disable 'details' mode.
62 | Displays whether 'details' mode is enabled.
63 |
64 |
65 | __
66 | ```
67 | ^ui fate$
68 | ```
69 | __
70 | ```js
71 | // Start a new details list
72 | clearDetailsIfUserTriggered();
73 |
74 | // Data
75 | const odds =
76 | [
77 | "Impossible", "No way", "Very unlikely",
78 | "Unlikely", "50 / 50", "likely",
79 | "Very likely", "Sure thing", "Has to be"
80 | ];
81 |
82 | // Choose what odds to use in the fate check
83 | const pick = popups.pick("Choose the odds", odds, 4, 9);
84 | if (pick === null) { return null; }
85 |
86 | // Choose whether the pc benefits from a "yes" or a "no"
87 | const pick2 =
88 | popups.pick("What answer is best for the pc(s)?", [ "YES", "NO" ], 0, 2);
89 | if (pick2 === null) { return null; }
90 |
91 | return expand( "fate " + (pick - 4) + " " + (pick2 ? "n" : "y") );
92 | ```
93 | __
94 | ui fate - Asks the user to choose the odds for this fate check.
95 | Asks the user what answer is best for the pc(s).
96 | Displays the answer to a yes/no question, possibly with a random event attached.
97 |
98 |
99 | __
100 | ```
101 | ^ui scene$
102 | ```
103 | __
104 | ```js
105 | // Start a new details list
106 | clearDetailsIfUserTriggered();
107 |
108 | // Choose a chaos adjustment
109 | const pick = popups.pick(
110 | "How was the previous scene?", [ "More controlled", "More chaotic" ], 1, 2);
111 | if (pick === null) { return null; }
112 |
113 | return expand("scene " + (pick * 2 - 1));
114 | ```
115 | __
116 | ui scene - Asks the user to choose whether chaos increased or decreased last scene.
117 | Starts a new scene with the chosen chaos value adjustment.
118 |
119 |
120 | __
121 | ```
122 | ^ui disposition$
123 | ```
124 | __
125 | ```js
126 | // Start a new details list
127 | clearDetailsIfUserTriggered();
128 |
129 | // Choose the descriptor modifier value
130 | const choices =
131 | [
132 | "-3 (ALL descriptors make the npc LESS ACTIVE)", "-2", "-1",
133 | "0", "+1", "+2",
134 | "+3 (ALL descriptors make the npc MORE ACTIVE)"
135 | ];
136 | const pick = popups.pick("Enter the total descriptor modifier for the npc.\n\nConsider the npc's three descriptors in the current situation.\n+1 for descriptors that make the npc MORE active.\n-1 for descriptors that make the npc LESS active.\n0 for descriptors that don't affect npc activity.", choices, 3, 7);
137 | if (pick === null) { return null; }
138 |
139 | // Choose the base disposition
140 | const choices2 =
141 | [
142 | "Random","2","3","4","5","6","7","8","9",
143 | "10","11","12","13","14","15","16","17",
144 | "18","19","20"
145 | ];
146 | const pick2 = popups.pick("Enter the base disposition for the npc for this scene.\n\nIf the npc has a base dispositon for this scene, select it.\nIf the npc does NOT yet have a base disposition for this scene, select 'Random'.", choices2, 0, 20);
147 | if (pick2 === null) { return null; }
148 |
149 | // Base disposition is random, use the shortcut that rolls it
150 | if (!pick2)
151 | {
152 | return expand("disposition " + (pick - 3));
153 | }
154 |
155 | // Base disposition is not random, use the shortcut that takes it as a parameter
156 | else
157 | {
158 | return expand("disposition " + (pick - 3) + " " + choices2[pick2]);
159 | }
160 | ```
161 | __
162 | ui disposition - Asks the user for the total descriptor modifier for an npc (+1 for descriptors that make the npc more active in the scene, -1 for descriptors that make the npc less active).
163 | If the npc already has a disposition for this scene, select it. If NOT, select "random" to choose one.
164 | Rolls and shows a disposition for the npc for the current scene.
165 |
166 |
167 | __
168 | ```
169 | ^ui action$
170 | ```
171 | __
172 | ```js
173 | // Start a new details list
174 | clearDetailsIfUserTriggered();
175 |
176 | // Choose the disposition modifier
177 | const dispositionMods =
178 | [
179 | "-2 (Passive)", "0 (Moderate)", "+2 (Active)",
180 | "+4 (Aggressive)"
181 | ];
182 | const pick =
183 | popups.pick("Enter the npc's current disposition modifier.", dispositionMods, 1,
184 | 4);
185 | if (pick === null) { return null; }
186 |
187 | return expand("action " + (pick * 2 - 2));
188 | ```
189 | __
190 | ui action - Asks the user for the npc's disposition modifier.
191 | Rolls and shows an action for the npc to take.
192 |
--------------------------------------------------------------------------------
/arrows.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts for writing various arrows:
6 | - →←↑↓
7 | - ▶◀▲▼
8 | - ⇒⇐⇑⇓
9 | - ↔↕⇔⇕⇄⇅
10 | - ↗↘↖↙
11 | - ↪↩↻↺↝↜
12 |
13 |
14 | __
15 | ```
16 | ^(?:arrow right|\-\>)$
17 | ```
18 | __
19 | ```js
20 | return "→";
21 | ```
22 | __
23 | -> - → (RIGHT arrow).
24 | - Alternative: __arrow right__
25 |
26 |
27 | __
28 | ```
29 | ^(?:arrow left|\<\-)$
30 | ```
31 | __
32 | ```js
33 | return "←";
34 | ```
35 | __
36 | <- - ← (LEFT arrow).
37 | - Alternative: __arrow left__
38 |
39 |
40 | __
41 | ```
42 | ^(?:arrow up|\-\^)$
43 | ```
44 | __
45 | ```js
46 | return "↑";
47 | ```
48 | __
49 | -^ - ↑ (UP arrow).
50 | - Alternative: __arrow up__
51 |
52 |
53 | __
54 | ```
55 | ^(?:arrow down|\-v)$
56 | ```
57 | __
58 | ```js
59 | return "↓";
60 | ```
61 | __
62 | -v - ↓ (DOWN arrow).
63 | - Alternative: __arrow down__
64 | ***
65 |
66 |
67 | __
68 | ```
69 | ^(?:tri right|\|\>)$
70 | ```
71 | __
72 | ```js
73 | return "▶";
74 | ```
75 | __
76 | |> - ▶ (RIGHT triangle).
77 | - Alternative: __tri right__
78 |
79 |
80 | __
81 | ```
82 | ^(?:tri left|\<\|)$
83 | ```
84 | __
85 | ```js
86 | return "◀";
87 | ```
88 | __
89 | <| - ◀ (LEFT triangle).
90 | - Alternative: __tri left__
91 |
92 |
93 | __
94 | ```
95 | ^(?:tri up|\|\^)$
96 | ```
97 | __
98 | ```js
99 | return "▲";
100 | ```
101 | __
102 | |^ - ▲ (UP triangle).
103 | - Alternative: __tri up__
104 |
105 |
106 | __
107 | ```
108 | ^(?:tri down|\|v)$
109 | ```
110 | __
111 | ```js
112 | return "▼";
113 | ```
114 | __
115 | |v - ▼ (DOWN triangle).
116 | - Alternative: __tri down__
117 | ***
118 |
119 |
120 | __
121 | ```
122 | ^(?:arrow dbl right|\=\>)$
123 | ```
124 | __
125 | ```js
126 | return "⇒";
127 | ```
128 | __
129 | => - ⇒ (RIGHT double-arrow).
130 | - Alternative: __arrow dbl right__
131 |
132 |
133 | __
134 | ```
135 | ^(?:arrow dbl left|\<\=)$
136 | ```
137 | __
138 | ```js
139 | return "⇐";
140 | ```
141 | __
142 | <= - ⇐ (LEFT double-arrow).
143 | - Alternative: __arrow dbl left__
144 |
145 |
146 | __
147 | ```
148 | ^(?:arrow dbl up|\=\^)$
149 | ```
150 | __
151 | ```js
152 | return "⇑";
153 | ```
154 | __
155 | =^ - ⇑ (UP double-arrow).
156 | - Alternative: __arrow dbl up__
157 |
158 |
159 | __
160 | ```
161 | ^(?:arrow dbl down|\=v)$
162 | ```
163 | __
164 | ```js
165 | return "⇓";
166 | ```
167 | __
168 | =v - ⇓ (DOWN double-arrow).
169 | - Alternative: __arrow dbl down__
170 | ***
171 |
172 |
173 | __
174 | ```
175 | ^(?:arrow leftright|\<\-\>)$
176 | ```
177 | __
178 | ```js
179 | return "↔";
180 | ```
181 | __
182 | <-> - ↔ (LEFT / RIGHT arrow).
183 | - Alternative: __arrow leftright__
184 |
185 |
186 | __
187 | ```
188 | ^(?:arrow updown|\^\-v)$
189 | ```
190 | __
191 | ```js
192 | return "↕";
193 | ```
194 | __
195 | ^-v - ↕ (UP / DOWN arrow).
196 | - Alternative: __arrow updown__
197 |
198 |
199 | __
200 | ```
201 | ^(?:arrow dbl leftright|\<\=\>)$
202 | ```
203 | __
204 | ```js
205 | return "⇔";
206 | ```
207 | __
208 | <=> - ⇔ (LEFT / RIGHT double arrow).
209 | - Alternative: __arrow dbl leftright__
210 |
211 |
212 | __
213 | ```
214 | ^(?:arrow dbl updown|\^\=v)$
215 | ```
216 | __
217 | ```js
218 | return "⇕";
219 | ```
220 | __
221 | ^=v - ⇕ (UP / DOWN double arrow).
222 | - Alternative: __arrow dbtl updown__
223 |
224 |
225 | __
226 | ```
227 | ^(?:arrow left right|\<\/\/\>)$
228 | ```
229 | __
230 | ```js
231 | return "⇄";
232 | ```
233 | __
234 | /> - ⇄ (LEFT arrow & RIGHT arrow).
235 | - Alternative: __arrow left right__
236 |
237 |
238 | __
239 | ```
240 | ^(?:arrow up down|\^\/\/v)$
241 | ```
242 | __
243 | ```js
244 | return "⇅";
245 | ```
246 | __
247 | ^//v - ⇅ (UP arrow & DOWN arrow).
248 | - Alternative: __arrow up down__
249 | ***
250 |
251 |
252 | __
253 | ```
254 | ^(?:arrow rightup|\-\^\>)$
255 | ```
256 | __
257 | ```js
258 | return "↗";
259 | ```
260 | __
261 | -^> - ↗ (RIGHT / UP arrow).
262 | - Alternative: __arrow rightup__
263 |
264 |
265 | __
266 | ```
267 | ^(?:arrow rightdown|\-v\>)$
268 | ```
269 | __
270 | ```js
271 | return "↘";
272 | ```
273 | __
274 | -v> - ↘ (RIGHT / DOWN arrow).
275 | - Alternative: __arrow rightdown__
276 |
277 |
278 | __
279 | ```
280 | ^(?:arrow leftup|\<\^\-)$
281 | ```
282 | __
283 | ```js
284 | return "↖";
285 | ```
286 | __
287 | <^- - ↖ (LEFT / UP arrow).
288 | - Alternative: __arrow leftup__
289 |
290 |
291 | __
292 | ```
293 | ^(?:arrow leftdown|\)$
308 | ```
309 | __
310 | ```js
311 | return "↪";
312 | ```
313 | __
314 | u> - ↪ (RIGHT curve arrow).
315 | - Alternative: __arrow curve right__
316 |
317 |
318 | __
319 | ```
320 | ^(?:arrow curve left|\)$
334 | ```
335 | __
336 | ```js
337 | return "↻";
338 | ```
339 | __
340 | c> - ↻ (CLOCKWISE arrow).
341 | - Alternative: __arrow clock__
342 |
343 |
344 | __
345 | ```
346 | ^(?:arrow cclock|\)$
360 | ```
361 | __
362 | ```js
363 | return "↝";
364 | ```
365 | __
366 | \~> - ↝ (RIGHT wavy arrow).
367 | - Alternative: __arrow wavy right__
368 |
369 |
370 | __
371 | ```
372 | ^(?:arrow wavy left|\<\~)$
373 | ```
374 | __
375 | ```js
376 | return "↜";
377 | ```
378 | __
379 | <\~ - ↜ (LEFT wavy arrow).
380 | - Alternative: __arrow wavy left__
381 |
--------------------------------------------------------------------------------
/notepick.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts to pick X random notes from a folder of notes and to get their front matter data.
6 |
7 | This shortcut-file has a tutorial video available:
8 | - [Using the "notepick" shortcut-file to randomly pick notes & get variables](https://www.youtube.com/watch?v=G1mvl-VwbIQ) (runtime 3:22)
9 |
10 |
11 | __
12 | ```
13 | ^sfile setup$
14 | ```
15 | __
16 | ```js
17 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
18 |
19 | // Setup the state
20 | confirmObjectPath("_inlineScripts.state.sessionState.notepick");
21 |
22 | // Event callback - state.onReseet
23 | confirmObjectPath(
24 | "_inlineScripts.state.listeners.onReset.notepick",
25 | function()
26 | {
27 | expand("notepick reset noconfirm");
28 | });
29 | ```
30 | __
31 | Sets up this shortcut-file
32 |
33 |
34 | __
35 | ```
36 | ^sfile shutdown$
37 | ```
38 | __
39 | ```js
40 | // Event callback removal
41 | delete _inlineScripts.state?.listeners?.onReset?.notepick;
42 |
43 | // State removal
44 | delete _inlineScripts.state?.sessionState?.notepick;
45 | ```
46 | __
47 | Shuts down this shortcut-file
48 |
49 |
50 | __
51 | ```
52 | ^notepick reset$
53 | ```
54 | __
55 | ```js
56 | // Confirm
57 | if (!popups.confirm("Confirm resetting the Notepick system")) { return null; }
58 |
59 | // Reset
60 | expand("notepick reset noconfirm");
61 |
62 | return expFormat("All notepicks cleared.");
63 | ```
64 | __
65 | notepick reset - Clears all picks.
66 | ***
67 |
68 |
69 | __
70 | ```
71 | ^notepick reset noconfirm$
72 | ```
73 | __
74 | ```js
75 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
76 |
77 | // Restart the state
78 | confirmObjectPath("_inlineScripts.state.sessionState");
79 | _inlineScripts.state.sessionState.notepick = {};
80 | ```
81 | __
82 | hidden - No-confirm reset
83 |
84 |
85 | __
86 | __
87 | ```js
88 | // Make a roll from 1 to max.
89 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
90 |
91 | // Pick an item from array a.
92 | function aPick(a) { return a[roll(a.length)-1]; }
93 | ```
94 | __
95 | Helper scripts
96 |
97 |
98 | __
99 | ```
100 | ^notepick pickFromFolderAndGetPick ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ?([1-9][0-9]*|) ?([_a-zA-Z][_a-zA-Z0-9]*|) ?(.*)$
101 | ```
102 | __
103 | ```js
104 | // Remove any quotes around the folder path
105 | $1 = $1.replace(/^"(.*)"$/, "$1")
106 |
107 | // Pick random items. If error, early-out.
108 | let result = expUnformat(
109 | expand("notepick pickFromFolder " + $1 + " " + $2 + " " + $3 + " " + $4));
110 | if (!result[0]) { return expFormat(result); }
111 |
112 | // Return the list of items picked
113 | return expand("notepick getPick " + $3);
114 | ```
115 | __
116 | notepick pickFromFolderAndGetPick {folder name: path text} {pick count: >0, default: 1} {pick id: name text, default: ""} {to ignore: text ( | separated filenames)} - Combines the shortcuts "notepick pickFromFolder" and "notepick getPick".
117 |
118 |
119 | __
120 | ```
121 | ^notepick pickFromFolderAndGetFrontmatter ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ?([1-9][0-9]*|) ?([_a-zA-Z][_a-zA-Z0-9]*|) ?(.*)$
122 | ```
123 | __
124 | ```js
125 | // Remove any quotes around the folder path
126 | $1 = $1.replace(/^"(.*)"$/, "$1")
127 |
128 | // Pick random items. If error, early-out.
129 | let result = expUnformat(
130 | expand("notepick pickFromFolder " + $1 + " " + $2 + " " + $3 + " " + $4));
131 | if (!result[0]) { return expFormat(result); }
132 |
133 | // Get the front matter for the items. If error, early-out.
134 | result = expUnformat(expand("notepick frontmatter " + $3));
135 | if (!result[0]) { return expFormat(result); }
136 |
137 | return result[1];
138 | ```
139 | __
140 | notepick pickFromFolderAndGetFrontmatter {folder name: path text} {pick count: >0, default: 1} {pick id: name text, default: ""} {to ignore: text ( | separated filenames)} - Combines the shortcuts "notepick pickFromFolder" and "notepick frontmatter".
141 | ***
142 |
143 |
144 | __
145 | ```
146 | ^notepick pickFromFolder ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ?([1-9][0-9]*|) ?([_a-zA-Z][_a-zA-Z0-9]*|) ?(.*)$
147 | ```
148 | __
149 | ```js
150 | // Remove any quotes around the folder path
151 | $1 = $1.replace(/^"(.*)"$/, "$1")
152 | // Count defaults to 1
153 | $2 = Number($2) || 1;
154 | // Split toIgnore into filenames
155 | $4 = $4.split("|");
156 |
157 | // Get the file object for the given folder. Early out of doesn't exist or is a file
158 | const folder = app.vault.fileMap[$1];
159 | if (!folder)
160 | {
161 | return expFormat([ "","No files picked. Folder __" + $1 + "__ doesn't exist." ]);
162 | }
163 | if (!folder.children)
164 | {
165 | return expFormat([ "", "No files picked. __" + $1 + "__ isn't a folder." ]);
166 | }
167 |
168 | // Get the non-folder children of folder
169 | let files = folder.children.filter(v => !v.children).map(v => v.path);
170 |
171 | // Remove the files excluded by the "to ignore" parameter
172 | files = files.filter(v => !$4.includes(v));
173 |
174 | // If file count is under the pick count, early out.
175 | if (files.length < $2)
176 | {
177 | return expFormat(
178 | [ "", "No files picked. Not enough files in Folder __" + $1 + "__." ]);
179 | }
180 |
181 | // Randomly pick of the files.
182 | const pick = [];
183 | while (pick.length < $2)
184 | {
185 | const nextPickItem = aPick(files);
186 | if (!pick.includes(nextPickItem))
187 | {
188 | pick.push(nextPickItem);
189 | }
190 | }
191 |
192 | // Add the files picked to the state
193 | _inlineScripts.state.sessionState.notepick[$3] = pick;
194 |
195 | return expFormat([ $2 + " file(s) picked" + ($3 ? " for " + $3 : "") + "." ]);
196 | ```
197 | __
198 | notepick pickFromFolder {folder name: path text} {count: >0, default: 1} {pick id: name text, default: ""} {to ignore: text ( | separated filenames)} - Picks {count} random notes from folder {folder name} and remembers them as {pick id}. Any files in {to ignore} are never picked.
199 |
200 |
201 | __
202 | ```
203 | ^notepick getPick ?([_a-zA-Z][_a-zA-Z0-9]*|)$
204 | ```
205 | __
206 | ```js
207 | // Get the specified pick from the state. Early out if specified pick isn't stored.
208 | const picks = _inlineScripts.state.sessionState.notepick[$1];
209 | if (!picks)
210 | {
211 | return expFormat([ "", "No pick. Pick id __" + $1 + "__ not found." ]);
212 | }
213 |
214 | return expFormat(
215 | [ "Pick" + ($1 ? " __" + $1 + "__" : "") + ":\n", picks.join("\n"), "" ]);
216 | ```
217 | __
218 | notepick getPick {pick id: name text, default: ""} - Gets a list of the files last picked for {pick id}.
219 |
220 |
221 | __
222 | ```
223 | ^notepick frontmatter ?([_a-zA-Z][_a-zA-Z0-9]*|)$
224 | ```
225 | __
226 | ```js
227 | // Get the specified pick from the state. Early out if specified pick isn't stored.
228 | const pick = _inlineScripts.state.sessionState.notepick[$1];
229 | if (!pick)
230 | {
231 | return expFormat(
232 | [ "", "No frontmatter gathered. Pick id __" + $1 + "__ not found." ]);
233 | }
234 |
235 | // Get the file objects of the picks
236 | const files = pick.map(v => app.vault.fileMap[v]);
237 |
238 | // Confirm that all picks have valid file objects. If not, early out.
239 | for (let i = 0; i < pick.length; i++)
240 | {
241 | if (!files[i])
242 | {
243 | return expFormat(
244 | [ "", "No frontmatter gathered. __" + fileNotFound + "__ not found." ]);
245 | }
246 | }
247 |
248 | // Get the frontmatter for each of the picks.
249 | let result = {};
250 | for (const file of files)
251 | {
252 | result[file.name.replace(/.md$/, "")] =
253 | app.metadataCache.getFileCache(file).frontmatter || {};
254 | }
255 |
256 | return expFormat(
257 | [ "Frontmatter gathered for " + pick.length + " note(s).\n", result, "" ]);
258 | ```
259 | __
260 | notepick frontmatter {pick id: name text, default: ""} - Gets the frontmatter from the notes that are remembered in {pick id}.
261 |
--------------------------------------------------------------------------------
/lipsum.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | A decent Lorum Ipsum generator.
6 |
7 |
8 | __
9 | ```
10 | ^sfile setup$
11 | ```
12 | __
13 | ```js
14 | // Only do this once
15 | if (window._inlineScripts?.lipsum) { return; }
16 |
17 | // Data to make output text from
18 | let l = `
19 |
20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum et sem quam. Proin viverra egestas sem, et finibus mi volutpat eget. Aliquam vel nulla mattis, malesuada lectus sed, sagittis eros. Etiam vulputate, felis nec aliquam tempor, velit ex lobortis nisi, ac aliquet magna arcu id velit. Nam odio ligula, consequat eget elit non, accumsan rutrum est. Mauris vitae ligula nec lacus finibus venenatis at molestie ligula. Ut suscipit ex et finibus gravida.
21 |
22 | Phasellus orci neque, dictum at dui vel, ultricies varius sapien. Praesent ultricies ultrices mi eget luctus. Integer porttitor purus sit amet ante luctus mollis. Nulla lorem diam, fringilla in rhoncus quis, vestibulum lacinia dolor. Nam finibus eros ut nisl pellentesque euismod. Nunc nec ipsum in purus dapibus cursus eu eu ex. Ut nec mauris neque. Sed eget enim ut velit laoreet vehicula quis quis sem.
23 |
24 | Nullam pulvinar hendrerit ipsum, nec gravida neque aliquam at. Suspendisse justo metus, rutrum ac ligula sed, varius rutrum sapien. Etiam porttitor arcu massa, non laoreet eros sodales a. Integer et est et dolor maximus feugiat. Nam tristique purus a nibh fringilla, sit amet porta libero dictum. Quisque rhoncus pharetra cursus. Pellentesque sed rutrum sapien, nec pretium nisi. Aenean vitae quam sit amet nisl venenatis congue ut non libero. Donec sit amet mattis tellus, et condimentum justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Maecenas tristique est nibh, ac dignissim quam pretium in. Nam semper odio eget tellus tempus, eu vestibulum tortor tempor. Curabitur elit ex, posuere nec diam eget, malesuada blandit arcu. Proin condimentum arcu sed lorem consectetur convallis. Sed facilisis convallis lorem, ut fermentum nulla pretium vel.
25 |
26 | Mauris pulvinar lectus eu mi vehicula, ut dapibus velit egestas. Nullam pellentesque a nibh quis sodales. Donec lacinia vulputate est. Vivamus lacinia neque a libero faucibus, sed condimentum dui imperdiet. In dignissim aliquet tellus a finibus. Cras suscipit tempus sagittis. In lobortis arcu quis ante sagittis cursus. Vestibulum vitae lectus sit amet felis placerat pulvinar ac id sapien. Suspendisse congue, sem vitae consectetur pharetra, libero metus congue quam, quis finibus felis eros eu arcu.
27 |
28 | Vestibulum sagittis tristique lectus sit amet accumsan. Sed laoreet lectus eu euismod scelerisque. In velit dolor, placerat id rhoncus aliquam, semper in lectus. Curabitur ut ante congue, ullamcorper ante vitae, accumsan quam. Phasellus ante leo, pulvinar eu leo quis, convallis elementum nisi. Praesent et imperdiet metus, sed aliquet arcu. Phasellus efficitur vehicula libero, quis efficitur arcu. Nulla maximus tellus non luctus auctor.
29 |
30 | Aliquam gravida condimentum nisl, id aliquam ipsum efficitur id. Quisque et mauris ac purus accumsan porta vel sit amet lectus. Duis viverra accumsan risus, nec fringilla ipsum posuere in. Donec dapibus nisl at congue facilisis. Nam malesuada accumsan dapibus. Vestibulum rhoncus ornare aliquam. Mauris viverra, diam sit amet fermentum venenatis, tortor leo ultrices massa, vel lobortis neque urna eget felis. Aliquam viverra erat ac dolor sagittis, nec aliquet lorem blandit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam molestie pellentesque laoreet. Duis nulla eros, lobortis vitae elit in, maximus facilisis felis. Proin viverra venenatis eros, nec consequat dui euismod eu.
31 |
32 | Nam vestibulum turpis ac lorem luctus, ut faucibus leo vehicula. Praesent laoreet nibh turpis, id tincidunt mauris ultricies ut. Integer euismod turpis sit amet nulla iaculis consequat. Vivamus cursus arcu ac sem luctus egestas. Integer varius lorem augue, ac consequat neque vehicula sit amet. Aliquam eu orci congue, porttitor est vitae, tincidunt quam. Phasellus vehicula, quam id ultricies aliquet, dolor risus tincidunt leo, sit amet eleifend nisi ex ut erat. Praesent porta vehicula nisl, ac feugiat dui viverra in. Nunc vehicula sagittis tortor, eget commodo erat hendrerit ac. Praesent accumsan laoreet nisi vitae faucibus. Proin in turpis id ante suscipit sodales.
33 |
34 | Vestibulum accumsan dignissim metus non cursus. Sed pulvinar imperdiet arcu, a condimentum ipsum sodales sit amet. Integer eu dapibus lacus. Duis id ligula et nunc scelerisque sagittis eget sed libero. Nulla id justo varius, pellentesque augue et, egestas sem. Donec cursus ligula in hendrerit suscipit. Nullam laoreet justo bibendum, mollis nibh sed, egestas ipsum. Aenean volutpat erat velit, vel placerat mauris efficitur eu. Aenean quam arcu, vehicula non commodo id, blandit a magna. Cras ac sem dolor. Nam tempor odio dictum, consequat augue in, finibus risus. Nunc elementum leo at maximus scelerisque. Nullam at feugiat ipsum, quis facilisis dolor.
35 |
36 | Aliquam nec sapien urna. Aliquam bibendum, ligula id lobortis elementum, odio nulla pretium justo, vitae maximus dolor elit vitae risus. Pellentesque suscipit mi eget augue hendrerit tincidunt. Phasellus ut pulvinar mi. Pellentesque euismod lobortis fermentum. Aliquam vitae lacus sit amet sapien malesuada tempus. Proin iaculis nunc sed ullamcorper rutrum. Nullam non interdum elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac metus at tortor luctus vehicula id at dolor. Nulla pulvinar ornare venenatis. Vivamus a ultricies mauris, et maximus enim. Mauris blandit tellus id lacus placerat, ut blandit purus ultrices.
37 |
38 | Ut pharetra semper leo at convallis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aenean vulputate, ex in imperdiet pharetra, leo libero mattis nunc, at tristique turpis libero ac lectus. Suspendisse potenti. Integer sed dolor at urna ultricies ornare. Ut auctor vulputate diam, sit amet condimentum lectus mattis at. Aenean placerat molestie arcu at pretium. Donec consequat nibh nulla, vel mollis lacus tincidunt et.
39 |
40 | `
41 |
42 | // Break data into pieces to recombine for output text
43 | let pSizes = [];
44 | let sentences = [];
45 | l = l.trim().split("\n\n");
46 | for (let i = 0; i < l.length; i++)
47 | {
48 | l[i] = l[i].slice(0,-1).split(". ");
49 | pSizes.push(l[i].length);
50 | sentences = sentences.concat(l[i]);
51 | }
52 | let first = sentences.shift();
53 | window._inlineScripts ||= {};
54 | window._inlineScripts.lipsum =
55 | { first: first, pSizes: pSizes, sentences: sentences };
56 | ```
57 | __
58 | Takes a source Lorem Ipsum text, generated at https://lipsum.com/feed/html, and breaks it down into individual sentences and counts of sentences per paragraph. On user request, builds a Lorem Ipsum text of random sentences from the source, combined into paragraphs of random size based on the source's paragraphs.
59 |
60 |
61 | __
62 | ```
63 | ^lipsum ?(|[1-9][0-9]*)$
64 | ```
65 | __
66 | ```js
67 | // Count parameter defaults to 1
68 | $1 = Number($1) || 1;
69 |
70 | // Helper function - Make a roll from 1 to max.
71 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
72 |
73 | // Helper function - Pick an item from array a.
74 | function aPick(a) { return a[roll(a.length)-1]; }
75 |
76 | // Generate and return text from the data created on setup
77 | let result = "";
78 | for (let i = 0; i < $1; i++)
79 | {
80 | let pSize = aPick(window._inlineScripts.lipsum.pSizes);
81 | if (i === 0)
82 | {
83 | pSize--;
84 | result += window._inlineScripts.lipsum.first + ". ";
85 | }
86 | else
87 | {
88 | result += "\n\n";
89 | }
90 | for (let k = 0; k < pSize; k++)
91 | {
92 | result += aPick(window._inlineScripts.lipsum.sentences) + ". ";
93 | }
94 | result = result.slice(0,-1);
95 | }
96 | return result;
97 | ```
98 | __
99 | lipsum {paragraph count: >0, default: 1} - Generates a lorem ipsum text with {paragraph count} paragraphs.
100 |
--------------------------------------------------------------------------------
/une_ui.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | An extension to __une.sfile__ that provides graphical ui versions of shortcuts.
6 |
7 |
8 | __
9 | ```
10 | ^sfile setup$
11 | ```
12 | __
13 | ```js
14 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
15 |
16 | _inlineScripts.inlineScripts.HelperFncs.addCss("une_ui", ".iscript_une_select { height: unset; padding: .25em; margin-top: .5em; margin-bottom: 1.5em } .iscript_popup { max-height: unset !important; }");
17 |
18 | // Custom popop definition for the 3 possible parameters used in uni shortcuts
19 | confirmObjectPath("_inlineScripts.une_ui.uneInputPopup");//,
20 | _inlineScripts.une_ui.uneInputPopup =
21 | {
22 | // Setup function
23 | onOpen: async (data, parent, firstButton, SettingType) =>
24 | {
25 | // Randomness
26 | if (data.getRandomness)
27 | {
28 | const titleUi = document.createElement("div");
29 | titleUi.innerText = "Randomness";
30 | titleUi.classList.add("setting-item-name");
31 | parent.append(titleUi);
32 | const descriptionUi = document.createElement("div");
33 | descriptionUi.innerText =
34 | "Choose the randomness of the scene (for Power Level)";
35 | descriptionUi.classList.add("setting-item-description");
36 | parent.append(descriptionUi);
37 | data.randomnessUi = document.createElement("select");
38 | data.randomnessUi.innerHTML =
39 | "" +
40 | "" +
41 | "" +
42 | "" +
43 | "";
44 | data.randomnessUi.size = 5;
45 | data.randomnessUi.classList.add("iscript_une_select");
46 | data.randomnessUi.addEventListener("keypress", e =>
47 | {
48 | if (e.key === "Enter") { firstButton.click(); }
49 | })
50 | parent.append(data.randomnessUi);
51 | }
52 |
53 | // Relationship
54 | if (data.getRelationship)
55 | {
56 | const titleUi = document.createElement("div");
57 | titleUi.innerText = "Relationship";
58 | titleUi.classList.add("setting-item-name");
59 | parent.append(titleUi);
60 | const descriptionUi = document.createElement("div");
61 | descriptionUi.innerText =
62 | "Choose the NPC's feelings towards the PC(s) (for mood)";
63 | descriptionUi.classList.add("setting-item-description");
64 | parent.append(descriptionUi);
65 | data.relationshipUi = document.createElement("select");
66 | data.relationshipUi.innerHTML =
67 | "" +
68 | "" +
69 | "" +
70 | "" +
71 | "" +
72 | "" +
73 | "";
74 | data.relationshipUi.size = 7;
75 | data.relationshipUi.classList.add("iscript_une_select");
76 | data.relationshipUi.addEventListener("keypress", e =>
77 | {
78 | if (e.key === "Enter") { firstButton.click(); }
79 | })
80 | parent.append(data.relationshipUi);
81 | }
82 |
83 | // Demeanor
84 | if (data.getDemeanor)
85 | {
86 | const titleUi = document.createElement("div");
87 | titleUi.innerText = "Demeanor";
88 | titleUi.classList.add("setting-item-name");
89 | parent.append(titleUi);
90 | const descriptionUi = document.createElement("div");
91 | descriptionUi.innerText =
92 | "Choose the NPC's demeanor (for bearing)";
93 | descriptionUi.classList.add("setting-item-description");
94 | parent.append(descriptionUi);
95 | data.demeanorUi = document.createElement("select");
96 | data.demeanorUi.innerHTML =
97 | "" +
98 | "" +
99 | "" +
100 | "" +
101 | "" +
102 | "" +
103 | "" +
104 | "" +
105 | "";
106 | data.demeanorUi.size = 9;
107 | data.demeanorUi.classList.add("iscript_une_select");
108 | data.demeanorUi.addEventListener("keypress", e =>
109 | {
110 | if (e.key === "Enter") { firstButton.click(); }
111 | })
112 | parent.append(data.demeanorUi);
113 | }
114 | },
115 |
116 | // Shutown function
117 | onClose: async (data, resolveFnc, buttonId) =>
118 | {
119 | if (buttonId !== "Ok") { return; }
120 |
121 | // Setup and return results
122 | let result = {};
123 | if (data.getRandomness)
124 | {
125 | result.randomness = Number(data.randomnessUi.value);
126 | }
127 | if (data.getRelationship)
128 | {
129 | result.relationship = Number(data.relationshipUi.value);
130 | }
131 | if (data.getDemeanor)
132 | {
133 | result.demeanor = Number(data.demeanorUi.value);
134 | }
135 | resolveFnc(result);
136 | }
137 | };//);
138 | ```
139 | __
140 | Sets up this shortcut-file
141 |
142 |
143 | __
144 | ```
145 | ^sfile shutdown$
146 | ```
147 | __
148 | ```js
149 | // State removal
150 | delete _inlineScripts.une_ui;
151 |
152 | // Custom CSS removal
153 | _inlineScripts.inlineScripts.HelperFncs.removeCss("une_ui");
154 | ```
155 | __
156 | Shuts down this shortcut-file
157 |
158 |
159 | __
160 | ```
161 | ^ui une$
162 | ```
163 | __
164 | ```js
165 | // Get input from the custom popup for une parameters
166 | const input = popups.custom(
167 | "UNE generator - Full",
168 | _inlineScripts.une_ui.uneInputPopup,
169 | { getRandomness: true, getRelationship: true, getDemeanor: true });
170 | if (!input) { return null; }
171 |
172 | return expand(
173 | "une " + input.randomness + " " + input.relationship +
174 | (input.demeanor ? (" " + input.demeanor) : ""));
175 | ```
176 | __
177 | ui une - Asks the user to choose the scene's randomness (for picking the NPC's power).
178 | Asks the user to choose the NPC's felings towards the PC(s) (for the NPC's mood).
179 | Asks the user to choose the NPC's demeanor, or pick it randomly (for the NPC's bearing).
180 | Displays the NPC's character (identity, power, motive) and the NPC's interaction for this scene (mood, bearing, focus).
181 | ***
182 |
183 |
184 | __
185 | ```
186 | ^ui une character$
187 | ```
188 | __
189 | ```js
190 | // Get input from the custom popup for une parameters
191 | const input = popups.custom(
192 | "UNE generator - Character",
193 | _inlineScripts.une_ui.uneInputPopup,
194 | { getRandomness: true, getRelationship: false, getDemeanor: false });
195 | if (!input) { return null; }
196 |
197 | return expand("une character " + input.randomness);
198 | ```
199 | __
200 | ui une character - Asks the user to choose the scene's randomness (for picking the NPC's power).
201 | Displays the NPC's character (identity, power, motive).
202 |
203 |
204 | __
205 | ```
206 | ^ui une interact$
207 | ```
208 | __
209 | ```js
210 | // Get input from the custom popup for une parameters
211 | const input = popups.custom(
212 | "UNE generator - Iteraction",
213 | _inlineScripts.une_ui.uneInputPopup,
214 | { getRandomness: false, getRelationship: true, getDemeanor: true });
215 | if (!input) { return null; }
216 |
217 | return expand(
218 | "une interact " + input.relationship +
219 | (input.demeanor ? (" " + input.demeanor) : ""));
220 | ```
221 | __
222 | ui une interact - Asks the user to choose the NPC's feelings towards the PC(s) (for the NPC's mood).
223 | Asks the user to choose the NPC's demeanor, or pick it randomly (for the NPC's bearing).
224 | Displays the NPC's interaction for this scene (mood, bearing, focus).
225 | ***
226 |
227 |
228 | __
229 | ```
230 | ^ui une identity$
231 | ```
232 | __
233 | ```js
234 | // Just wraps "une identity"
235 | return expand("une identity");
236 | ```
237 | __
238 | ui une identity - Generates a 2-word description for an NPC.
239 |
240 |
241 | __
242 | ```
243 | ^ui une power$
244 | ```
245 | __
246 | ```js
247 | // Get input from the custom popup for une parameters
248 | const input = popups.custom(
249 | "UNE generator - Power",
250 | _inlineScripts.une_ui.uneInputPopup,
251 | { getRandomness: true, getRelationship: false, getDemeanor: false });
252 | if (!input) { return null; }
253 |
254 | return expand("une power " + input.randomness);
255 | ```
256 | __
257 | ui une power - Asks the user to choose the scene's randomness.
258 | Displays the NPC's power relative to the PC power(s).
259 |
260 |
261 | __
262 | ```
263 | ^ui une motive$
264 | ```
265 | __
266 | ```js
267 | // Just wraps "une motive"
268 | return expand("une motive");
269 | ```
270 | __
271 | ui une motive - Generates three 2-word descriptions for an NPC's motivations.
272 | ***
273 |
274 |
275 | __
276 | ```
277 | ^ui une mood$
278 | ```
279 | __
280 | ```js
281 | // Get input from the custom popup for une parameters
282 | const input = popups.custom(
283 | "UNE generator - Mood",
284 | _inlineScripts.une_ui.uneInputPopup,
285 | { getRandomness: false, getRelationship: true, getDemeanor: false });
286 | if (!input) { return null; }
287 |
288 | return expand("une mood " + input.relationship);
289 | ```
290 | __
291 | ui une mood - Asks the user to choose the NPC's felings towards the PC(s).
292 | Displays the NPC's mood for this scene.
293 |
294 |
295 | __
296 | ```
297 | ^ui une bearing$
298 | ```
299 | __
300 | ```js
301 | // Get input from the custom popup for une parameters
302 | const input = popups.custom(
303 | "UNE generator - Bearing",
304 | _inlineScripts.une_ui.uneInputPopup,
305 | { getRandomness: false, getRelationship: false, getDemeanor: true });
306 | if (!input) { return null; }
307 |
308 | return expand("une bearing" + (input.demeanor ? (" " + input.demeanor) : ""));
309 | ```
310 | __
311 | ui une bearing - Asks the user to choose the NPC's demeanor, or pick it randomly.
312 | Displays a the NPC's bearing.
313 |
314 |
315 | __
316 | ```
317 | ^ui une focus$
318 | ```
319 | __
320 | ```js
321 | // Just wraps "une focus"
322 | return expand("une focus");
323 | ```
324 | __
325 | ui une focus - Generates an NPC's primary interest for this interaction.
--------------------------------------------------------------------------------
/state.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | This shortcut-file works with the state of many other shortcut-files. Notably, it automatically maintains their state between Obsidian sessions. It also includes shortcuts to allow you to save, load and reset states.
6 |
7 | This shortcut file can be used by other shortcut-files to let them setup their own state that persists across Obsidian sessions.
8 |
9 | This shortcut-file has 2 tutorial videos available. The first video is the best for most users, but if you are writing shortcuts, then the second video shows you how to setup your own shortcut-file to use the state system:
10 | - [Using the "state" shortcut-file to save and load session state](https://www.youtube.com/watch?v=IiRHB0FfJcI) (runtime 2:45)
11 | - [Setup a shortcut-file for saving & loading with the "state" shortcut-file](https://www.youtube.com/watch?v=K-6Xy3YLuh4) (runtime 6:12)
12 |
13 |
14 | To save the session state, use the "state get" shortcut. This expands into a "state-string"- a string which includes all data in the current session state. You can save this state-string simply by leaving it as part of the note. To load the session state, call the "state set" shortcut, passing in a state-string _(created earlier with the "state get" shortcut)_.
15 |
16 | Alternately, "state set last" will load the state-string from the last call to the "state get" shortcut in the current document.
17 |
18 |
19 | __
20 | ```
21 | ^sfile setup$
22 | ```
23 | __
24 | ```js
25 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
26 |
27 | // Prevent running this shortcut multiple times concurrently. There are race-
28 | // condition in the file handling.
29 | if (_inlineScripts?.state?.inSetup) { return; }
30 | confirmObjectPath("_inlineScripts.state.inSetup", true);
31 |
32 | // The name of the state data-file - the file to store the state in
33 | const STATE_FILE_NAME = "Ξ_state.data.md";
34 |
35 | // Helper function - Remomve file without triggering an exception if it doesn't exist
36 | async function removeFile(filename)
37 | {
38 | if (await app.vault.adapter.exists(filename))
39 | {
40 | await app.vault.adapter.remove(filename);
41 | }
42 | }
43 |
44 | // Setup the event callback arrays
45 | confirmObjectPath("_inlineScripts.state.listeners.onReset");
46 | confirmObjectPath("_inlineScripts.state.listeners.onLoad");
47 |
48 | // Determine root folder for state data-file (every time, in case sfiles are moved)
49 | confirmObjectPath("_inlineScripts.state.stateFile");
50 | let stateFileHasBeenSet = false;
51 | // Find the "state" sfile in the sfile's list...
52 | for (const sfile of _inlineScripts.inlineScripts.plugin.settings.shortcutFiles)
53 | {
54 | // Found the "state" sfile!
55 | if (sfile.address.endsWith("state.sfile.md"))
56 | {
57 | // Make the state data-file be stored right next to the state sfile
58 | const path = sfile.address.slice(0, -14) + STATE_FILE_NAME;
59 | // If the new path is different, change it and remember that it's been changed
60 | if (_inlineScripts.state.stateFile.path != path)
61 | {
62 | _inlineScripts.state.stateFile.path = path;
63 | stateFileHasBeenSet = true;
64 | }
65 | break;
66 | }
67 | }
68 |
69 | // Handle old versions of state data-file locations by finding the latest one and
70 | // copying it to the new state data-file. Then remove all existing old version files.
71 | // NOTE - this block is to upgrade old vaults to the latest state system version.
72 | if (!_inlineScripts.state.sessionState)
73 | {
74 | const oldStateFilePossibilities =
75 | [
76 | ".obsidian/plugins/obsidian-text-expander-js/state.data.txt",
77 | ".obsidian/plugins/obsidian-text-expander-js/state_onquit.data.txt",
78 | ".obsidian/plugins/obsidian-text-expander-js/state_auto.data.txt"
79 | ];
80 | let oldStateFiles = [];
81 | for (let i = oldStateFilePossibilities.length - 1; i >= 0; i--)
82 | {
83 | if (await app.vault.adapter.exists(oldStateFilePossibilities[i]))
84 | {
85 | oldStateFiles.push(oldStateFilePossibilities[i]);
86 | }
87 | }
88 | if (oldStateFiles.length > 1)
89 | {
90 | let times = [];
91 | for (let i = 0; i < oldStateFiles.length; i++)
92 | {
93 | times.push((await app.vault.adapter.stat(oldStateFiles[i])).mtime);
94 | }
95 | let latest = 0;
96 | let latestTime = times[0];
97 | for (let i = 1; i < oldStateFiles.length; i++)
98 | {
99 | if (times[i] > latestTime)
100 | {
101 | latest = i;
102 | latestTime = times[i];
103 | }
104 | }
105 | oldStateFiles = [ oldStateFiles[latest] ];
106 | }
107 | if (oldStateFiles.length == 1)
108 | {
109 | // Remove current state file (if it exists)
110 | await removeFile(_inlineScripts.state.stateFile.path);
111 | // Copy the latest old state file into the current state file
112 | await app.vault.adapter.copy(
113 | oldStateFiles[0],
114 | _inlineScripts.state.stateFile.path);
115 | stateFileHasBeenSet = true;
116 | // Delete oldStateFilePossibilities
117 | for (const oldStateFilePossibility of oldStateFilePossibilities)
118 | {
119 | await removeFile(oldStateFilePossibility);
120 | }
121 | }
122 | }
123 |
124 | // Load the state file if running initial setup, or if the state file has been moved
125 | if (!_inlineScripts.state.sessionState || stateFileHasBeenSet)
126 | {
127 | try
128 | {
129 | // Get the raw content
130 | const stateString =
131 | await app.vault.adapter.read(_inlineScripts.state.stateFile.path);
132 |
133 | // Parse the content into the state
134 | _inlineScripts.state.sessionState = JSON.parse(stateString);
135 |
136 | // Assign raw content to the "priorString" to recognize when state has changed
137 | _inlineScripts.state.stateFile.priorString = stateString;
138 |
139 | // Call event callbacks for the state.onLoad event
140 | _inlineScripts.inlineScripts.HelperFncs.callEventListenerCollection(
141 | "state.onLoad", _inlineScripts.state.listeners.onLoad);
142 | }
143 | catch (e)
144 | {
145 | // If parsing failed, create a console error
146 | if (e.code !== "ENOENT")
147 | {
148 | console.error(
149 | "state.sfile\n\tUnable to load from state file:\n\t" +
150 | _inlineScripts.state.stateFile.path + "\n\t", e);
151 | }
152 | // Make sure we have SOMETHING for the state
153 | _inlineScripts.state.sessionState ||= {};
154 | // Trigger a state save to make sure the state data-file is up to date
155 | expand("state save");
156 | }
157 | }
158 |
159 | // Event callback - inlineScripts.onExpansion - React to each user-triggered shortcut
160 | // expansion by checking for state changes and re-saving if there are any.
161 | confirmObjectPath(
162 | "_inlineScripts.inlineScripts.listeners.onExpansion.state",
163 | async (expansionInfo) =>
164 | {
165 | // Only re-save if the expansion is user-triggered
166 | if (expansionInfo.isUserTriggered)
167 | {
168 | // Check for state changes and re-save if there are any.
169 | expand("state save");
170 | }
171 | });
172 |
173 | // End the block to running this shortcut multiple times concurrently.
174 | _inlineScripts.state.inSetup = false;
175 | ```
176 | __
177 | Sets up this shortcut-file
178 |
179 |
180 | __
181 | ```
182 | ^sfile shutdown$
183 | ```
184 | __
185 | ```js
186 | // State removal
187 | delete _inlineScripts.state;
188 | ```
189 | __
190 | Shuts down this shortcut-file
191 |
192 |
193 | __
194 | ```
195 | ^state reset$
196 | ```
197 | __
198 | ```js
199 | // Confirm
200 | if (!popups.confirm("Confirm resetting the State system")) { return null; }
201 | if (!popups.confirm(
202 | "This will clear the state for all shortcut files!
Are you sure?"))
203 | {
204 | return null;
205 | }
206 |
207 | // Wipe out all states
208 | _inlineScripts.state.sessionState = {};
209 |
210 | // Notify listeners of state.onReset event
211 | _inlineScripts.inlineScripts.HelperFncs.callEventListenerCollection(
212 | "state.onReset", _inlineScripts.state.listeners.onReset);
213 |
214 | return expFormat("All state cleared.");
215 | ```
216 | __
217 | state reset - Clears all session state.
218 |
219 |
220 | __
221 | __
222 | ```js
223 | // The part of the "state get" expansion that is not the state data-string.
224 | // Used in "state get" and "state restore" shortcuts.
225 | const GET_MSG_PREFIX = "State:\n";
226 | ```
227 | __
228 | Helper script
229 |
230 |
231 | __
232 | ```
233 | ^state get$
234 | ```
235 | __
236 | ```js
237 | // Return the state data-string.
238 | // NOTE: don't use the expansion-format's prefix or line-prefix. This makes the
239 | // data-string is easier to select and copy in the note.
240 | return expFormat(
241 | GET_MSG_PREFIX + JSON.stringify(_inlineScripts.state.sessionState), true, true);
242 | ```
243 | __
244 | state get - Expands to a state-string - a string containing all data for the current session state.
245 |
246 |
247 | __
248 | ```
249 | ^state restore$
250 | ```
251 | __
252 | ```js
253 | // Get the content of the current note
254 | const content = await app.vault.cachedRead( app.workspace.getActiveFile());
255 |
256 | // Find the last expansion of "state get" in the content. Early out if no expansions
257 | const getMsgIndex = content.lastIndexOf(GET_MSG_PREFIX);
258 | if (getMsgIndex < 0)
259 | {
260 | return expFormat("State not loaded. Last state not found.");
261 | }
262 |
263 | // Get the end index of the data-string in the content. Early out if endIndex failed
264 | const endIndex = content.indexOf("\n", getMsgIndex + GET_MSG_PREFIX.length);
265 | if (endIndex < 0)
266 | {
267 | return expFormat("State not loaded. Last state has a bad format.");
268 | }
269 |
270 | // Get the data-String from the content.
271 | const stateString = content.slice(getMsgIndex + GET_MSG_PREFIX.length, endIndex);
272 |
273 | // Load the data-string into the state.
274 | return expand("state set " + stateString);
275 | ```
276 | __
277 | state restore - Loads the session state from the last "state get" shortcut expansion in the current note.
278 |
279 |
280 | __
281 | ```
282 | ^state set (.+)$
283 | ```
284 | __
285 | ```js
286 | // Try to parse the given data-string
287 | try
288 | {
289 | // If data-string parsed, assign to the state
290 | _inlineScripts.state.sessionState = JSON.parse($1);
291 | }
292 | catch (e)
293 | {
294 | // Notify the user of the failure to parse
295 | return expFormat([ "", "State not loaded. Invalid state:\n" + $1 ]);
296 | }
297 |
298 | // Call event callbacks for the state.onLoad event
299 | _inlineScripts.inlineScripts.HelperFncs.callEventListenerCollection(
300 | "state.onLoad", _inlineScripts.state.listeners.onLoad);
301 |
302 | return expFormat([ State set." ]);
303 | ```
304 | __
305 | state set {state: text} - Loads the session state from {state}, a state-string created with the "state get" shortcut.
306 |
307 |
308 | __
309 | ```
310 | ^state save$
311 | ```
312 | __
313 | ```js
314 | // Get the current state's data-string.
315 | const stateString = JSON.stringify(_inlineScripts.state.sessionState);
316 |
317 | // If the data-string hasn't changed, do nothing
318 | if (stateString === _inlineScripts.state.stateFile.priorString)
319 | {
320 | return expFormat("State unchanged since prior save");
321 | }
322 |
323 | // Write the data-string to the state data-file
324 | await app.vault.adapter.write(
325 | _inlineScripts.state.stateFile.path, stateString);
326 |
327 | // Remember the new data-string to check for future changes
328 | _inlineScripts.state.stateFile.priorString = stateString;
329 |
330 | return expFormat("State saved.");
331 | ```
332 | __
333 | state save - Store the state to file. This is called automatically after each shortcut expansion that triggers a state change.
334 |
--------------------------------------------------------------------------------
/notevars.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | This shortcut-file includes shortcuts to get and set note-variables. Note-variables are variables set in the YAML frontmatter. They can be used by many plugins, including DataView and TTRPG StatBlocks.
6 |
7 | This shortcut file can be used by other shortcut-files to let them read and manipulate data in notes for many uses, including TTRPG character sheets.
8 |
9 | This shortcut-file has a tutorial video available:
10 | - [Using the "notevars" shortcut-file to work with note variables](https://www.youtube.com/watch?v=EsV4WcMwhbA) (runtime 6:59)
11 |
12 | Uses __state.sfile__ shortcut-file (optional).
13 | It uses this to save & load the isMarkdownRefreshed flag.
14 |
15 |
16 | __
17 | ```
18 | ^sfile setup$
19 | ```
20 | __
21 | ```js
22 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
23 |
24 | // Initialize state (just a flag)
25 | confirmObjectPath(
26 | "_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed", true);
27 |
28 | // Event callback - state.onReset
29 | confirmObjectPath(
30 | "_inlineScripts.state.listeners.onReset.notevars",
31 | function()
32 | {
33 | expand("notevars reset noconfirm");
34 | });
35 | ```
36 | __
37 | Sets up this shortcut-file
38 |
39 |
40 | __
41 | ```
42 | ^sfile shutdown$
43 | ```
44 | __
45 | ```js
46 | // Event callback removal
47 | delete _inlineScripts.state?.listeners?.onReset?.notevars;
48 |
49 | // State removal
50 | delete _inlineScripts.state?.sessionState?.notevars;
51 | ```
52 | __
53 | Shuts down this shortcut-file
54 |
55 |
56 | __
57 | ```
58 | ^notevars reset$
59 | ```
60 | __
61 | ```js
62 | // Confirm
63 | if (!popups.confirm("Confirm resetting the Notevars system")) { return null; }
64 |
65 | // Reset
66 | expand("notevars reset noconfirm");
67 |
68 | return expFormat("Notevars cleared.");
69 | ```
70 | __
71 | notevars reset - Clears the isMarkdownRefreshed flag.
72 | ***
73 |
74 |
75 | __
76 | ```
77 | ^notevars reset noconfirm$
78 | ```
79 | __
80 | ```js
81 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
82 |
83 | // Reset the state
84 | confirmObjectPath("_inlineScripts.state.sessionState.notevars");
85 | _inlineScripts.state.sessionState.notevars.isMarkdownRefreshed = true;
86 | ```
87 | __
88 | hidden - No-confirm reset
89 |
90 |
91 | __
92 | ```
93 | ^notevars isMarkdownRefreshed ?(|[y|n])$
94 | ```
95 | __
96 | ```js
97 | // If parameter is specified, update the flag based on it
98 | if ($1)
99 | {
100 | _inlineScripts.state.sessionState.notevars.isMarkdownRefreshed = ($1 === "y");
101 | }
102 |
103 | // Return a notification announcing the flag's current state
104 | return expFormat(
105 | "notevars isMarkdownRefreshed is " +
106 | (_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed ?
107 | "__enabled__" : "__disabled__"));
108 | ```
109 | __
110 | notevars isMarkdownRefreshed {state: y OR n, default: ""} - If {state} is given, assigns it to the notevars "isMarkdownRefreshed" flag. Otherwise, displays the current "isMarkdownRefreshed" flag.
111 | - If isMarkdownRefreshed flag is set then a note's markdown is refreshed each time one of it's variables is set.
112 | ***
113 |
114 |
115 | __
116 | ```
117 | ^notevars get ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*@!].*"|[^ \t\['`\|\{\}">*@!][^ \t]*)$
118 | ```
119 | __
120 | ```js
121 | // Remove any quotes around parameters
122 | $1 = $1.replace(/^"(.*)"$/, "$1")
123 | $2 = $2.replace(/^"(.*)"$/, "$1")
124 |
125 | // If notename is ".", change it to the current file
126 | if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path; }
127 |
128 | // Get the file object for the specified note. Early if not available or is a folder
129 | const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
130 | if (!file)
131 | {
132 | return expFormat([ "", "No value. Note __" + $1 + "__ not found." ]);
133 | }
134 | if (file.children)
135 | {
136 | return expFormat(
137 | [ "", "No value. __" + $1 + "__ is a folder." ]);
138 | }
139 |
140 | // Get the file's cached data. Early out if not available.
141 | const cache = app.metadataCache.getFileCache(file);
142 | if (!cache)
143 | {
144 | return expFormat(
145 | [ "", "No value. Note __" + $1 + "__ is not properly cached by Obsidian." ]);
146 | }
147 |
148 | // Get the front-matter object
149 | // If no front-matter, early out (no message, since it technically worked, but the
150 | // variable is empty)
151 | const fm = cache.frontmatter;
152 | if (!fm)
153 | {
154 | return null;
155 | }
156 |
157 | // Get the variable valuable
158 | const result = fm[$2] || fm[$2 + ":"] || null;
159 |
160 | // Return the result. If it's an array, it's joined to a single string.
161 | return Array.isArray(result) ? result.join(",") : result;
162 | ```
163 | __
164 | notevars get {note name: path text} {variable name: yaml name text} - Expands to the value of variable {variable name} in note {note name}. If {note name} is "." then it represents the current note.
165 |
166 |
167 | __
168 | ```
169 | ^notevars getArray ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*@!].*"|[^ \t\['`\|\{\}">*@!][^ \t]*) ([0-9]+)$
170 | ```
171 | __
172 | ```js
173 | // Remove any quotes around parameters
174 | $1 = $1.replace(/^"(.*)"$/, "$1")
175 | $2 = $2.replace(/^"(.*)"$/, "$1")
176 |
177 | // If notename is ".", change it to the current file
178 | if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path; }
179 |
180 | // Get the file object for the specified note. Early out if unavailable, or folder
181 | const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
182 | if (!file)
183 | {
184 | return expFormat([ "", "No value. Note __" + $1 + "__ not found." ]);
185 | }
186 | if (file.children)
187 | {
188 | return expFormat(
189 | [ "", "No value. __" + $1 + "__ is a folder." ]);
190 | }
191 |
192 | // Get the file's cached data. Early out if unavailable
193 | const cache = app.metadataCache.getFileCache(file);
194 | if (!cache)
195 | {
196 | return expFormat(
197 | [ "", "No value. Note __" + $1 + "__ is not properly cached by " +
198 | "Obsidian." ]);
199 | }
200 |
201 | // Get the front-matter object
202 | // If no front-matter, early out (no message, since it technically worked, but the
203 | // variable is empty)
204 | const fm = cache.frontmatter;
205 | if (!fm)
206 | {
207 | return null;
208 | }
209 |
210 | // Get the variable valuable
211 | const result = fm[$2] || fm[$2 + ":"] || null;
212 |
213 | // Return the result, indexed by $3 (as an array)
214 | return Array.isArray(result) ? result[$3] : ($3 === "0") ? result : null;
215 | ```
216 | __
217 | notevars getArray {note name: path text} {array name: yaml name text} {index: >=0} - Expands to the value of item {index} of array {array name} in note {note name}. If {note name} is "." then it represents the current note.
218 |
219 |
220 | __
221 | ```
222 | ^notevars set ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*@!].*"|[^ \t\['`\|\{\}">*@!][^ \t]*) (.*)$
223 | ```
224 | __
225 | ```js
226 | // Remove any quotes around parameters
227 | $1 = $1.replace(/^"(.*)"$/, "$1")
228 | $2 = $2.replace(/^"(.*)"$/, "$1")
229 |
230 | // If notename is ".", change it to the current file
231 | if ($1 === ".") { $1 = app.workspace.getActiveFile()?.path || ""; }
232 |
233 | // Get the file object for the specified note. Early out if unavailable or is folder
234 | const file = app.vault.fileMap[$1] || app.vault.fileMap[$1 + ".md"];
235 | if (!file)
236 | {
237 | return expFormat(
238 | [ "", "Variable __" + $2 + "__ not set. Note __" + $1 + "__ not found." ]);
239 | }
240 | if (file.children)
241 | {
242 | return expFormat(
243 | [ "", "Variable __" + $2 + "__ not set. __" + $1 + "__ is a folder." ]);
244 | }
245 |
246 | // Get the file's content. Early out if unavailable.
247 | const content = await app.vault.cachedRead(file);
248 | if (content === null || content === undefined)
249 | {
250 | return expFormat(
251 | [ "", "Variable __" + $2 + "__ not set. Note __" + $1 + "__ read failed." ]);
252 | }
253 |
254 | // Adjust value to resolve escape characters for newline and tab
255 | $3 = $3.trim().replaceAll("\\n", "\n").replaceAll("\\t", "\t");
256 |
257 | // Get the file content's frontmatter
258 | const fmMatch = content.match(/^(\n*---)(\n[\S\s]*\n)(\n*---\n)/);
259 |
260 | // Is the new value multiline?
261 | const isValueMultiline = $3.includes("\n") || $3[0] === "-";
262 |
263 | // Determine what to add and where
264 | let toAdd = { text: "", start: 0, end: 0 };
265 |
266 | // If there isn't yet a frontmatter, create it along with the variable name/value pair
267 | if (!fmMatch)
268 | {
269 | // Define what to add
270 | toAdd.text = "---\n" + $2 + ": " + $3 + "\n---\n\n";
271 | }
272 |
273 | // If there IS a frontmatter, add the variable name/value pair to it
274 | else
275 | {
276 | // Find the variable in the frontmatter
277 | const varMatch =
278 | fmMatch[2].match("\n( *)(" + $2 + " *:)(\n|(?: |\t)[^\n]*\n)");
279 |
280 | // If the variable is NOT found append it to the frontmatter
281 | if (!varMatch)
282 | {
283 | toAdd.text = $2 + (
284 | isValueMultiline ?
285 | ":\n " + $3.replaceAll("\n", "\n ") + "\n" :
286 | ": " + $3 + "\n"
287 | );
288 | toAdd.start = fmMatch[1].length + fmMatch[2].length;
289 | toAdd.end = toAdd.start;
290 | }
291 |
292 | // If the variable IS found, modify it
293 | else
294 | {
295 | // Define value-entry
296 | const lineIndent = "\n" + varMatch[1] + " ";
297 | toAdd.text =
298 | isValueMultiline ?
299 | lineIndent + $3.replaceAll("\n", lineIndent) :
300 | " " + $3;
301 | // Define start of definition
302 | toAdd.start =
303 | fmMatch[1].length + varMatch.index + varMatch[1].length +
304 | varMatch[2].length + 1;
305 | // Define end of definition
306 | let lineStart = toAdd.start;
307 | let lineEnd = content.indexOf("\n", toAdd.start + 1);
308 | if (varMatch[3].trim())
309 | {
310 | // Old definition is single-line
311 | toAdd.end = lineEnd;
312 | }
313 | else
314 | {
315 | // Old definition is multi-line
316 | let lineStartRegex = new RegExp("^" + varMatch[1] + "(?:- | )");
317 | while (lineEnd !== -1)
318 | {
319 | var line = content.slice(lineStart + 1, lineEnd);
320 | if (!line.match(lineStartRegex))
321 | {
322 | toAdd.end = lineStart;
323 | break;
324 | }
325 | lineStart = lineEnd;
326 | lineEnd = content.indexOf("\n", lineStart + 1);
327 | }
328 | }
329 | }
330 | }
331 |
332 | // Force the reading views to update (dataview isn't currently working for this)
333 | if (_inlineScripts.state.sessionState.notevars.isMarkdownRefreshed)
334 | {
335 | const leaves = _inlineScripts.inlineScripts.HelperFncs.getLeavesForFile(file);
336 | if (leaves.length)
337 | {
338 | const onChanged = (changedFile) =>
339 | {
340 | // If the changed file is the one we're monitoring
341 | if (changedFile === file)
342 | {
343 | // Stop monitoring for file changes
344 | app.metadataCache.off("changed", onChanged);
345 |
346 | // Force all views of the file to re-render on the next frame
347 | setTimeout(() =>
348 | {
349 | for (const leaf of leaves)
350 | {
351 | leaf.view.modes.preview.rerender(true);
352 | }
353 | }, 100);
354 | }
355 | }
356 | app.metadataCache.on("changed", onChanged);
357 | }
358 | }
359 |
360 | // Modify the variable in the frontmatter of the note
361 | await _inlineScripts.inlineScripts.HelperFncs.addToNote(toAdd.text, toAdd, file.path);
362 |
363 | return expFormat(
364 | "Note __" + $1 + "__, variable __" + $2 + "__ set to __" + $3 + "__.");
365 | ```
366 | __
367 | notevars set {note name: path text} {variable name: yaml name text} {value: text} - Sets the value of variable {variable name} to {value} in note {note name}. If {note name} is "." then it represents the current note.
368 |
369 |
370 | __
371 | ```
372 | ^notevars setArray ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+) ("[^ \t\['`\|\{\}">*@!].*"|[^ \t\['`\|\{\}">*@!][^ \t]*) (.*)$
373 | ```
374 | __
375 | ```js
376 | // Remove any quotes around parameters
377 | $1 = $1.replace(/^"(.*)"$/, "$1")
378 | $2 = $2.replace(/^"(.*)"$/, "$1")
379 |
380 | // Get the input array
381 | const valueArray = $3.split(",").map(v => v.trim());
382 |
383 | // Call "notevars set" with the input array
384 | const result =
385 | expand("notevars set " + $1 + " " + $2 + " " + "- " + valueArray.join("\\n- "));
386 |
387 | // If error with "notevars set", return that error
388 | if (!result[0]) { return result; }
389 |
390 | return expFormat(
391 | "Note __" + $1 + "__, variable __" + $2 + "__ set to array __\\[ " +
392 | valueArray.join(", ") + " \\]__.");
393 | ```
394 | __
395 | notevars setArray {note name: path text} {array name: yaml name text} {values: text (comma separated)} - Sets the values of array {array name} to {value1}, {value2}, etc. in note {note name}. If {note name} is "." then it represents the current note.
396 |
--------------------------------------------------------------------------------
/une.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts for UNE: The Universal NPC Emulator. UNE is an excellent character generation system for tabletop role playing and general storytelling. It was designed by Zach Best.
6 | You can find more info about UNE at its [drivethrurpg page](https://www.drivethrurpg.com/product/134163/UNE-The-Universal-NPC-Emulator-rev).
7 |
8 | This shortcut-file has supplementary shortcut files:
9 | __une_ui.sfile__ - Graphical UI versions of some of these shortcuts.
10 |
11 |
12 | __
13 | __
14 | ```js
15 | // Make a roll from 1 to max.
16 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
17 |
18 | // Pick an item from array a.
19 | function aPick(a) { return a[roll(a.length)-1]; }
20 |
21 | // Pick an item from array a, weighted by element wIndex of the item. If theRoll is passed, use that as the roll.
22 | function aPickWeight(a, wIndex, theRoll)
23 | {
24 | wIndex = wIndex || 1;
25 | theRoll = theRoll || roll(a.last()[wIndex]);
26 | for (const item of a)
27 | {
28 | if (item[wIndex] >= theRoll)
29 | {
30 | return item;
31 | }
32 | }
33 | return a.last();
34 | }
35 | ```
36 | __
37 | Helper scripts
38 |
39 |
40 | __
41 | ```
42 | ^une ?(|[1-5]) ?(|[1-7]) ?(|[1-8])$
43 | ```
44 | __
45 | ```js
46 | // Combine 2 combo-results:
47 | // - each needs separate formatting, since separator mustn't have formatting
48 | // - The first needs the footer dropped, since it's not at the end anymore
49 | // - The second needs the header dropped, since it's not at the start anymore
50 | return "" +
51 | expFormat(expUnformat(expand("une character " + $1)), false, false, true) +
52 | "\n***\n" +
53 | expFormat(expUnformat(expand("une interact " + $2 + " " + $3)), true);
54 | ```
55 | __
56 | une {randomness: 1 TO 5 (order to chaos), default: 3 (standard)} {relationship to PC: 1 TO 7 (love to hate), default: 4 (neutral)} {demeanor: 1 TO 8 (scheming to prejudiced), default: random} - Runs "une character" and "une interact" together. {randomness} is a value for "une character". {relationship to PC} and {demeanor} are values for "une interact".
57 | ***
58 |
59 |
60 | __
61 | ```
62 | ^une character ?(|[1-5])$
63 | ```
64 | __
65 | ```js
66 | // Combine 3 results:
67 | // - remove their exp formatting and line-separated titles
68 | // - add new exp formatting and inline titles (except for motives)
69 | return expFormat("Character:" +
70 | "\nidentity: " + expUnformat(expand("une identity"))[1] +
71 | "\npower: " + expUnformat(expand("une power " + $1))[1] +
72 | "\nmotives:\n" + expUnformat(expand("une motive"))[1]
73 | );
74 | ```
75 | __
76 | une character {randomness: 1 TO 5 (order to chaos), default: 3 (standard)} - Runs "identity", "power" and "motive" together. {randomness} is a value for "power".
77 |
78 |
79 | __
80 | ```
81 | ^une interact ?(|[1-7]) ?(|[1-8])$
82 | ```
83 | __
84 | ```js
85 | // Combine 3 results:
86 | // - remove their exp formatting and line-separated titles
87 | // - add new exp formatting and inline titles
88 | return expFormat("Interact:" +
89 | "\nmood: " + expUnformat(expand("une mood " + $1))[1] +
90 | "\nbearing: " + expUnformat(expand("une bearing " + $2))[1] +
91 | "\nfocus: " + expUnformat(expand("une focus"))[1]
92 | );
93 | ```
94 | __
95 | une interact {relationship to PC: 1 TO 7 (love to hate), default: 4 (neutral)} {demeanor: 1 TO 8 (scheming to prejudiced), default: random} - Runs "mood", "bearing" and "focus" together. {relationship to PC} is a value for "mood". {demeanor} is a value for "bearing".
96 | ***
97 |
98 |
99 | __
100 | ```
101 | ^une identity$
102 | ```
103 | __
104 | ```js
105 | // Data
106 | const table1 =
107 | ["SUPERFLUOUS","ADDICTED","CONFORMIST","NEFARIOUS","SENSIBLE","UNTRAINED","ROMANTIC","UNREASONABLE","SKILLED","NEGLECTFUL","LIVELY","FORTHRIGHT","IDEALISTIC","UNSUPPORTIVE","RATIONAL","COARSE","FOOLISH","CUNNING","DELIGHTFUL","MISERLY","INEPT","BANAL","LOGICAL","SUBTLE","REPUTABLE","WICKED","LAZY","PESSIMISTIC","SOLEMN","HABITUAL","MEEK","HELPFUL","UNCONCERNED","GENEROUS","DOCILE","CHEERY","PRAGMATIC","SERENE","THOUGHTFUL","HOPELESS","PLEASANT","INSENSITIVE","TITLED","INEXPERIENCED","PRYING","OBLIVIOUS","REFINED","INDISPENSABLE","SCHOLARLY","CONSERVATIVE","UNCOUTH","WILLFUL","INDIFFERENT","FICKLE","ELDERLY","SINFUL","NAIVE","PRIVILEGED","GLUM","LIKABLE","LETHARGIC","DEFIANT","OBNOXIOUS","INSIGHTFUL","TACTLESS","FANATIC","PLEBEIAN","CHILDISH","PIOUS","UNEDUCATED","INCONSIDERATE","CULTURED","REVOLTING","CURIOUS","TOUCHY","NEEDY","DIGNIFIED","PUSHY","KIND","CORRUPT","JOVIAL","SHREWD","LIBERAL","COMPLIANT","DESTITUTE","CONNIVING","CAREFUL","ALLURING","DEFECTIVE","OPTIMISTIC","AFFLUENT","DESPONDENT","MINDLESS","PASSIONATE","DEVOTED","ESTABLISHED","UNSEEMLY","DEPENDABLE","RIGHTEOUS","CONFIDENT"];
108 | const table2 =
109 | ["GYPSY","WITCH","MERCHANT","EXPERT","COMMONER","JUDGE","RANGER","OCCULTIST","REVEREND","THUG","DRIFTER","JOURNEYMAN","STATESMAN","ASTROLOGER","DUELIST","JACK-OF-ALL-TRADES","ARISTOCRAT","PREACHER","ARTISAN","ROGUE","MISSIONARY","OUTCAST","MERCENARY","CARETAKER","HERMIT","ORATOR","CHIEFTAIN","PIONEER","BURGLAR","VICAR","OFFICER","EXPLORER","WARDEN","OUTLAW","ADEPT","BUM","SORCERER","LABORER","MASTER","ASCENDANT","VILLAGER","MAGUS","CONSCRIPT","WORKER","ACTOR","HERALD","HIGHWAYMAN","FORTUNE-HUNTER","GOVERNOR","SCRAPPER","MONK","HOMEMAKER","RECLUSE","STEWARD","POLYMATH","MAGICIAN","TRAVELER","VAGRANT","APPRENTICE","POLITICIAN","MEDIATOR","CROOK","CIVILIAN","ACTIVIST","HERO","CHAMPION","CLERIC","SLAVE","GUNMAN","CLAIRVOYANT","PATRIARCH","SHOPKEEPER","CRONE","ADVENTURER","SOLDIER","ENTERTAINER","CRAFTSMAN","SCIENTIST","ASCETIC","SUPERIOR","PERFORMER","MAGISTER","SERF","BRUTE","INQUISITOR","LORD","VILLAIN","PROFESSOR","SERVANT","CHARMER","GLOBETROTTER","SNIPER","COURTIER","PRIEST","TRADESMAN","HITMAN","WIZARD","BEGGAR","TRADESMAN","WARRIOR"];
110 |
111 | // Simply combine a roll of table 1 and a roll of table 2
112 | return expFormat([ "NPC identity:\n", aPick(table1) + " " + aPick(table2) ]);
113 | ```
114 | __
115 | une identity - Generates a 2-word description for an NPC.
116 |
117 |
118 | __
119 | ```
120 | ^une power ?(|[1-5])$
121 | ```
122 | __
123 | ```js
124 | // The parameter defaults to 3
125 | $1 = Number($1) || 3;
126 |
127 | // Data
128 | const table3 =
129 | [ ["MUCH WEAKER",2,4,5,8,12],["SLIGHTLY WEAKER",10,15,20,25,30],["COMPARABLE",90,85,80,75,70],["SLIGHTLY STRONGER",98,96,95,92,88],["MUCH STRONGER",100,100,100,100,100] ];
130 |
131 | // Result is a weighted roll of table3, which weight being defined by the parameter
132 | return expFormat([ "NPC power level:\n", aPickWeight(table3, $1)[0] ]);
133 | ```
134 | __
135 | une power {randomness: 1 TO 5 (order to chaos), default: 3 (standard)} - Generates an NPC's power level relative to PC's power level, based on {randomness}.
136 |
137 |
138 | __
139 | ```
140 | ^une motive$
141 | ```
142 | __
143 | ```js
144 | // Data
145 | const table4 =
146 | ["ADVISE","OBTAIN","ATTEMPT","SPOIL","OPPRESS","INTERACT","CREATE","ABDUCT","PROMOTE","CONCEIVE","BLIGHT","PROGRESS","DISTRESS","POSSESS","RECORD","EMBRACE","CONTACT","PURSUE","ASSOCIATE","PREPARE","SHEPHERD","ABUSE","INDULGE","CHRONICLE","FULFILL","DRIVE","REVIEW","AID","FOLLOW","ADVANCE","GUARD","CONQUER","HINDER","PLUNDER","CONSTRUCT","ENCOURAGE","AGONIZE","COMPREHEND","ADMINISTER","RELATE","TAKE","DISCOVER","DETER","ACQUIRE","DAMAGE","PUBLICIZE","BURDEN","ADVOCATE","IMPLEMENT","UNDERSTAND","COLLABORATE","STRIVE","COMPLETE","COMPEL","JOIN","ASSIST","DEFILE","PRODUCE","INSTITUTE","ACCOUNT","WORK","ACCOMPANY","OFFEND","GUIDE","LEARN","PERSECUTE","COMMUNICATE","PROCESS","REPORT","DEVELOP","STEAL","SUGGEST","WEAKEN","ACHIEVE","SECURE","INFORM","PATRONIZE","DEPRESS","DETERMINE","SEEK","MANAGE","SUPPRESS","PROCLAIM","OPERATE","ACCESS","REFINE","COMPOSE","UNDERMINE","EXPLAIN","DISCOURAGE","ATTEND","DETECT","EXECUTE","MAINTAIN","REALIZE","CONVEY","ROB","ESTABLISH","OVERTHROW","SUPPORT"];
147 | const table5 =
148 | ["WEALTH","HARDSHIP","AFFLUENCE","RESOURCES","PROSPERITY","POVERTY","OPULENCE","DEPRIVATION","SUCCESS","DISTRESS","CONTRABAND","MUSIC","LITERATURE","TECHNOLOGY","ALCOHOL","MEDICINES","BEAUTY","STRENGTH","INTELLIGENCE","FORCE","THE_WEALTHY","THE_POPULOUS","ENEMIES","THE_PUBLIC","RELIGION","THE_POOR","FAMILY","THE_ELITE","ACADEMIA","THE_FORSAKEN","THE_LAW","THE_GOVERNMENT","THE_OPPRESSED","FRIENDS","CRIMINALS","ALLIES","SECRET_SOCIETIES","THE_WORLD","MILITARY","THE_CHURCH","DREAMS","DISCRETION","LOVE","FREEDOM","PAIN","FAITH","SLAVERY","ENLIGHTENMENT","RACISM","SENSUALITY","DISSONANCE","PEACE","DISCRIMINATION","DISBELIEF","PLEASURE","HATE","HAPPINESS","SERVITUDE","HARMONY","JUSTICE","GLUTTONY","LUST","ENVY","GREED","LAZINESS","WRATH","PRIDE","PURITY","MODERATION","VIGILANCE","ZEAL","COMPOSURE","CHARITY","MODESTY","ATROCITIES","COWARDICE","NARCISSISM","COMPASSION","VALOR","PATIENCE","ADVICE","PROPAGANDA","SCIENCE","KNOWLEDGE","COMMUNICATIONS","LIES","MYTHS","RIDDLES","STORIES","LEGENDS","INDUSTRY","NEW_RELIGIONS","PROGRESS","ANIMALS","GHOSTS","MAGIC","NATURE","OLD_RELIGIONS","EXPERTISE","SPIRITS"];
149 |
150 | // Helper function - Get the table column of a table index
151 | function tableColumn(index) { return Math.trunc(index / 20); }
152 |
153 | // Result is three motives
154 | // a motive is: (a) a roll on table1 plus (b) a roll on table2 with unique column
155 | let result = [];
156 |
157 | // Track noun rolls to make sure they all have unique columns
158 | let priorNounRolls = [];
159 |
160 | // Roll the three motives
161 | for (let i = 0; i < 3; i++)
162 | {
163 | // Fill this with a noun roll with a unique column
164 | let nounRoll;
165 |
166 | // Find a noun roll with a unique column
167 | do
168 | {
169 | // Pick a random noun roll
170 | nounRoll = roll(100);
171 |
172 | // Check if the noun roll shares a previous noun roll's column
173 | let hasAUniqueColumn = true;
174 | for (let i = 0; i < priorNounRolls.length; i++)
175 | {
176 | if (tableColumn(priorNounRolls[i] - 1) === tableColumn(nounRoll - 1))
177 | {
178 | hasAUniqueColumn = false;
179 | break;
180 | }
181 | }
182 |
183 | // If we found a noun roll with a unique column, stop searching
184 | if (hasAUniqueColumn) { break; }
185 | }
186 | while (true);
187 |
188 | // Add a new motive (with the uniquely columned noun roll) to the list of motives
189 | result.push(aPick(table4) + " " + table5[nounRoll-1]);
190 |
191 | // Add the noun roll to the list of prior nouns rolls
192 | priorNounRolls.push(nounRoll);
193 | }
194 |
195 | return expFormat([ "NPC motives:\n", ". " + result.join("\n. ") ]);
196 | ```
197 | __
198 | une motive - Generates three 2-word descriptions for an NPC's motivations.
199 | ***
200 |
201 |
202 | __
203 | ```
204 | ^une mood ?(|[1-7])$
205 | ```
206 | __
207 | ```js
208 | // Parameter defaults to 4
209 | $1 = Number($1) || 4;
210 |
211 | // Data
212 | const table6 = [
213 | ["WITHDRAWN",1,2,3,5,7,11,15],
214 | ["GUARDED",6,8,11,15,18,24,30],
215 | ["CAUTIOUS",16,20,25,30,46,61,69],
216 | ["NEUTRAL",31,40,55,60,76,81,84],
217 | ["SOCIABLE",70,76,82,85,90,93,94],
218 | ["HELPFUL",85,89,93,95,97,98,99],
219 | ["FORTHCOMING",100,100,100,100,100,100,100] ];
220 |
221 | // Result is a weighted roll of table6, which weight being defined by the parameter
222 | return expFormat([ "NPC mood:\n", aPickWeight(table6, $1)[0] ]);
223 | ```
224 | __
225 | une mood {relationship to PC: 1 TO 7 (love to hate), default: 4 (neutral)} - Generates an NPC's willingness to socialize for this interaction, based on {relationship to PC}.
226 |
227 |
228 | __
229 | ```
230 | ^une bearing ?(|[1-8])$
231 | ```
232 | __
233 | ```js
234 | // Parameter defaults to a random number from 1 to 8
235 | $1 = Number($1) || roll(8)
236 |
237 | // Data
238 | const table7a = ["SCHEMING", "INSANE", "FRIENDLY", "HOSTILE", "INQUISITIVE", "KNOWING", "MYSTERIOUS", "PREJUDICED"];
239 | const table7b = [
240 | ["INTENT","BARGAIN","MEANS","PROPOSITION","PLAN","COMPROMISE","AGENDA","ARRANGEMENT","NEGOTIATION","PLOT"],
241 | ["MADNESS","FEAR","ACCIDENT","CHAOS","IDIOCY","ILLUSION","TURMOIL","CONFUSION","FACADE","BEWILDERMENT"],
242 | ["ALLIANCE","COMFORT","GRATITUDE","SHELTER","HAPPINESS","SUPPORT","PROMISE","DELIGHT","AID","CELEBRATION"],
243 | ["DEATH","CAPTURE","JUDGMENT","COMBAT","SURRENDER","RAGE","RESENTMENT","SUBMISSION","INJURY","DESTRUCTION"],
244 | ["QUESTIONS","INVESTIGATION","INTEREST","DEMAND","SUSPICION","REQUEST","CURIOSITY","SKEPTICISM","COMMAND","PETITION"],
245 | ["REPORT","EFFECTS","EXAMINATION","RECORDS","ACCOUNT","NEWS","HISTORY","TELLING","DISCOURSE","SPEECH"],
246 | ["RUMOR","UNCERTAINTY","SECRETS","MISDIRECTION","WHISPERS","LIES","SHADOWS","ENIGMA","OBSCURITY","CONUNDRUM"],
247 | ["REPUTATION","DOUBT","BIAS","DISLIKE","PARTIALITY","BELIEF","VIEW","DISCRIMINATION","ASSESSMENT","DIFFERENCE"] ];
248 |
249 | // Result is based on demeanor and bearing. Demeanor is parameter 1, bearing is a roll
250 | let demeanor = $1
251 | let bearing = roll(10);
252 |
253 | // Result is an element from the 2-d table 7b, chosen by demeanor and bearing.
254 | // Also show the demeanor as a text from table7a
255 | let result = table7a[demeanor-1] + " - " + table7b[demeanor-1][bearing-1];
256 |
257 | return expFormat([ "NPC bearing:\n", result ]);
258 | ```
259 | __
260 | une bearing {demeanor: 1 TO 8 (scheming to prejudiced), default: random} - Generates an NPC's attitude for this interaction, based on {demeanor}: a number defaulting to random and meaning one of the following:
261 | 1 - sceming 2 - insane 3 - friendly 4 - hostile
262 | 5 - inquisitive 6 - knowing 7 - mysterious 8 - prejudiced
263 |
264 |
265 | __
266 | ```
267 | ^une focus$
268 | ```
269 | __
270 | ```js
271 | // Data
272 | const table8 = [ ["CURRENT_SCENE", 3],["LAST_STORY",6],["EQUIPMENT",9],["PARENTS",12],["HISTORY",15],["RETAINERS",18],["WEALTH",21],["RELICS",24],["LAST_ACTION",27],["SKILLS",30],["SUPERIORS",33],["FAME",36],["CAMPAIGN",39],["FUTURE_ACTION",42],["FRIENDS",45],["ALLIES",48],["LAST_SCENE",51],["CONTACTS",54],["FLAWS",57],["ANTAGONIST",60],["REWARDS",63],["EXPERIENCE",66],["KNOWLEDGE",69],["RECENT_SCENE",72],["COMMUNITY",75],["TREASURE",78],["THE_CHARACTER",81],["CURRENT_STORY",84],["FAMILY",87],["POWER",90],["WEAPONS",93],["PREVIOUS_SCENE",96],["ENEMY",100] ];
273 |
274 | // Result is a simple weighted roll
275 | let result = "THE PC'S " + aPickWeight(table8)[0];
276 |
277 | return expFormat([ "NPC focus:\n", result ]);
278 | ```
279 | __
280 | une focus - Generates an NPC's primary interest for this interaction.
281 |
--------------------------------------------------------------------------------
/cards_pileviewer.sfile.md:
--------------------------------------------------------------------------------
1 | Adds an Obsidian panel type that lets you view card-piles.
2 |
3 | This shortcut-file has a tutorial video available:
4 | [Using the "cards" shortcut-file to use virtual cards](https://www.youtube.com/watch?v=KkpjTL2UvtQ) (runtime 12:43)
5 |
6 |
7 | __
8 | __
9 | ```js
10 | // This is referred to by a few different systems
11 | const CARDPILE_VIEW_TYPE = "inline-scripts-cardpile-view";
12 |
13 | // Get the current back-image, url
14 | function getBackImage()
15 | {
16 | return _inlineScripts.state.sessionState.cards.backImage ||
17 | _inlineScripts.cards.defaultBackImage;
18 | }
19 |
20 | // Turn a relative path url into an absolute path url based in the vault's root
21 | function getAbsolutePath(path)
22 | {
23 | if (path.startsWith("data:image")) { return path; }
24 | path = app.vault.fileMap[path];
25 | if (!path) { return ""; }
26 | return app.vault.getResourcePath(path);
27 | }
28 |
29 | // Create a block of html to represent a specific card
30 | function createCardUi(isFaceUp, card, id, scale, includeDataSrc)
31 | {
32 | const result = document.createElement("img");
33 | result.classList.add("cardUi");
34 | result.src = getAbsolutePath(isFaceUp ? card.path : getBackImage());
35 | const size = _inlineScripts.state.sessionState.cards.size * (scale || 1.0);
36 | result.style.width = size + "px";
37 | result.style.height = (size * card.aspect) + "px";
38 | result.dataset.id = id;
39 | if (includeDataSrc)
40 | {
41 | result.dataset.src = getAbsolutePath(card.path);
42 | }
43 | if (isFaceUp)
44 | {
45 | switch (card.rotation)
46 | {
47 | case 1: result.classList.add("rotated1"); break;
48 | case 2: result.classList.add("rotated2"); break;
49 | case 3: result.classList.add("rotated3"); break;
50 | }
51 | }
52 | return result;
53 | }
54 | ```
55 | __
56 | Helper scripts
57 |
58 |
59 | __
60 | ```
61 | ^sfile setup$
62 | ```
63 | __
64 | ```js
65 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
66 |
67 | // Event callbacks for cards system events
68 | confirmObjectPath(
69 | "_inlineScripts.cards.listeners.onPileListChanged.cards_pileViewer",
70 | function()
71 | {
72 | // React to pile list changes by updating the dropdown list
73 | const leaves = app.workspace.getLeavesOfType(CARDPILE_VIEW_TYPE);
74 | for (const leaf of leaves)
75 | {
76 | leaf.view.refreshPileList();
77 | }
78 | });
79 | confirmObjectPath(
80 | "_inlineScripts.cards.listeners.onPileChanged.cards_pileViewer",
81 | function()
82 | {
83 | // React to pile changes by refreshing the current pile of every view
84 | const leaves = app.workspace.getLeavesOfType(CARDPILE_VIEW_TYPE);
85 | for (const leaf of leaves)
86 | {
87 | leaf.view.refreshPile();
88 | }
89 | });
90 |
91 | // Custom CSS
92 | _inlineScripts.inlineScripts.HelperFncs.addCss("cards_pileviewer", ".iscript_pileViewer_container { display: flex; flex-direction: column; padding: 0.5em !important; } .iscript_pileViewer_header { display: flex; margin-bottom: 0.5em; } .iscript_pileViewer_content { overflow-y: scroll; } .iscript_pileViewer_select { flex-grow: 1; margin-right: 0.25em; } .iscript_emptyMsg { text-align: center; margin-top: 1em; font-weight: bold; color: grey } .iscript_notDragged { filter: brightness(50%); } .iscript_viewerCardUi { margin: .1em; -webkit-user-drag: none; }");
93 |
94 | // Panel icon
95 | _inlineScripts.inlineScripts.HelperFncs.addIcon(CARDPILE_VIEW_TYPE, `
96 | `);
112 |
113 | // Only register this viewer panel registration once per Obsidian session
114 | if (_inlineScripts.inlineScripts.hasRegisteredCardPileView) { return; }
115 | _inlineScripts.inlineScripts.hasRegisteredCardPileView = true;
116 |
117 | // This viewer panel's class for registration
118 | class CardPileView extends _inlineScripts.inlineScripts.HelperFncs.ItemView
119 | {
120 | // Member vars
121 | pileSelect;
122 | zoomSelect;
123 | cardDisplay;
124 | dragReorder;
125 | _onDragReordered;
126 |
127 | constructor(leaf)
128 | {
129 | super(leaf);
130 | this._onDragReordered = this.onDragReordered.bind(this);
131 | }
132 |
133 | // Initial setup
134 | load()
135 | {
136 | // UI element creation
137 | const root = this.containerEl.children[1];
138 | root.style.overflow = "unset";
139 | root.classList.add("iscript_pileViewer_container");
140 | // Row for the pile select and the zoom select
141 | const header = document.createElement('div');
142 | header.classList.add("iscript_pileViewer_header");
143 | root.appendChild(header);
144 | // The pile select for selecting the current pile
145 | this.pileSelect = document.createElement("select");
146 | this.pileSelect.classList.add("iscript_pileViewer_select");
147 | this.pileSelect.options[this.pileSelect.options.length] = new Option("");
148 | this.pileSelect.onchange = () => { this.refreshPile(true); }
149 | header.appendChild(this.pileSelect);
150 | // The zoom select for selecting the current zoom
151 | this.zoomSelect = document.createElement("select");
152 | this.zoomSelect.options[0] = new Option("25%");
153 | this.zoomSelect.options[1] = new Option("50%");
154 | this.zoomSelect.options[2] = new Option("75%");
155 | this.zoomSelect.options[3] = new Option("100%", undefined, undefined, true);
156 | this.zoomSelect.options[4] = new Option("200%");
157 | this.zoomSelect.options[5] = new Option("300%");
158 | this.zoomSelect.onchange = () =>
159 | {
160 | this.refreshPile(true);
161 | }
162 | header.appendChild(this.zoomSelect);
163 | // Main space for card visualization UIs
164 | this.cardDisplay = document.createElement("div");
165 | this.cardDisplay.classList.add("iscript_pileViewer_content");
166 | root.appendChild(this.cardDisplay);
167 |
168 | // First refresh of list of piles in the select ui
169 | this.refreshPileList();
170 | }
171 |
172 | // Update the list of piles in the select ui
173 | refreshPileList()
174 | {
175 | // Remember the current pile while the select is being re-populated
176 | const oldValue = this.pileSelect.value;
177 |
178 | // Clear the select
179 | this.pileSelect.options.length = 0;
180 |
181 | // Add a blank option
182 | this.pileSelect.options[this.pileSelect.options.length] = new Option("");
183 |
184 | // Add an option for each pile
185 | const piles = Object.keys(_inlineScripts.state.sessionState.cards.piles);
186 | piles.sort();
187 | for (const pile of piles)
188 | {
189 | this.pileSelect.options[this.pileSelect.options.length] =
190 | new Option(pile);
191 | }
192 |
193 | // Restore the current pile. If not available, clear the pile view (refresh).
194 | this.pileSelect.value = oldValue;
195 | if (this.pileSelect.value !== oldValue)
196 | {
197 | this.refreshPile(true);
198 | }
199 | }
200 |
201 | // Update the visualization of the current pile being viewed
202 | refreshPile(force)
203 | {
204 | // Only refresh if being forced, or if the pile that changed is specified and
205 | // is the current pile
206 | if (!force && _inlineScripts.cards.listeners.changedPile &&
207 | _inlineScripts.cards.listeners.changedPile !== this.pileSelect.value)
208 | {
209 | return;
210 | }
211 |
212 | // Clear the view to repopulate it
213 | this.cardDisplay.innerText = "";
214 |
215 | // Get the current pile
216 | const pile =
217 | _inlineScripts.state.sessionState.cards.piles[this.pileSelect.value];
218 | if (!pile) { return; }
219 |
220 | // If pile has no cards, add a ui saying so, then early out
221 | if (!pile.cards.length)
222 | {
223 | let emptyMsg = document.createElement("div");
224 | emptyMsg.innerText = "-- Empty card-pile --";
225 | emptyMsg.classList.add("iscript_emptyMsg");
226 | this.cardDisplay.append(emptyMsg);
227 | return;
228 | }
229 |
230 | // Work out the zoom level
231 | const zoom = Number(this.zoomSelect.value.slice(0,-1)) / 100.0;
232 |
233 | // Add a visualization ui for each card in the current pile
234 | for (let i = pile.cards.length-1; i >= 0; i--)
235 | {
236 | let cardUi = createCardUi(pile.isFaceUp, pile.cards[i], i, zoom);
237 | cardUi.classList.add("iscript_viewerCardUi");
238 | this.cardDisplay.append(cardUi);
239 |
240 | // Card ui double-click triggers rotation of the card
241 | cardUi.addEventListener("dblclick", async () =>
242 | {
243 | cardUi.classList.remove("rotated" + pile.cards[i].rotation);
244 | pile.cards[i].rotation++;
245 | if (pile.cards[i].aspect !== 1)
246 | {
247 | pile.cards[i].rotation++;
248 | }
249 | if (pile.cards[i].rotation > 3)
250 | {
251 | pile.cards[i].rotation = 0;
252 | }
253 | cardUi.classList.add("rotated" + pile.cards[i].rotation);
254 |
255 | // Save the state
256 | expand("state save");
257 | });
258 | }
259 |
260 | // Setup a drag system to handle drag-reordering the cards
261 | this.dragReorder =
262 | new _inlineScripts.inlineScripts.HelperFncs.DragReorder(
263 | this.cardDisplay, this._onDragReordered);
264 | }
265 |
266 | // Event callback for reorder event of the drag system
267 | async onDragReordered()
268 | {
269 | // Get the current pile
270 | const pile =
271 | _inlineScripts.state.sessionState.cards.piles[this.pileSelect.value];
272 |
273 | // Create a new cards array, based on the old one, but with the new card order
274 | let newCards = [];
275 | for (let i = this.cardDisplay.childNodes.length - 1; i >= 0; i--)
276 | {
277 | newCards.push(pile.cards[this.cardDisplay.childNodes[i].dataset.id]);
278 | this.cardDisplay.childNodes[i].dataset.id = newCards.length-1;
279 | }
280 |
281 | // Put new cards array in the current pile
282 | pile.cards = newCards;
283 |
284 | // Save the state
285 | expand("state save");
286 | }
287 |
288 | // Return the string id for this panel type
289 | getViewType() { return CARDPILE_VIEW_TYPE; }
290 | // Return the caption for this panel type
291 | getDisplayText() { return "Inline Scripts - Card-pile view"; }
292 | // Return the string id for the iconfor this panel type
293 | getIcon() { return CARDPILE_VIEW_TYPE; }
294 | }
295 |
296 | // Register the panel type with the above class
297 | _inlineScripts.inlineScripts.HelperFncs.registerView(
298 | CARDPILE_VIEW_TYPE, leaf => new CardPileView(leaf));
299 | ```
300 | __
301 | Sets up this shortcut-file
302 |
303 |
304 | __
305 | ```
306 | ^sfile shutdown$
307 | ```
308 | __
309 | ```js
310 | // Event callback removal
311 | delete _inlineScripts.cards?.listeners?.onPileListChanged?.cards_pileViewer;
312 | delete _inlineScripts.cards?.listeners?.onPileChanged?.cards_pileViewer;
313 |
314 | // Custom CSS removal
315 | _inlineScripts.inlineScripts.HelperFncs.removeCss("cards_pileviewer");
316 |
317 | // UI removal
318 | app.workspace.detachLeavesOfType(CARDPILE_VIEW_TYPE);
319 |
320 | ```
321 | __
322 | Shuts down this shortcut-file
323 |
324 |
325 | __
326 | ```
327 | ^cards? open viewer$
328 | ```
329 | __
330 | ```js
331 | await app.workspace.getRightLeaf(false).setViewState({ type: CARDPILE_VIEW_TYPE });
332 | return null;
333 | ```
334 | __
335 | cards open viewer - Open a panel for viewing card-piles.
336 |
--------------------------------------------------------------------------------
/lists_ui.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | An extension to __lists.sfile__ that provides graphical ui versions of shortcuts.
6 |
7 |
8 | __
9 | ```
10 | ^ui lists? reset$
11 | ```
12 | __
13 | ```js
14 | return expand("list reset");
15 | ```
16 | __
17 | ui lists reset - Clears all lists.
18 | ***
19 |
20 |
21 | __
22 | ```
23 | ^ui lists?$
24 | ```
25 | __
26 | ```js
27 | // Get the list of list names, early out empty
28 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
29 | if (!lists.length)
30 | {
31 | return expand("lists");
32 | }
33 |
34 | // Add "all lists" option
35 | lists.unshift("");
36 |
37 | // Choose a list (or "all lists")
38 | const pick = popups.pick("Choose a list to view", lists, 0, "adaptive");
39 | if (pick === null) { return null; }
40 |
41 | // return expansion of the list info (or ALL lists if pick is 0)
42 | return expand("lists" + (pick ? (" " + lists[pick]) : ""));
43 | ```
44 | __
45 | ui lists - User chooses a list (options include "all lists"), then shows info on the chosen list.
46 |
47 |
48 | __
49 | ```
50 | ^ui lists? rename$
51 | ```
52 | __
53 | ```js
54 | // Get the list of list names, early out if empty
55 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
56 | if (!lists.length)
57 | {
58 | return expFormat("List not renamed. No lists to rename.");
59 | }
60 |
61 | // Choose a list
62 | const pick = popups.pick("Choose a list to rename", lists, 0, "adaptive");
63 | if (pick === null) { return null; }
64 |
65 | // Enter a new name, early out if not in a valid format
66 | const newName =
67 | popups.input("Enter a new name for __" + lists[pick] + "__", lists[pick]);
68 | if (newName === null) { return null; }
69 | if (!newName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/))
70 | {
71 | return expFormat(
72 | "List item not added. List name __\"" + newName + "\"_ isn't valid.");
73 | }
74 |
75 | // If newName IS oldName, do nothing
76 | if (newName === lists[pick]) { return null; }
77 |
78 | return expand("lists rename " + lists[pick] + " " + newName);
79 | ```
80 | __
81 | ui lists rename - User chooses a list, and enters a new name for it. The list is renamed.
82 |
83 |
84 | __
85 | ```
86 | ^ui lists? removelist$
87 | ```
88 | __
89 | ```js
90 | // Get the list of list names, early out if empty
91 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
92 | if (!lists.length)
93 | {
94 | return expFormat("List not removed. No lists to remove.");
95 | }
96 |
97 | // Choose a list
98 | const pick = popups.pick("Choose a list to remove", lists, 0, "adaptive");
99 | if (pick === null) { return null; }
100 |
101 | return expand("lists removelist " + lists[pick]);
102 | ```
103 | __
104 | ui lists removelist - User chooses a list, which is removed.
105 |
106 |
107 | __
108 | __
109 | ```js
110 | // get the items of a given list, regardless of list type.
111 | async function getListItems(name)
112 | {
113 | return expand("lists listraw " + name);
114 | }
115 |
116 | // returns the names of lists that have items.
117 | async function getNamesOfPopulatedLists()
118 | {
119 | let result = Object.keys(_inlineScripts.state.sessionState.lists).sort();
120 | return await _inlineScripts.inlineScripts.HelperFncs.asyncFilter(
121 | result, async v => (await getListItems(v)).length);
122 | };
123 |
124 | // Filter list names to those of basic and combo type
125 | function filterListsToBasicAndCombo(lists)
126 | {
127 | return lists.filter(v =>
128 | {
129 | const type = _inlineScripts.state.sessionState.lists[v].type;
130 | return (type === "basic" || type === "combo");
131 | });
132 | }
133 | ```
134 | __
135 | Helper scripts
136 |
137 |
138 | __
139 | ```
140 | ^ui lists? pick$
141 | ```
142 | __
143 | ```js
144 | // Get the list of populated list's names, early out if empty
145 | const lists = await getNamesOfPopulatedLists();
146 | if (!lists.length)
147 | {
148 | return expFormat("No item picked. There are no non-empty lists.");
149 | }
150 |
151 | // Choose a list
152 | const pick =
153 | popups.pick("Choose a list to randomly pick from", lists, 0, "adaptive");
154 | if (pick === null) { return null; }
155 |
156 | return expand("lists pick " + lists[pick]);
157 | ```
158 | __
159 | ui lists pick - User chooses a non-empty list, a random item is picked from that list and returned.
160 | ***
161 |
162 |
163 | __
164 | ```
165 | ^ui lists? add$
166 | ```
167 | __
168 | ```js
169 | // Get the list of list names
170 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
171 |
172 | // Only include lists of basic and combo type
173 | lists = filterListsToBasicAndCombo(lists);
174 |
175 | // Choice defaults to 0 (which means creating a new list to add to)
176 | let listName = "";
177 | let listItems = [];
178 |
179 | // If choices aren't empty, ask user to choose which list to add to
180 | if (lists.length)
181 | {
182 | // Add option for a new list
183 | lists.unshift("");
184 |
185 | // Choose a list
186 | const pick = popups.pick("Choose a list to add to", lists, 0, "adaptive");
187 | if (pick === null) { return null; }
188 |
189 | // If user's choice wasn't "new list", record the choice
190 | if (pick > 0)
191 | {
192 | listName = lists[pick];
193 | listItems = [...new Set(await getListItems(listName))];
194 | }
195 | }
196 |
197 | // If choice was "new list", OR there are no lists, get a name for the new list.
198 | if (listName === "")
199 | {
200 | // Enter name, early out if not in a valid format
201 | listName = popups.input("Enter a list to add to");
202 | if (!listName) { return null; }
203 | if (!listName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/))
204 | {
205 | return expFormat(
206 | "List item not added. List name __\"" + listName + "\"_ isn't valid.");
207 | }
208 | }
209 |
210 | // Get the item to add to the chosen list
211 | const item =
212 | popups.input("Type up an item to add to list " + listName + "", "",
213 | listItems);
214 | if (!item) { return null; }
215 |
216 | return expand("lists add " + listName + " " + item);
217 | ```
218 | __
219 | ui lists add - User chooses a list to add to and an item to add. The item is added to the list.
220 | - Identical items within a list are allowed.
221 | - Can only add to (1) basic lists and (2) combo lists with at least one basic list as a sub-list.
222 |
223 |
224 | __
225 | ```
226 | ^ui lists? replace$
227 | ```
228 | __
229 | ```js
230 | // Get the list of populated list's names
231 | let lists = await getNamesOfPopulatedLists();
232 |
233 | // Only include lists of basic and combo type, early out if empty
234 | lists = filterListsToBasicAndCombo(lists);
235 | if (!lists.length)
236 | {
237 | return expFormat("No item replaced. There are no non-empty lists.");
238 | }
239 |
240 | // Choose a list
241 | const pick = popups.pick("Choose a list to replace items from", lists, 0, "adaptive");
242 | if (pick === null) { return null; }
243 |
244 | // Get items from the list
245 | let items = await getListItems(lists[pick]);
246 |
247 | // Count diplicate items
248 | let itemCounts = {};
249 | for (const item of items)
250 | {
251 | itemCounts[item] = (itemCounts[item] || 0) + 1;
252 | }
253 |
254 | // Remove duplicate items
255 | items = [...new Set(items)];
256 | let itemLabels = [...items];
257 |
258 | // Add count to items with duplicates
259 | for (let i = 0; i < items.length; i++)
260 | {
261 | const count = itemCounts[items[i]];
262 | itemLabels[i] = items[i] + (count <= 1 ? "" : " (x " + count + ")");
263 | }
264 |
265 | // Choose an item
266 | const pick2 = popups.pick("Choose an item to replace.", itemLabels, 0, "adaptive");
267 | if (pick2 === null) { return null; }
268 | const item = items[pick2];
269 |
270 | // Eenter a new item text
271 | let replacement =
272 | popups.input("Enter text to replace \"" + item + "\" with.", item);
273 | if (!replacement) { return null; }
274 |
275 | return expand("lists replace " + lists[pick] + " \"" + item + "\" " + replacement);
276 | ```
277 | __
278 | ui lists replace - User chooses a list, an item from the list and enters text to replace the item with. All instances of the item are replaced with the text.
279 | - Can only replace from (1) basic lists and (2) combo lists that contain basic lists.
280 |
281 |
282 | __
283 | ```
284 | ^ui lists? remove$
285 | ```
286 | __
287 | ```js
288 | // Get the list of populated list's names
289 | let lists = await getNamesOfPopulatedLists();
290 |
291 | // Only include lists of basic and combo type, early out if empty
292 | lists = filterListsToBasicAndCombo(lists);
293 | if (!lists.length)
294 | {
295 | return expFormat("No item removed. There are no non-empty lists.");
296 | }
297 |
298 | // Choose a list
299 | const pick = popups.pick("Choose list to remove an item from", lists, 0, "adaptive");
300 | if (pick === null) { return null; }
301 |
302 | // Get items from the list
303 | let items = await getListItems(lists[pick]);
304 |
305 | // Remove duplicate items
306 | items = [...new Set(items)];
307 |
308 | // Choose an item from the list
309 | const pick2 = popups.pick("Choose an item to remove", items, 0, "adaptive");
310 | if (pick2 === null) { return null; }
311 |
312 | return expand("lists remove " + lists[pick] + " " + items[pick2]);
313 | ```
314 | __
315 | ui lists remove - User choses a list and an item from the list. The last instance of the item is removed from the list.
316 | - Can only remove from (1) basic lists and (2) combo lists that contain basic lists.
317 | ***
318 |
319 |
320 | __
321 | ```
322 | ^ui lists? addfolder$
323 | ```
324 | __
325 | ```js
326 | // Enter a list name, early out if not in a valid format or unavailable
327 | listName = popups.input("Enter a name for the new folder-list");
328 | if (!listName) { return null; }
329 | if (!listName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/))
330 | {
331 | return expFormat(
332 | "Folder-List not added. List name __\"" + listName + "\"_ isn't valid.");
333 | }
334 | if (_inlineScripts.state.sessionState.lists[listName])
335 | {
336 | return expFormat(
337 | "Folder-list not added. Name __\"" + listName + "\"__ unavailable.");
338 | }
339 |
340 | // Get a list of folders
341 | const folders =
342 | Object.keys(app.vault.fileMap).filter(v => app.vault.fileMap[v].children);
343 |
344 | // Choose a folder
345 | const pick =
346 | popups.pick("Choose the folder to attach list " + listName + " to.",
347 | folders, 0, "adaptive");
348 | if (pick === null) { return null; }
349 |
350 | return expand("lists addfolder " + listName + " " + folders[pick]);
351 | ```
352 | __
353 | ui lists addfolder - Asks the user to type a name for the new folder-list.
354 | Asks the user to choose a folder.
355 | Creates the folder-list, attached to the folder.
356 |
357 |
358 | __
359 | ```
360 | ^ui lists? addcombo$
361 | ```
362 | __
363 | ```js
364 | // Enter a list name, early out if not in a valid format or unavailable
365 | listName = popups.input("Enter a name for the new combo-list");
366 | if (!listName) { return null; }
367 | if (!listName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/))
368 | {
369 | return expFormat(
370 | "Combo-List not added. List name __\"" + listName + "\"_ isn't valid.");
371 | }
372 | if (_inlineScripts.state.sessionState.lists[listName])
373 | {
374 | return expFormat(
375 | "Combo-list not added. Name __\"" + listName + "\"__ unavailable.");
376 | }
377 |
378 | // Get the list of list names
379 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
380 |
381 | // Get all lists to use as sub-lists
382 | let subLists = [];
383 | let noMoreMsg = "";
384 | do
385 | {
386 | // Show selected lists so far
387 | const listsSoFar =
388 | !subLists.length ? "" :
389 | "
Lists so far:- " + subLists.join("
- ") + "
";
390 |
391 | // Pick the next sub-list
392 | let pick =
393 | popups.pick("Choose a list to link as a sub-list" + noMoreMsg + listsSoFar,
394 | lists, 0, "adaptive");
395 | if (pick === null) { return null; }
396 |
397 | // After the first choice, add the option to finish the list of sub-lists
398 | if (!subLists.length)
399 | {
400 | lists.unshift("");
401 | pick++;
402 | noMoreMsg = "
or choose \"<No more lists>\" if done linking";
403 | }
404 |
405 | // A choice of 0 means "No more lists" was chosen
406 | if (pick === 0)
407 | {
408 | break;
409 | }
410 |
411 | // Add the chosen list to the sub-lists
412 | subLists.push(lists[pick]);
413 | }
414 | while (true);
415 |
416 | return expand("lists addcombo " + listName + " " + subLists.join(" "));
417 | ```
418 | __
419 | ui lists addcombo - User enters a name for he comb-list and chooses sub-lists. A combo-list is made from the choices.
420 | ***
421 |
422 |
423 | __
424 | ```
425 | ^ui lists? shortcutbatch$
426 | ```
427 | __
428 | ```js
429 | // Get the list of populated list's names, early out if empty
430 | const lists = await getNamesOfPopulatedLists();
431 | if (!lists.length)
432 | {
433 | return expFormat("No shorcut batched. There are no non-empty lists.");
434 | }
435 |
436 | // Choose a list
437 | const pick = popups.pick("Choose a list for a batch shortut", lists, 0, "adaptive");
438 | if (pick === null) { return null; }
439 |
440 | // Enter the shortcut text
441 | const batchShortcut =
442 | popups.input("Enter a shortcut-text. Put \"%1\" where the list item should be.");
443 |
444 | return expand("lists shortcutbatch " + lists[pick] + " " + batchShortcut);
445 | ```
446 | __
447 | ui lists shortcutbatch - User chooses a list and enters a shortcut-text. The shortcut-text is run for each item in the list, with "%1" being replaced by the list item.
448 |
449 |
450 | __
451 | ```
452 | ^ui lists? fromfile lines$
453 | ```
454 | __
455 | ```js
456 | // Get the list of list names
457 | let lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
458 |
459 | // Only include lists of basic and combo type
460 | lists = filterListsToBasicAndCombo(lists);
461 |
462 | // Choice defaults to 0 (which means creating a new list to add to)
463 | let listName = "";
464 |
465 | // If choices aren't empty, ask user to choose which list to add to
466 | if (lists.length)
467 | {
468 | // Add option for a new list
469 | lists.unshift("");
470 |
471 | // Choose a list
472 | const pick = popups.pick("Choose a list to add to", lists, 0, "adaptive");
473 | if (pick === null) { return null; }
474 |
475 | // If user's choice wasn't "new list", record the choice
476 | if (pick > 0) { listName = lists[pick]; }
477 | }
478 |
479 | // If choice was "new list", OR there are no lists, get a name for the new list.
480 | if (listName === "")
481 | {
482 | // Enter a new list name. Early out if not in a valid format
483 | listName = popups.input("Enter a list to add to");
484 | if (!listName) { return null; }
485 | if (!listName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/))
486 | {
487 | return expFormat(
488 | "List item not added. List name __\"" + listName + "\"_ isn't valid.");
489 | }
490 | }
491 |
492 | // Get a list of files
493 | const filenames =
494 | Object.keys(app.vault.fileMap).filter(v => !app.vault.fileMap[v].children);
495 |
496 | // Choose a file
497 | const pick2 =
498 | popups.pick("Choose the file to load lines from.", filenames, 0, "adaptive");
499 | if (pick2 === null) { return null; }
500 |
501 | return expand("lists fromfile lines " + listName + " \"" + filenames[pick2] + "\"");
502 | ```
503 | __
504 | ui lists fromfile lines - Use chooses a list (or enters a new list) and chooses a file. The file is broken into lines, which are added to the list as items.
505 |
--------------------------------------------------------------------------------
/mythicgme.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts for Mythic Game Master Emulator. Mythic GME is an excellent "GM emulator" system for solo and GM'less gaming. It was designed by Tana Pigeon. You can find more info about Mythic GME at [wordmill games](http://wordmillgames.com/mythic-game-master-emulator.html).
6 |
7 | This shortcut-file has a tutorial video available:
8 | - [Using the "mythicgme" shortcut-file to play Mythic GME](https://www.youtube.com/watch?v=NwHwctDp_vM) (runtime 5:00)
9 |
10 | Incompatible with __mythicv2.sfile__. If __mythicv2.sfile__ comes before __mythicgme.sfile__ in the shortcut-list, then __mythicgme.sfile__ will be disabled.
11 |
12 | Uses __state.sfile__ shortcut-file (optional).
13 | It uses this to save & load the chaos value and the scene count.
14 |
15 | Uses __lists.sfile__ shortcut-file (optional).
16 | Adds and uses lists for pcs, npcs and threads.
17 | This requires that __lists.sfile__ comes before __mythicgme.sfile__ in the shortcut-list.
18 | If the pc, npc and thread lists have items, then the __event__ and __detail__ shortcuts will incorporate them into their results.
19 |
20 | This shortcut-file has supplementary shortcut files:
21 | __mythicgme_ui.sfile__ - Graphical UI versions of some of these shortcuts.
22 |
23 |
24 | __
25 | ```
26 | ^sfile setup$
27 | ```
28 | __
29 | ```js
30 | // Block loading if mythicv2 is already setup (incompatible shortcut-files)
31 | if (_inlineScripts.inlineScripts.sfileIndices["mythicv2"])
32 | {
33 | print(
34 | "The mythicgme shortcut-file is disabled as it is incompatible with " +
35 | "the mythicv2 shortcut-file.");
36 | return true;
37 | }
38 |
39 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
40 |
41 | // Setup the state
42 | confirmObjectPath("_inlineScripts.state.sessionState.mythicgme.chaos", 5);
43 | confirmObjectPath("_inlineScripts.state.sessionState.mythicgme.scene", 1);
44 |
45 | // Setup the lists: pcs, npcs and threads
46 | confirmObjectPath(
47 | "_inlineScripts.state.sessionState.lists.pcs", { type: "basic", content: [] });
48 | confirmObjectPath(
49 | "_inlineScripts.state.sessionState.lists.npcs", { type: "basic", content: [] });
50 | confirmObjectPath(
51 | "_inlineScripts.state.sessionState.lists.threads",{ type: "basic", content: [] });
52 |
53 | // Event callback - state.onReset
54 | confirmObjectPath(
55 | "_inlineScripts.state.listeners.onReset.mythicgme",
56 | function()
57 | {
58 | expand("mythicgme reset noconfirm");
59 | });
60 | ```
61 | __
62 | Sets up this shortcut-file
63 |
64 |
65 | __
66 | ```
67 | ^sfile shutdown$
68 | ```
69 | __
70 | ```js
71 | // Event callback removal
72 | delete _inlineScripts.state?.listeners?.onReset?.mythicgme;
73 |
74 | // State removal
75 | delete _inlineScripts.state?.sessionState?.mythicgme;
76 | ```
77 | __
78 | Shuts down this shortcut-file
79 |
80 |
81 | __
82 | ```
83 | ^mythic(?:gme)? reset$
84 | ```
85 | __
86 | ```js
87 | // Confirm
88 | if (!popups.confirm("Confirm resetting the Mythic GME system"))
89 | {
90 | return null;
91 | }
92 |
93 | // Reset
94 | return expand("mythicgme reset noconfirm");
95 | ```
96 | __
97 | mythicgme reset - Resets mythic state to defaults and displays scene heading.
98 | ***
99 |
100 |
101 | __
102 | ```
103 | ^mythic(?:gme)? reset noconfirm$
104 | ```
105 | __
106 | ```js
107 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
108 |
109 | // Reset state
110 | confirmObjectPath("_inlineScripts.state.sessionState.mythicgme");
111 | _inlineScripts.state.sessionState.mythicgme.chaos = 5;
112 | _inlineScripts.state.sessionState.mythicgme.scene = 1;
113 |
114 | // Recreate lists: pcs, npcs, threads
115 | confirmObjectPath("_inlineScripts.state.sessionState.lists");
116 | _inlineScripts.state.sessionState.lists.pcs =
117 | { type: "basic", content: [] };
118 | _inlineScripts.state.sessionState.lists.npcs =
119 | { type: "basic", content: [] };
120 | _inlineScripts.state.sessionState.lists.threads =
121 | { type: "basic", content: [] };
122 |
123 | // Return a heading for scene 1
124 | return "***\n\n\n### SCENE " +
125 | _inlineScripts.state.sessionState.mythicgme.scene + "\n- Setup:\n - ";
126 | ```
127 | __
128 | hidden - No-confirm reset
129 |
130 |
131 | __
132 | __
133 | ```js
134 | // Make a roll from 1 to max.
135 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
136 |
137 | // Pick an item from array a.
138 | function aPick(a) { return a[roll(a.length)-1]; }
139 |
140 | // Pick an item from array a, weighted by element wIndex of the item. If theRoll is
141 | // passed, use that as the roll.
142 | function aPickWeight(a, wIndex, theRoll)
143 | {
144 | wIndex = wIndex || 1;
145 | theRoll = theRoll || roll(a.last()[wIndex]);
146 | for (const item of a)
147 | {
148 | if (item[wIndex] >= theRoll)
149 | {
150 | return item;
151 | }
152 | }
153 | return a.last();
154 | }
155 | ```
156 | __
157 | Helper scripts
158 |
159 |
160 | __
161 | ```
162 | ^fate ?(|-4|-3|-2|-1|0|1|2|3|4|5|6)$
163 | ```
164 | __
165 | ```js
166 | // Parameter defaults to 0
167 | $1 = Number($1) || 0;
168 |
169 | // Data
170 | const ODDS = ["impossible","no way","very unlikely","unlikely","50/50","somewhat likely","likely","very likely","near sure thing","sure thing","has to be"];
171 | const FATE_CHART = [
172 | [ [ 10, 50, 91],[ 5, 25, 86],[ 3, 15, 84],[ 2, 10, 83],[ 1, 5, 82],[ 1, 5, 82],[ 0, 0, 81],[ 0, 0, 81],[ 0,-20, 77] ],
173 | [ [ 15, 75, 96],[ 10, 50, 91],[ 7, 35, 88],[ 5, 25, 86],[ 3, 15, 84],[ 2, 10, 83],[ 1, 5, 82],[ 1, 5, 82],[ 0, 0, 81] ],
174 | [ [ 16, 85, 97],[ 13, 65, 94],[ 10, 50, 91],[ 9, 45, 90],[ 5, 25, 86],[ 3, 15, 84],[ 2, 10, 83],[ 1, 5, 82],[ 1, 5, 82] ],
175 | [ [ 18, 90, 99],[ 15, 75, 96],[ 11, 55, 92],[ 10, 50, 91],[ 7, 35, 88],[ 4, 20, 85],[ 3, 15, 84],[ 2, 10, 83],[ 1, 5, 82] ],
176 | [ [ 19, 95,100],[ 16, 85, 97],[ 15, 75, 96],[ 13, 65, 94],[ 10, 50, 91],[ 7, 35, 88],[ 5, 25, 86],[ 3, 15, 84],[ 2, 10, 83] ],
177 | [ [ 19, 95,100],[ 18, 90, 99],[ 16, 85, 97],[ 16, 80, 97],[ 13, 65, 94],[ 10, 50, 91],[ 9, 45, 90],[ 5, 25, 86],[ 4, 20, 85] ],
178 | [ [ 20,100, 0],[ 19, 95,100],[ 18, 90, 99],[ 16, 85, 97],[ 15, 75, 96],[ 11, 55, 92],[ 10, 50, 91],[ 7, 35, 88],[ 5, 25, 86] ],
179 | [ [ 21,105, 0],[ 19, 95,100],[ 19, 95,100],[ 18, 90, 99],[ 16, 85, 97],[ 15, 75, 96],[ 13, 65, 94],[ 10, 50, 91],[ 9, 45, 90] ],
180 | [ [ 23,115, 0],[ 20,100, 0],[ 19, 95,100],[ 19, 95,100],[ 18, 90, 99],[ 16, 80, 97],[ 15, 75, 96],[ 11, 55, 92],[ 10, 50, 91] ],
181 | [ [ 25,125, 0],[ 22,110, 0],[ 19, 95,100],[ 19, 95,100],[ 18, 90, 99],[ 16, 85, 97],[ 16, 80, 97],[ 13, 65, 94],[ 11, 55, 92] ],
182 | [ [ 26,145, 0],[ 26,130, 0],[ 20,100, 0],[ 20,100, 0],[ 19, 95,100],[ 19, 95,100],[ 18, 90, 99],[ 16, 85, 97],[ 16, 80, 97] ] ];
183 |
184 | // Determine the fate range from the "odds" parameter and the current chaos level
185 | const fateRange =
186 | FATE_CHART[$1+4][9 - _inlineScripts.state.sessionState.mythicgme.chaos];
187 |
188 | // Roll on the fate range to determine fate result
189 | const r = roll(100);
190 | let result =
191 | (r <= fateRange[0]) ? "EXTEREME YES" :
192 | (r <= fateRange[1]) ? "YES" :
193 | (r <= fateRange[2]) ? "NO" :
194 | "EXTREME NO";
195 |
196 | // If the roll triggers a random event, add it
197 | let eventOutput = "";
198 | if (Math.trunc(r/10) == r % 10 &&
199 | r % 10 < _inlineScripts.state.sessionState.mythicgme.chaos)
200 | {
201 | eventOutput = "\nevent - " + expand("event")[1];
202 | }
203 |
204 | // Return the fate result, along with the odds and possible random event
205 | return expFormat("Fate check (" + ODDS[$1+4] + "):\n" + result + eventOutput);
206 | ```
207 | __
208 | fate {odds: -4 TO 6 ("impossible" TO "has to be"), default: 0 ("50/50")} - Makes a fate check based on {odds}.
209 |
210 |
211 | __
212 | ```
213 | ^fate ?\s?(.*)$
214 | ```
215 | __
216 | ```js
217 | // Data
218 | const INPUT_ODDS =
219 | {
220 | "impossible": -4, "no way": -3, "very unlikely": -2, "unlikely": -1, "50/50": 0, "somewhat likely": 1, "likely": 2, "very likely": 3, "near sure thing": 4, "sure thing": 5, "has to be": 6
221 | };
222 |
223 | // Turn the odds parameter from text to number, then call the other fate shortcut
224 | return expand("fate " + (INPUT_ODDS[$1.trim().toLowerCase()] ?? 0));
225 | ```
226 | __
227 | fate {odds: text, default: "50/50"} - Makes a fate check based on {odds}: a specific text, such as "impossible", "sure thing", etc.
228 |
229 | __
230 | ```
231 | ^scene get?$
232 | ```
233 | __
234 | ```js
235 | return expFormat(
236 | "Currently in scene " + _inlineScripts.state.sessionState.mythicgme.scene + ".");
237 | ```
238 | __
239 | scene get - Shows the current scene.
240 |
241 |
242 | __
243 | ```
244 | ^scene (1|-1)$
245 | ```
246 | __
247 | ```js
248 | // Adjust the chaos by the parameter
249 | let result =
250 | ($1 === "1") ? expUnformat(expand("chaos++")) :
251 | ($1 === "-1") ? expUnformat(expand("chaos--")) :
252 | "Chaos is unchanged at " + expUnformat(expand("chaos"));
253 |
254 | // Prefix the scene heading with a bunch of space
255 | result += "\n\n\n***\n\n\n";
256 |
257 | // Advance the scene index
258 | _inlineScripts.state.sessionState.mythicgme.scene++;
259 |
260 | // Add the scene header
261 | result += "### SCENE " + _inlineScripts.state.sessionState.mythicgme.scene;
262 |
263 | // Roll scene check for scene control
264 | let chk = roll(10);
265 | if (chk <= _inlineScripts.state.sessionState.mythicgme.chaos)
266 | {
267 | // Odd roll? Modify the scene in some way
268 | if (chk % 2)
269 | {
270 | result += "\n- Scene altered";
271 | }
272 | // Even Roll? Replace the scene with a random event
273 | else
274 | {
275 | result += "\n- Scene interrupted:\n - event - " + expand("event")[1];
276 | }
277 | }
278 |
279 | // Return the scene header
280 | return result + "\n- setup:\n - ";
281 | ```
282 | __
283 | scene {chaosAdjust: -1 OR 1} - Shifts the chaos value by {chaosAdjust}, then increments the current scene and runs a scene check.
284 | ***
285 |
286 |
287 | __
288 | ```
289 | ^event$
290 | ```
291 | __
292 | ```js
293 | // Data
294 | const FOCUS_TABLE = [ ["REMOTE",7],["NPC ACTS",28,"npcs"],["NEW NPC",35],["THREAD ADVANCE",45,"threads"],["THREAD LOSS",52,"threads"],["THREAD END",55,"threads"],["PC NEGATIVE",67,"pcs"],["PC POSITIVE",75,"pcs"],["AMBIGUOUS",83],["NPC NEGATIVE",92,"npcs"],["NPC POSITIVE",100,"npcs"] ];
295 |
296 | // Roll on the table
297 | let result = aPickWeight(FOCUS_TABLE);
298 |
299 | // If the table result has an associated list, add a random pick from the list
300 | let focus = result[2] ? expand("lists pick " + result[2]) : "";
301 | focus = (focus.length < 2) ? "" : (" (" + focus[1] + ")");
302 |
303 | // Add a roll from the meaning tables
304 | let meaning = expand("meaning")[1];
305 |
306 | // Return the random event result
307 | return expFormat([ "Event:\n", result[0] + focus + " - " + meaning, "" ]);
308 | ```
309 | __
310 | event - Makes an event check.
311 |
312 |
313 | __
314 | ```
315 | ^meaning$
316 | ```
317 | __
318 | ```js
319 | // Data
320 | const ACTION_TABLE = ["ATTAINMENT","STARTING","NEGLECT","FIGHT","RECRUIT","TRIUMPH","VIOLATE","OPPOSE","MALICE","COMMUNICATE","PERSECUTE","INCREASE","DECREASE","ABANDON","GRATIFY","INQUIRE","ANTAGONISE","MOVE","WASTE","TRUCE","RELEASE","BEFRIEND","JUDGE","DESERT","DOMINATE","PROCRASTINATE","PRAISE","SEPARATE","TAKE","BREAK","HEAL","DELAY","STOP","LIE","RETURN","IMMITATE","STRUGGLE","INFORM","BESTOW","POSTPONE","EXPOSE","HAGGLE","IMPRISON","RELEASE","CELEBRATE","DEVELOP","TRAVEL","BLOCK","HARM","DEBASE","OVERINDULGE","ADJOURN","ADVERSITY","KILL","DISRUPT","USURP","CREATE","BETRAY","AGREE","ABUSE","OPPRESS","INSPECT","AMBUSH","SPY","ATTACH","CARRY","OPEN","CARELESSNESS","RUIN","EXTRAVAGANCE","TRICK","ARRIVE","PROPOSE","DIVIDE","REFUSE","MISTRUST","DECEIVE","CRUELTY","INTOLERANCE","TRUST","EXCITEMENT","ACTIVITY","ASSIST","CARE","NEGLIGENCE","PASSION","WORK_HARD","CONTROL","ATTRACT","FAILURE","PURSUE","VENGEANCE","PROCEEDINGS","DISPUTE","PUNISH","GUIDE","TRANSFORM","OVERTHROW","OPPRESS","CHANGE"];
321 | const SUBJECT_TABLE = ["GOALS","DREAMS","ENVIRONMENT","OUTSIDE","INSIDE","REALITY","ALLIES","ENEMIES","EVIL","GOOD","EMOTIONS","OPPOSITION","WAR","PEACE","THE_INNOCENT","LOVE","THE_SPIRITUAL","THE_INTELLECTUAL","NEW_IDEAS","JOY","MESSAGES","ENERGY","BALANCE","TENSION","FRIENDSHIP","THE_PHYSICAL","A_PROJECT","PLEASURES","PAIN","POSSESSIONS","BENEFITS","PLANS","LIES","EXPECTATIONS","LEGAL_MATTERS","BUREAUCRACY","BUSINESS","A_PATH","NEWS","EXTERIOR_FACTORS","ADVICE","A_PLOT","COMPETITION","PRISON","ILLNESS","FOOD","ATTENTION","SUCCESS","FAILURE","TRAVEL","JEALOUSY","DISPUTE","HOME","INVESTMENT","SUFFERING","WISHES","TACTICS","STALEMATE","RANDOMNESS","MISFORTUNE","DEATH","DISRUPTION","POWER","A_BURDEN","INTRIGUES","FEARS","AMBUSH","RUMOR","WOUNDS","EXTRAVAGANCE","A_REPRESENTATIVE","ADVERSITIES","OPULENCE","LIBERTY","MILITARY","THE_MUNDANE","TRIALS","MASSES","VEHICLE","ART","VICTORY","DISPUTE","RICHES","STATUS_QUO","TECHNOLOGY","HOPE","MAGIC","ILLUSIONS","PORTALS","DANGER","WEAPONS","ANIMALS","WEATHER","ELEMENTS","NATURE","THE_PUBLIC","LEADERSHIP","FAME","ANGER","INFORMATION"];
322 |
323 | // Roll on the two meaning tables
324 | let result = aPick(ACTION_TABLE) + " _(of)_ " + aPick(SUBJECT_TABLE);
325 |
326 | // Return the meaning roll result
327 | return expFormat([ "Meaning:\n", result, "" ]);
328 | ```
329 | __
330 | meaning - Rolls on the meaning tables.
331 | ***
332 |
333 |
334 | __
335 | ```
336 | ^chaos$
337 | ```
338 | __
339 | ```js
340 | // Return the current chaos value, wrapped in a nice message
341 | return expFormat(
342 | [ "Chaos is __", _inlineScripts.state.sessionState.mythicgme.chaos, "__." ]);
343 | ```
344 | __
345 | chaos - Shows the current chaos value.
346 |
347 |
348 | __
349 | ```
350 | ^chaos--$
351 | ```
352 | __
353 | ```js
354 | // Lower the chaos value
355 | _inlineScripts.state.sessionState.mythicgme.chaos--;
356 |
357 | // Floor the chaos value (with expansion notifying user of the flooring)
358 | if (_inlineScripts.state.sessionState.mythicgme.chaos < 1)
359 | {
360 | _inlineScripts.state.sessionState.mythicgme.chaos = 1;
361 | return expFormat("Chaos remains at __1__ (hit minimum).");
362 | }
363 |
364 | return expFormat(
365 | "Chaos lowered to __" + _inlineScripts.state.sessionState.mythicgme.chaos +
366 | "__.");
367 | ```
368 | __
369 | chaos-- - Decreases the chaos value by 1 (minimum of 1).
370 |
371 |
372 | __
373 | ```
374 | ^chaos\+\+$
375 | ```
376 | __
377 | ```js
378 | // Raise the chaos value
379 | _inlineScripts.state.sessionState.mythicgme.chaos++;
380 |
381 | // Ceiling the chaos value (with expansion notifying user of the ceiling)
382 | if (_inlineScripts.state.sessionState.mythicgme.chaos > 9)
383 | {
384 | _inlineScripts.state.sessionState.mythicgme.chaos = 9;
385 | return expFormat("Chaos remains at __9__ (hit maximum).");
386 | }
387 |
388 | return expFormat(
389 | "Chaos raised to __" + _inlineScripts.state.sessionState.mythicgme.chaos + "__.");
390 | ```
391 | __
392 | chaos++ - Increases the chaos value by 1 (maximum of 9).
393 |
394 |
395 | __
396 | ```
397 | ^chaos=([1-9])$
398 | ```
399 | __
400 | ```js
401 | // Explicity set the chaos to the parameter
402 | _inlineScripts.state.sessionState.mythicgme.chaos = $1;
403 |
404 | return expFormat("Chaos set to __" + $1 + "__.");
405 | ```
406 | __
407 | chaos={value: 1 TO 9} - Sets the chaos value to {value}.
408 |
409 |
410 | __
411 | ```
412 | ^lists? pick (|[_a-zA-Z][_a-zA-Z0-9]*)(| [1-9][0-9]*)$
413 | ```
414 | __
415 | ```js
416 | return [];
417 | ```
418 | __
419 | hidden - A placeholder shortcut in case the shortcut-file lists.sfile isn't available.
420 |
--------------------------------------------------------------------------------
/lists.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts for working with lists.
6 |
7 | This shortcut-file has a tutorial video available:
8 | - [Using the "list" shortcut-file to manage lists](https://www.youtube.com/watch?v=xIYpnBKdYRg) (runtime 5:34)
9 |
10 | Uses __state.sfile__ shortcut-file (optional).
11 | It uses this to save & load the lists.
12 |
13 | This shortcut-file has supplementary shortcut files:
14 | __lists_ui.sfile__ - Graphical UI versions of some of these shortcuts.
15 |
16 |
17 | __
18 | ```
19 | ^sfile setup$
20 | ```
21 | __
22 | ```js
23 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
24 |
25 | // Check that the state is initialized
26 | confirmObjectPath("_inlineScripts.state.sessionState.lists");
27 |
28 | // Event callback for state.onReset
29 | confirmObjectPath(
30 | "_inlineScripts.state.listeners.onReset.lists",
31 | function()
32 | {
33 | expand("lists reset noconfirm");
34 | });
35 | ```
36 | __
37 | Sets up this shortcut-file
38 |
39 |
40 | __
41 | ```
42 | ^sfile shutdown$
43 | ```
44 | __
45 | ```js
46 | //Event callback removal
47 | delete _inlineScripts?.state?.listeners?.onReset?.lists;
48 |
49 | // State removal
50 | delete _inlineScripts?.state?.sessionState?.lists;
51 | ```
52 | __
53 | Shuts down this shortcut-file
54 |
55 |
56 | __
57 | ```
58 | ^lists? reset$
59 | ```
60 | __
61 | ```js
62 | // Confirm
63 | if (!popups.confirm("Confirm resetting the Lists system")) { return null; }
64 |
65 | // Reset
66 | expand("lists reset noconfirm");
67 |
68 | return expFormat("All lists cleared.");
69 | ```
70 | __
71 | lists reset - Clears all lists.
72 | ***
73 |
74 |
75 | __
76 | ```
77 | ^lists? reset noconfirm$
78 | ```
79 | __
80 | ```js
81 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
82 |
83 | // Confirm that the state is initialized
84 | confirmObjectPath("_inlineScripts.state.sessionState");
85 |
86 | // Reset lists
87 | _inlineScripts.state.sessionState.lists = {};
88 | ```
89 | __
90 | hidden - No-confirm reset
91 |
92 |
93 | __
94 | __
95 | ```js
96 | function caseInsensitiveSort(lhs, rhs)
97 | {
98 | return lhs.toLowerCase().localeCompare(rhs.toLowerCase());
99 | }
100 |
101 | // a function to get the items of a given list, for all possible list types.
102 | function getListItems(name)
103 | {
104 | // The list to get items for
105 | let list = _inlineScripts.state.sessionState.lists[name];
106 | if (!list) { return []; }
107 |
108 | // Handle different list types
109 | switch (list.type)
110 | {
111 | // basic type - contain their items in their "content" property
112 | case "basic":
113 | return list.content.sort(caseInsensitiveSort);
114 | break;
115 | // folder type - items are the files in the associated folder ("content" prop)
116 | case "folder":
117 | // Get folder and confirm it's valid
118 | const targetFile = app.fileManager.vault.fileMap[list.content];
119 | if (!targetFile || !targetFile.children)
120 | {
121 | return [];
122 | }
123 | // Return links to folder children that are notes
124 | return [... targetFile.children ]
125 | .filter(v => !v.children)
126 | .filter(v => v.name.endsWith(".md"))
127 | .map(v => "[[" + v.basename + "]]")
128 | .sort(caseInsensitiveSort);
129 | // combo type - items are from the sub-lists of this combo ("content" prop)
130 | case "combo":
131 | {
132 | let result = [];
133 | // Iterate over lists and add items from each
134 | for (const sublist of list.content)
135 | {
136 | result = result.concat(getListItems(sublist));
137 | }
138 | // Sort and return the resulting list
139 | return result.sort(caseInsensitiveSort);
140 | }
141 | }
142 | }
143 | ```
144 | __
145 | Helper scripts
146 |
147 |
148 | __
149 | ```
150 | ^lists?$
151 | ```
152 | __
153 | ```js
154 | // Get all lists names
155 | const lists = Object.keys(_inlineScripts.state.sessionState.lists).sort();
156 |
157 | // Start the expansion
158 | let result = "Lists:";
159 |
160 | // Early out with the result if no lists
161 | if (!lists.length)
162 | {
163 | return expFormat(result + "\n- NONE");
164 | }
165 |
166 | // Add each list's individual expansion to the final expansion
167 | for (let i = 0; i < lists.length; i++)
168 | {
169 | // Individual list's expansion (but without the header, and as a single string)
170 | const listExpansion = expand("lists " + lists[i]).slice(1).join("");
171 | // Unformat the individual list expansion, replace period-bullets with indented
172 | // dash-bullets, then add it to the final expansion.
173 | result +=
174 | "\n- " + expUnformat(listExpansion).replaceAll("\n. ", "\n - ");
175 | }
176 |
177 | return expFormat(result);
178 | ```
179 | __
180 | lists - Shows all lists and their info: type, associations and items.
181 |
182 |
183 | __
184 | ```
185 | ^lists? ([_a-zA-Z][_a-zA-Z0-9]*)$
186 | ```
187 | __
188 | ```js
189 | // Create items list, or "NONE" if no items
190 | let itemText = getListItems($1);
191 | itemText = (itemText?.length ? (". " + itemText.join("\n. ")) : ". NONE");
192 |
193 | // Create the type info. If list isn't registered, type defaults to basic.
194 | let typeText = _inlineScripts.state.sessionState.lists[$1]?.type || "basic";
195 | // No type info for basic types
196 | if (typeText === "basic")
197 | {
198 | typeText = "";
199 | }
200 | // Non-basic types show the list's type and associations. For folder-list,
201 | // association is the folder. For combo-list, association is the sub-lists.
202 | else
203 | {
204 | const associations = _inlineScripts.state.sessionState.lists[$1].content;
205 | typeText = " _(" + typeText + ": " + associations + ")_";
206 | }
207 |
208 | return expFormat([ "List ", "__" + $1 + "__" + typeText + ":\n", itemText, "" ]);
209 | ```
210 | __
211 | lists {list name: name text} - Shows all info for the single list {list name}: type, associations and items.
212 |
213 |
214 | __
215 | ```
216 | ^lists? rename ([_a-zA-Z][_a-zA-Z0-9]*) ([_a-zA-Z][_a-zA-Z0-9]*)$
217 | ```
218 | __
219 | ```js
220 | // Confirm that old list name is valid and new list name is NOT valid
221 | if (!_inlineScripts.state.sessionState.lists[$1])
222 | {
223 | return expFormat("List not renamed. List __" + $1 + "__ not found.");
224 | }
225 | if (_inlineScripts.state.sessionState.lists[$2])
226 | {
227 | return expFormat("List not renamed. List name __" + $2 + "__ is already used.");
228 | }
229 |
230 | // Copy the list to the new list name
231 | _inlineScripts.state.sessionState.lists[$2] = _inlineScripts.state.sessionState.lists[$1];
232 |
233 | // Remove the old list name
234 | delete _inlineScripts.state.sessionState.lists[$1];
235 |
236 | return expFormat("List __" + $1 + "__ renamed to __" + $2 + "__.");
237 | ```
238 | __
239 | lists rename {original list name: name text} {new list name: name text} - Changes the name of list {original list name} to {new list name}.
240 |
241 |
242 | __
243 | ```
244 | ^lists? removelist ([_a-zA-Z][_a-zA-Z0-9]*)$
245 | ```
246 | __
247 | ```js
248 | // Error out if specified list doesn't exist
249 | if (!_inlineScripts.state.sessionState.lists[$1])
250 | {
251 | return expFormat("List __" + $1 + "__ not removed. List does not exist.");
252 | }
253 |
254 | // Remove the list
255 | delete _inlineScripts.state.sessionState.lists[$1];
256 |
257 | return expFormat("List __" + $1 + "__ removed.");
258 | ```
259 | __
260 | lists removelist {list name: name text} - Removes the entire list {list name}.
261 |
262 |
263 | __
264 | ```
265 | ^lists? pick ([_a-zA-Z][_a-zA-Z0-9]*)$
266 | ```
267 | __
268 | ```js
269 | // Helper function - Make a roll from 1 to max.
270 | function roll(max) { return Math.trunc(Math.random() * max + 1); }
271 |
272 | // Get the number of items. Early out if no items
273 | let itemCount = getListItems($1).length;
274 | if (itemCount === 0)
275 | {
276 | return expFormat([ "Failed to pick from list __" + $1 + "__. List is empty." ]);
277 | }
278 |
279 | // Use the unlisted pick shortcut, which takes the index to pick. The unlisted
280 | // version is used to handle the randomness externally. It is used by the
281 | // adventurecrafter shortcut-file.
282 | return expand("lists pick " + $1 + " " + roll(itemCount));
283 | ```
284 | __
285 | lists pick {list name: name text} - Gets a random item from the list {list name}.
286 |
287 |
288 | __
289 | ```
290 | ^lists? pick ([_a-zA-Z][_a-zA-Z0-9]*) ([1-9][0-9]*)$
291 | ```
292 | __
293 | ```js
294 | // Convert $2 from 1-index to 0-index
295 | $2 = Number($2) - 1;
296 |
297 | // Generic expansion messages
298 | const ERROR_MSG_PREFIX = "Item not picked from list __" + $1 + "__. ";
299 |
300 | // Get the list's items. Early out if parameter is out-of-bounds
301 | let items = getListItems($1);
302 | if ($2 >= items.length)
303 | {
304 | return expFormat(
305 | [ ERROR_MSG_PREFIX + "The item index " + $2 + " is out of range." ]);
306 | }
307 |
308 | // Return expansion with the picked item
309 | return expFormat([ "__", items[$2], "__ picked from list __", $1, "__." ]);
310 | ```
311 | __
312 | hidden - lists pick {list name: name text} {item index: >0} - Gets the item number {item index} from the list {list name}.
313 | ***
314 |
315 |
316 | __
317 | ```
318 | ^lists? add ([_a-zA-Z][_a-zA-Z0-9]*) (.+)$
319 | ```
320 | __
321 | ```js
322 | // If the given list isn't yet defined, auto-define it as a basic list.
323 | _inlineScripts.state.sessionState.lists[$1] ||= { type: "basic", content: [] };
324 |
325 | // Generic expansion messages
326 | const SUCCESS_MSG = "Item __" + $2 + "__ added to list __" + $1 + "__.";
327 | const ERROR_MSG_PREFIX = "Item __" + $2 + "__ not added to list __" + $1 + "__. ";
328 |
329 | // Add is handled differently for different list types
330 | switch (_inlineScripts.state.sessionState.lists[$1].type)
331 | {
332 | // Basic type - Just append the item
333 | case "basic":
334 | _inlineScripts.state.sessionState.lists[$1].content.push($2);
335 | return expFormat([ SUCCESS_MSG ]);
336 | // Combo type - Append the item to the last possible sub-list
337 | case "combo":
338 | {
339 | // Iterate sub-lists IN REVERSE ORDER, to add to the LAST possible sublist
340 | const sublists = _inlineScripts.state.sessionState.lists[$1].content;
341 | for (let i = sublists.length-1; i >= 0; i--)
342 | {
343 | // Try adding
344 | const result = expand("lists add " + sublists[i] + " " + $2);
345 | // if add was successful (signified by size 1), expand success message
346 | if (result.length === 1)
347 | {
348 | return expFormat([ SUCCESS_MSG ]);
349 | }
350 | }
351 | // If failed to add to any sub-list, expand error message
352 | return expFormat([ ERROR_MSG_PREFIX, "No sub-lists support adding." ]);
353 | }
354 | // Any other type doesn't support adding
355 | default:
356 | return expFormat([ ERROR_MSG_PREFIX, "List-type doesn't support adding." ]);
357 | }
358 | ```
359 | __
360 | lists add {list name: name text} {item: text} - Adds {item} to the list {list name}. Allows duplicate items.
361 | - Can only add to (1) basic lists and (2) combo lists which contain basic lists.
362 |
363 |
364 | __
365 | ```
366 | ^lists? replace ([_a-zA-Z][_a-zA-Z0-9]*) ("[^ ].*"|[^ ]+) (.+)$
367 | ```
368 | __
369 | ```js
370 | // Remove any quotes around the old item
371 | $2 = $2.replace(/^"(.*)"$/, "$1")
372 |
373 | // Generic expansion messages
374 | const SUCCESS_MSG =
375 | "All __" + $2 + "__ replaced with __" + $3 + "__ in list __" + $1 + "__.";
376 | const ERROR_MSG_PREFIX = "Item __" + $2 + "__ not replaced in list __" + $1 + "__. ";
377 |
378 | // Confirm that the list exists
379 | if (!_inlineScripts.state.sessionState.lists[$1])
380 | {
381 | return expFormat(ERROR_MSG_PREFIX + "List __" + $1 + "__ not found.");
382 | }
383 |
384 | // Relacement is handled differently for different list types
385 | switch (_inlineScripts.state.sessionState.lists[$1].type)
386 | {
387 | // Basic type - iterate over items, replacing any matches
388 | case "basic":
389 | {
390 | const items = _inlineScripts.state.sessionState.lists[$1].content;
391 | for (let i = 0; i < items.length; i++)
392 | {
393 | if (items[i] === $2)
394 | {
395 | items[i] = $3;
396 | }
397 | }
398 | return expFormat(SUCCESS_MSG);
399 | }
400 | // combo type - iterate over sub-lists, running the replacement for each
401 | case "combo":
402 | {
403 | const sublists = _inlineScripts.state.sessionState.lists[$1].content;
404 | for (const sublist of sublists)
405 | {
406 | expand("lists replace " + sublist + " " + $2 + " " + $3);
407 | }
408 | return expFormat(SUCCESS_MSG);
409 | }
410 | // Any other type doesn't support replacement
411 | default:
412 | return expFormat(ERROR_MSG_PREFIX + "List-type doesn't support replacement.");
413 | }
414 | ```
415 | __
416 | lists replace {list name: name text} {item: text} {replacement: text} - Replaces all instances of {item} with {replacement}. {item} must be quoted if it has any space.
417 |
418 |
419 | __
420 | ```
421 | ^lists? remove ([_a-zA-Z][_a-zA-Z0-9]*) (.+)$
422 | ```
423 | __
424 | ```js
425 | // Generic expansion messages
426 | const SUCCESS_MSG = "__" + $2 + "__ removed from list __" + $1 + "__.";
427 | const ERROR_MSG_PREFIX =
428 | "Item __" + $2 + "__ not removed from list __" + $1 + "__. ";
429 |
430 | // Confirm that the list exists
431 | if (!_inlineScripts.state.sessionState.lists[$1])
432 | {
433 | return expFormat(ERROR_MSG_PREFIX + "List not found.");
434 | }
435 |
436 | // Remove is handled differently for different list types
437 | switch (_inlineScripts.state.sessionState.lists[$1].type)
438 | {
439 | // Basic type - Iterate over items backwards, removing the last instance found
440 | case "basic":
441 | {
442 | const items = _inlineScripts.state.sessionState.lists[$1].content;
443 | for (let i = items.length-1; i >= 0; i--)
444 | {
445 | if (items[i] === $2)
446 | {
447 | items.splice(i, 1);
448 | return expFormat([ SUCCESS_MSG ]);
449 | }
450 | }
451 | // If we get here, no items were removed
452 | return expFormat([ ERROR_MSG_PREFIX, "Item not found." ]);
453 | }
454 | // Combo type - Iterate over sub-items backwards, running remove until successful.
455 | case "combo":
456 | {
457 | const sublists = _inlineScripts.state.sessionState.lists[$1].content;
458 | for (let i = sublists.length-1; i >= 0; i--)
459 | {
460 | const result = expand("lists remove " + sublists[i] + " " + $2);
461 | if (result.length === 1)
462 | {
463 | return expFormat([ SUCCESS_MSG ]);
464 | }
465 | }
466 | // If we get here, no items were removed
467 | return expFormat([ ERROR_MSG_PREFIX, "Unable to remove from any sub-list." ]);
468 | }
469 | // Any other type doesn't support removal
470 | default:
471 | return expFormat([ ERROR_MSG_PREFIX, "List type doesn't support removal." ]);
472 | }
473 | ```
474 | __
475 | lists remove {list name: name text} {item: text} - Removes an instance of {item} from the list {list name}.
476 | - Can only remove from (1) basic lists and (2) combo lists which contain basic lists.
477 | ***
478 |
479 |
480 | __
481 | ```
482 | ^lists? addfolder ([_a-zA-Z][_a-zA-Z0-9]*) ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+)$
483 | ```
484 | __
485 | ```js
486 | // Remove any quotes around the specified folder path
487 | $2 = $2.replace(/^"(.*)"$/, "$1")
488 |
489 | // Confirm the list name is available
490 | if (_inlineScripts.state.sessionState.lists[$1])
491 | {
492 | return expFormat("File-list not created. Name __\"" + $1 + "\"__ unavailable.");
493 | }
494 |
495 | // Create the folder-list
496 | _inlineScripts.state.sessionState.lists[$1] = { type: "folder", content: $2 };
497 |
498 | return expFormat("Folder-list __" +$1 + "__ added, linked to __\"" + $2 + "\"__.");
499 | ```
500 | __
501 | lists addfolder {list name: name text} {folder: path text} - Creates a folder-list named {list name} that is linked to the folder {folder}. A "folder-list" is a list who's items are the names of the notes in the linked folder.
502 |
503 |
504 | __
505 | ```
506 | ^lists? addcombo ([_a-zA-Z][_a-zA-Z0-9]*) ([_a-zA-Z][ _a-zA-Z0-9]*)$
507 | ```
508 | __
509 | ```js
510 | // Turn the sub-lists parameter into an array of (non-empty) names
511 | let sublists = $2.split(" ").filter(v => v);
512 |
513 | // Confirm the list name is available
514 | if (_inlineScripts.state.sessionState.lists[$1])
515 | {
516 | return expFormat("Combo-list not created. Name __\"" + $1 + "\"__ unavailable.");
517 | }
518 |
519 | // Create the combo-list
520 | _inlineScripts.state.sessionState.lists[$1] = { type: "combo", content: sublists };
521 |
522 | return expFormat(
523 | "Combo-list __" + $1 + "__ added, linked to:\n. " + sublists.join("\n. "));
524 | ```
525 | __
526 | lists addcombo {list name: name text} {sub-list: name text separated by spaces} - Creates a combo-list named {list name} that is linked to the lists given in {sub-list}. A "combo-list" is a list who's items are all of the items of its linked sub-lists.
527 | ***
528 |
529 |
530 | __
531 | ```
532 | ^lists? shortcutbatch ([_a-zA-Z][_a-zA-Z0-9]*) (.*)$
533 | ```
534 | __
535 | ```js
536 | // Check that the specified list is valid
537 | if (!_inlineScripts.state.sessionState.lists[$1])
538 | {
539 | return expFormat("Shortcut batch not run. List __" + $1 + "__ not found.");
540 | }
541 |
542 | // Begin the expansion
543 | let result = "Shortcut batch for list __" + $1 + "__...\n\n";
544 |
545 | // Run the shortcut once for each item in the list, adding the result to the expansion
546 | for (const item of getListItems($1))
547 | {
548 | result += expUnformat( expand($2.replaceAll("%1", item)) ) + "\n\n";
549 | }
550 |
551 | // End and return the expansion
552 | return expFormat(result + "... shortcut batch finished.");
553 | ```
554 | __
555 | lists shortcutbatch {list name: name text} {shortcut: text} - Runs shortcut {shortcut} once for each item in list {list name}, replacing "%1" in {shortcut} with the item.
556 |
557 |
558 | __
559 | ```
560 | ^lists? fromfile lines ([_a-zA-Z][_a-zA-Z0-9]*) ("[^ \t\\:*?"<>|][^\t\\:*?"<>|]*"|[^ \t\\:*?"<>|]+)$
561 | ```
562 | __
563 | ```js
564 | // Remove any quotes around the file path
565 | $2 = $2.replace(/^"(.*)"$/, "$1")
566 |
567 | // Get the specified file
568 | let file = app.vault.fileMap[$2];
569 |
570 | // If file isn't valid, try adding an "md" extension
571 | if (!file || file.children)
572 | {
573 | file = app.vault.fileMap[$2 + ".md"];
574 | }
575 |
576 | // If file still isn't valid, give up
577 | if (!file)
578 | {
579 | return expFormat("Lines not added. File __" + $2 + "__ not found.");
580 | }
581 | if (file.children)
582 | {
583 | return expFormat("Lines not added. __" + $2 + "__ is a folder.");
584 | }
585 |
586 | // Get the file's lines
587 | const content = await app.vault.cachedRead(file);
588 | const lines = content.split("\n").filter(v => v);
589 |
590 | // Add the file's lines
591 | for (const line of lines)
592 | {
593 | const result = expand("lists add " + $1 + " " + line);
594 | if (result.length > 1)
595 | {
596 | return expFormat(
597 | "Lines not added. Type of list __" + $1 + "__ can't be added to.");
598 | }
599 | }
600 |
601 | return expFormat("__" + lines.length + "__ items added to list __" + $1 + "__.");
602 | ```
603 | __
604 | lists fromfile lines {list name: name text} {file: path text} - Takes the file {file}, breaks it up into individual lines and adds each of those lines, as items, to list {list name}.
605 |
606 |
607 | __
608 | ```
609 | ^lists? listraw ([_a-zA-Z][_a-zA-Z0-9]*)$
610 | ```
611 | __
612 | ```js
613 | // Return the array of items directly
614 | return getListItems($1);
615 | ```
616 | __
617 | hidden - Returns an array of the items in a list without formatting. Useful internally (as a sub-shortcut).
618 |
619 |
620 | __
621 | ```
622 | ^lists? type ([_a-zA-Z][_a-zA-Z0-9]*)$
623 | ```
624 | __
625 | ```js
626 | // Get the specified list
627 | const list = inlineScripts.state.sessionState.lists[$1];
628 |
629 | // If list doesn't exist, return type of "none"
630 | if (!list) { return "none"; }
631 |
632 | // Return the list's type
633 | return list.type;
634 | ```
635 | __
636 | hidden - gets the type of the list named {list name}. Useful internally (as a sub-shortcut).
637 |
--------------------------------------------------------------------------------
/cards_ui.sfile.md:
--------------------------------------------------------------------------------
1 | An extension to __cards.sfile__ that provides graphical ui versions of shortcuts.
2 |
3 | This shortcut-file has a tutorial video available:
4 | [Using the "cards" shortcut-file to use virtual cards](https://www.youtube.com/watch?v=KkpjTL2UvtQ) (runtime 12:43)
5 |
6 |
7 | __
8 | ```
9 | ^sfile setup$
10 | ```
11 | __
12 | ```js
13 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
14 |
15 | // Helper function - Turn relative path into absolute path based in the vault's root
16 | function getAbsolutePath(path)
17 | {
18 | if (path.startsWith("data:image")) { return path; }
19 | path = app.vault.fileMap[path];
20 | if (!path) { return ""; }
21 | return app.vault.getResourcePath(path);
22 | }
23 |
24 | // Helper function - Get the current back-image, url
25 | function getBackImage()
26 | {
27 | return _inlineScripts.state.sessionState.cards.backImage ||
28 | _inlineScripts.cards.defaultBackImage;
29 | }
30 |
31 | // Custom popop definition for various card manipulations
32 | confirmObjectPath("_inlineScripts.cards_ui.manipulateCardsPopup",
33 | {
34 | // Setup function
35 | onOpen: async (data, parent, firstButton, SettingType) =>
36 | {
37 | data.pileNames = Object.keys(_inlineScripts.state.sessionState.cards.piles);
38 |
39 | // Source pile dropdown
40 | new SettingType(parent)
41 | .setName(
42 | data.defaults?.dst === undefined ? "Card-pile" : "Source card-pile")
43 | .setDesc(data.descriptions?.src)
44 | .addDropdown(dropdown =>
45 | {
46 | dropdown.addOptions(data.pileNames);
47 | dropdown.setValue(
48 | data.defaults?.src ?
49 | data.pileNames.indexOf(data.defaults?.src) :
50 | 0);
51 | data.src = dropdown;
52 | dropdown.selectEl.addEventListener("keypress", e =>
53 | {
54 | if (e.key === "Enter") { firstButton.click(); }
55 | });
56 | return dropdown;
57 | });
58 |
59 | // Destination pile dropdown
60 | if (data.defaults?.dst !== undefined)
61 | {
62 | new SettingType(parent)
63 | .setName("Destination Card-pile")
64 | .setDesc(data.descriptions?.dst)
65 | .addDropdown(dropdown =>
66 | {
67 | dropdown.addOptions(data.pileNames);
68 | dropdown.setValue(
69 | data.defaults?.dst ?
70 | data.pileNames.indexOf(data.defaults?.dst) :
71 | 0);
72 | data.dst = dropdown;
73 | dropdown.selectEl.addEventListener("keypress", e =>
74 | {
75 | if (e.key === "Enter") { firstButton.click(); }
76 | });
77 | return dropdown;
78 | });
79 | }
80 |
81 | // Count textbox
82 | if (data.defaults?.count !== undefined)
83 | {
84 | new SettingType(parent)
85 | .setName("Count")
86 | .setDesc(data.descriptions?.count)
87 | .addText(text =>
88 | {
89 | text.setValue(data.defaults?.count + "");
90 | data.count = text;
91 | text.inputEl.addEventListener("keypress", e =>
92 | {
93 | if (e.key === "Enter") { firstButton.click(); }
94 | else if (e.keyCode < 48 || e.keyCode > 57)
95 | {
96 | window.event.returnValue = null;
97 | }
98 | });
99 | text.inputEl.addEventListener("blur", e =>
100 | {
101 | if (Number(e.target.value) < 1)
102 | {
103 | e.target.value = "1";
104 | }
105 | });
106 | return text;
107 | });
108 | }
109 |
110 | // Topbottom dropdown
111 | if (data.defaults?.topBottom !== undefined)
112 | {
113 | new SettingType(parent)
114 | .setName("Top or bottom")
115 | .setDesc(data.descriptions?.topBottom)
116 | .addDropdown(dropdown =>
117 | {
118 | dropdown.addOptions([ "Top", "Bottom" ]);
119 | dropdown.setValue(data.defaults?.topBottom);
120 | data.topBottom = dropdown;
121 | dropdown.selectEl.addEventListener("keypress", e =>
122 | {
123 | if (e.key === "Enter") { firstButton.click(); }
124 | });
125 | return dropdown;
126 | });
127 | }
128 |
129 | // Rotate toggle button
130 | if (data.defaults?.rotate !== undefined)
131 | {
132 | new SettingType(parent)
133 | .setName("Rotate cards")
134 | .setDesc(data.descriptions?.rotate)
135 | .addToggle(toggle =>
136 | {
137 | toggle.setValue(data.defaults?.rotate);
138 | data.rotate = toggle;
139 | toggle.toggleEl.addEventListener("keypress", e =>
140 | {
141 | if (e.key === "Enter") { firstButton.click(); }
142 | });
143 | return toggle;
144 | });
145 | }
146 | },
147 |
148 | // Shutown function
149 | onClose: async (data, resolveFnc, buttonId) =>
150 | {
151 | if (buttonId !== "Ok") { return; }
152 | let result = { src: data.pileNames[data.src.getValue()] };
153 | if (data.dst) { result.dst = data.pileNames[data.dst.getValue()]; }
154 | if (data.count) { result.count = data.count.getValue(); }
155 | if (data.topBottom) { result.topBottom = data.topBottom.getValue(); }
156 | if (data.rotate) { result.rotate = data.rotate.getValue(); }
157 | resolveFnc(result);
158 | }
159 | });
160 |
161 | // Custom popop definition for altering the settings of the cards system
162 | confirmObjectPath("_inlineScripts.cards_ui.settingsPopup",
163 | {
164 | // Setup function
165 | onOpen: async (data, parent, firstButton, SettingType) =>
166 | {
167 | // The current card size (before we change it)
168 | const currentSize = _inlineScripts.state.sessionState.cards.size;
169 | const currentBackImage = getBackImage();
170 |
171 | // A parent div to center the card visualization
172 | let sampleContainerUi = document.createElement("div");
173 | sampleContainerUi.style["text-align"] = "center";
174 | parent.append(sampleContainerUi);
175 |
176 | // The card visualization
177 | data.sampleUi = document.createElement("img");
178 | data.sampleUi.src = getAbsolutePath(currentBackImage);
179 | data.sampleUi.style.width = currentSize + "px";
180 | sampleContainerUi.append(data.sampleUi);
181 |
182 | // A slider to change the card size
183 | let sizeUi = new SettingType(parent)
184 | .setName("Size (" + currentSize + ")")
185 | .setDesc("Pick the size for all cards (in pixels).")
186 | .addSlider((slider) =>
187 | {
188 | slider
189 | .setLimits(10, 500, 10)
190 | .setValue(currentSize)
191 | .onChange(value =>
192 | {
193 | data.sampleUi.style.width = value + "px";
194 | data.titleUi.innerText = "Size (" + value + ")";
195 | });
196 | data.sliderUi = slider;
197 | slider.sliderEl.addEventListener("keypress", e =>
198 | {
199 | if (e.key === "Enter") { firstButton.click(); }
200 | });
201 | return slider;
202 | });
203 | // Keep track of the slider label - to change its text when the size changes
204 | data.titleUi = sizeUi.nameEl;
205 |
206 | // A dropdown to select the card back
207 | let backImageUi = new SettingType(parent)
208 | .setName("Back image")
209 | .setDesc("Choose the image to represent the back of all cards.")
210 | .addDropdown(dropdown =>
211 | {
212 | data.backImageOptions =
213 | Object.keys(app.vault.fileMap).filter(
214 | v => { return v.match(/.(?:png|bmp|gif|jpg|jpeg)$/); }
215 | );
216 | data.backImageOptions.unshift("");
217 | dropdown.addOptions(data.backImageOptions);
218 | dropdown.setValue(data.backImageOptions.indexOf(currentBackImage));
219 | dropdown.onChange(value =>
220 | {
221 | data.sampleUi.src = getAbsolutePath(
222 | data.backImageOptions[value] ||
223 | _inlineScripts.cards.defaultBackImage);
224 | });
225 | data.backImageUi = dropdown;
226 | dropdown.selectEl.addEventListener("keypress", e =>
227 | {
228 | if (e.key === "Enter") { firstButton.click(); }
229 | });
230 | return dropdown;
231 | });
232 | },
233 |
234 | // Shutown function
235 | onClose: async (data, resolveFnc, buttonId) =>
236 | {
237 | if (buttonId !== "Ok") { return; }
238 | resolveFnc({
239 | size: data.sliderUi.getValue(),
240 | backImage: data.backImageOptions[data.backImageUi.getValue()] || "default"
241 | });
242 | }
243 | });
244 | ```
245 | __
246 | Sets up this shortcut-file
247 |
248 |
249 | __
250 | __
251 | ```js
252 | // User chooses an existing pile
253 | userChoosesPileId = function(requestMessage, failMessage, pileToBlock)
254 | {
255 | // Get names of the piles
256 | const pileNames =
257 | Object.keys(_inlineScripts.state.sessionState.cards.piles).
258 | filter(v => v !== pileToBlock);
259 | if (!pileNames.length)
260 | {
261 | return [ null, expFormat(failMessage + "No card-piles available.") ];
262 | }
263 |
264 | // Choose a pile
265 | const pileIndex =
266 | popups.pick("Choose a card-pile " + requestMessage, pileNames, 0, "adaptive");
267 | if (pileIndex === null) { return [ null, null ]; }
268 |
269 | // Return the chosen pile name
270 | return [ pileNames[pileIndex], null ];
271 | }
272 |
273 | // Return true if at least one pile exists, return false otherwise
274 | function doPilesExist()
275 | {
276 | return !!( Object.keys(_inlineScripts.state.sessionState.cards.piles).length );
277 | }
278 | ```
279 | __
280 | Helper scripts
281 |
282 |
283 | __
284 | ```
285 | ^ui cards? reset$
286 | ```
287 | __
288 | ```js
289 | return expand("cards reset");
290 | ```
291 | __
292 | ui cards reset - Clears all card-piles.
293 |
294 |
295 | __
296 | ```
297 | ^ui cards? settings$
298 | ```
299 | __
300 | ```js
301 | // Choose settings
302 | const newSettings = popups.custom(
303 | "Choose settings for all cards", _inlineScripts.cards_ui.settingsPopup);
304 | if (newSettings === null) { return null; }
305 |
306 | return expand("cards settings " + newSettings.size + " " + newSettings.backImage);
307 | ```
308 | __
309 | ui cards settings - User chooses card size and card back image.
310 | ***
311 |
312 |
313 | __
314 | ```
315 | ^ui cards? pile$
316 | ```
317 | __
318 | ```js
319 | // Get the data-string to import
320 | const toImport = popups.input("Enter a name for the new pile");
321 | if (toImport === null) { return null; }
322 | if (_inlineScripts.state.sessionState.cards.piles[toImport])
323 | {
324 | return expFormat("Card-pile not created. __" + toImport + "__ already exists.");
325 | }
326 |
327 | // User decides whether to rotate cards
328 | const isFaceUp = popups.pick(
329 | "How should cards face in the card-pile?", [ "Face-down", "Face-up" ], 0, 2);
330 | if (isFaceUp === null) { return null; }
331 |
332 | // User decides whether to rotate cards
333 | const hideMoved = popups.pick(
334 | "Should cards be printed when drawn or picked into the card-pile",
335 | [ "Yes", "No" ], _inlineScripts.state.sessionState.cards.priorShowMoved ? 0 : 1,
336 | 2);
337 | if (hideMoved === null) { return null; }
338 |
339 | // Pile creation shortcut is called
340 | return expand(
341 | "cards pile " + toImport + " " + (isFaceUp ? "up" : "down") + " " +
342 | (hideMoved ? "n" : "y"));
343 | ```
344 | __
345 | ui cards pile - User enters a name, card-facing and show-moved flag for the new card-pile. Card-pile is created from the choices.
346 |
347 |
348 | __
349 | ```
350 | ^ui cards? remove$
351 | ```
352 | __
353 | ```js
354 | // User chooses the pile to remove
355 | const pile = await userChoosesPileId("to remove", "Card-pile not removed.");
356 | if (pile[0] == null) { return pile[1]; }
357 |
358 | // User decides whether to rotate cards
359 | const doRecall = popups.pick(
360 | "Try recalling card-pile's cards before removing it?", [ "Yes", "No" ], 0, 2);
361 | if (doRecall === null) { return null; }
362 |
363 | // Shuffle shortcut is called
364 | return expand("cards remove " + pile[0] + " " + (doRecall ? "n" : "y"));
365 | ```
366 | __
367 | ui cards remove - User choses a card-pile to remove, then removes that card-pile.
368 |
369 |
370 | __
371 | ```
372 | ^ui cards? pilesettings$
373 | ```
374 | __
375 | ```js
376 | // User chooses the pile to change settings for
377 | const pileName =
378 | await userChoosesPileId("to change settings for", "Card-pile not changed.");
379 | if (pileName[0] == null) { return pileName[1]; }
380 |
381 | // Get the pile to use it's current settings for defaults
382 | const pile = _inlineScripts.state.sessionState.cards.piles[pileName[0]];
383 |
384 | // User decides whether to rotate cards
385 | const isFaceUp = popups.pick(
386 | "How should cards face in the card-pile?", [ "Face-down", "Face-up" ],
387 | pile.isFaceUp ? 1 : 0, 2);
388 | if (isFaceUp === null) { return null; }
389 |
390 | // User decides whether to rotate cards
391 | const hideMoved = popups.pick(
392 | "Should cards be printed when drawn or picked into the card-pile",
393 | [ "Yes", "No" ], pile.showMoved ? 0 : 1, 2);
394 | if (hideMoved === null) { return null; }
395 |
396 | // Pile creation shortcut is called
397 | return expand(
398 | "cards pilesettings " + pileName[0] + " " + (isFaceUp ? "up" : "down") + " " +
399 | (hideMoved ? "n" : "y"));
400 | ```
401 | __
402 | ui cards pilesettings - User chooses a card-pile, then choses its card-facing and show-moved flag.
403 | ***
404 |
405 |
406 | __
407 | ```
408 | ^ui cards? fromfolder$
409 | ```
410 | __
411 | ```js
412 | // Make a list of viable folders to choose from
413 | const folders = Object.keys(app.vault.fileMap)
414 | .filter(v => app.vault.fileMap[v].children?.length)
415 | // Choose folders with non-markdown files
416 | .filter(v =>
417 | {
418 | for (const child of app.vault.fileMap[v].children)
419 | {
420 | if (child.extension && child.extension !== "md")
421 | {
422 | return true;
423 | }
424 | }
425 | return false;
426 | });
427 |
428 | // Choose a folder to create cards from
429 | let folder =
430 | popups.pick("Choose a folder to take card images from", folders, 0, "adaptive");
431 | if (!folder) { return null; }
432 | folder = folders[folder];
433 |
434 | // Choose a pile to put the cards into
435 | const pileId =
436 | await userChoosesPileId("to hold new cards", "Card images not loaded.");
437 | if (pileId === null) { return pile[1]; }
438 |
439 | return expand("cards fromfolder " + pileId[0] + " " + folder);
440 | ```
441 | __
442 | ui cards fromfolder - User choses a folder to create new cards from, then choses a card-pile to put them into. Cards are created and added to the chosen card-pile.
443 | ***
444 |
445 |
446 | __
447 | ```
448 | ^ui cards? list$
449 | ```
450 | __
451 | ```js
452 | return expand("cards list");
453 | ```
454 | __
455 | ui cards list - Lists all card-piles.
456 |
457 |
458 | __
459 | ```
460 | ^ui cards? peek$
461 | ```
462 | __
463 | ```js
464 | // Show a custom popup
465 | const data =
466 | {
467 | defaults:
468 | {
469 | src: _inlineScripts.state.sessionState.cards.priorPeekPile || "",
470 | count: 1,
471 | topBottom: 0
472 | },
473 | descriptions:
474 | {
475 | src: "Card-pile to peek into",
476 | count: "How many cards to peek",
477 | topBottom: "Peek at top or bottom of card-pile"
478 | }
479 | };
480 | const result = popups.custom(
481 | "Choose peek options", _inlineScripts.cards_ui.manipulateCardsPopup, data);
482 | if (result === null) { return null; }
483 |
484 | // Run the non-ui shortcut
485 | return expand(
486 | "cards peek " + result.count + " " + result.src + " " +
487 | (result.topBottom === "1" ? "y" : "n"));
488 | ```
489 | __
490 | ui cards peek - User choses a card-pile, how many cards to peek and what side to peek from (top or bottom). The peeked cards are printed to the note.
491 | ***
492 |
493 |
494 | __
495 | ```
496 | ^ui cards? draw$
497 | ```
498 | __
499 | ```js
500 | // Show a custom popup
501 | const data =
502 | {
503 | defaults:
504 | {
505 | src: _inlineScripts.state.sessionState.cards.priorDrawSrc || "",
506 | dst: _inlineScripts.state.sessionState.cards.priorDrawDst || "",
507 | count: 1,
508 | },
509 | descriptions:
510 | {
511 | src: "Card-pile to draw from",
512 | dst: "Card-pile to add to",
513 | count: "How many cards to draw"
514 | }
515 | };
516 | const result = popups.custom(
517 | "Choose draw options", _inlineScripts.cards_ui.manipulateCardsPopup, data);
518 | if (result === null) { return null; }
519 |
520 | // Run the non-ui shortcut
521 | return expand("cards draw " + result.count + " " + result.dst + " " + result.src);
522 | ```
523 | __
524 | ui cards draw - User choses one card-pile to draw from, one to add to and how many cards to draw. Cards are moved from the top of one card-pile to the top of the other.
525 |
526 |
527 | __
528 | ```
529 | ^ui cards? pick$
530 | ```
531 | __
532 | ```js
533 | // Show a custom popup
534 | const data =
535 | {
536 | defaults:
537 | {
538 | src: _inlineScripts.state.sessionState.cards.priorPickSrc || "",
539 | dst: _inlineScripts.state.sessionState.cards.priorPickDst || ""
540 | },
541 | descriptions:
542 | {
543 | src: "Card-pile to pick from",
544 | dst: "Card-pile to add to"
545 | }
546 | };
547 | const result = popups.custom(
548 | "Choose pick options", _inlineScripts.cards_ui.manipulateCardsPopup, data);
549 | if (result === null) { return null; }
550 |
551 | // Run the non-ui shortcut
552 | return expand("cards pick " + result.dst + " " + result.src);
553 | ```
554 | __
555 | ui cards pick - User choses one card-pile to pick from and one to add to, then the user picks the cards to move from the top of one to the top of the other.
556 | ***
557 |
558 |
559 | __
560 | ```
561 | ^ui cards? shuffle$
562 | ```
563 | __
564 | ```js
565 | // Show a custom popup
566 | const data =
567 | {
568 | defaults:
569 | {
570 | src: 0,
571 | rotate: false
572 | },
573 | descriptions:
574 | {
575 | src: "Card-pile to shuffle",
576 | rotate: "Rotate cards while shuffling?"
577 | }
578 | };
579 | const result = popups.custom(
580 | "Choose pick options", _inlineScripts.cards_ui.manipulateCardsPopup, data);
581 | if (result === null) { return null; }
582 |
583 | // Run the non-ui shortcut
584 | return expand("cards shuffle " + result.src + " " + (result.rotate ? "y" : "n"));
585 | ```
586 | __
587 | ui cards shuffle - User choses a card-pile, and whether to rotate the cards while shuffling. The card-pile is then shuffled.
588 |
589 |
590 | __
591 | ```
592 | ^ui cards? unrotate$
593 | ```
594 | __
595 | ```js
596 | const pile = await userChoosesPileId("to un-rotate", "Cards not un-rotated.");
597 | if (pile[0] == null) { return pile[1]; }
598 | return expand("cards unrotate " + pile[0]);
599 | ```
600 | __
601 | ui cards unrotate - User choses a card-pile, then all the card-pile's cards are turned right-side-up.
602 |
603 |
604 | __
605 | ```
606 | ^ui cards? reverse$
607 | ```
608 | __
609 | ```js
610 | const pile = await userChoosesPileId("to reverse", "Card-pile not reversed.");
611 | if (pile[0] == null) { return pile[1]; }
612 | return expand("cards reverse " + pile[0]);
613 | ```
614 | __
615 | ui cards reverse - User choses a card-pile, then the card-pile's order is reversed.
616 |
617 |
618 | __
619 | ```
620 | ^ui cards? recall$
621 | ```
622 | __
623 | ```js
624 | const pile = await userChoosesPileId("to recall", "Cards not recalled.");
625 | if (pile[0] == null) { return pile[1]; }
626 | return expand("cards recall " + pile[0]);
627 | ```
628 | __
629 | ui cards recall - User choses a card-pile, then the card-pile is recalled. Recalling means finding all cards with the card-pile as their origin, then moving them to the card-pile.
630 |
631 |
632 | __
633 | ```
634 | ^ui cards? reorigin$
635 | ```
636 | __
637 | ```js
638 | const pile = await userChoosesPileId("to re-origin", "Cards not re-origined.");
639 | if (pile[0] == null) { return pile[1]; }
640 | return expand("cards reorigin " + pile[0]);
641 | ```
642 | __
643 | ui cards reorigin - Asks the user to choose a card-pile, then the card-pile is re-origined.. Re-origining means setting the origin of all cards in a card-pile to that card-pile.
644 | ***
645 |
646 |
647 | __
648 | ```
649 | ^ui cards? import$
650 | ```
651 | __
652 | ```js
653 | // Get the pile to import into
654 | let pile =
655 | await userChoosesPileId("to import into", "Card-pile not imported.");
656 | if (pile[0] == null) { return pile[1]; }
657 | pile = pile[0];
658 |
659 | // Get the pile's data-string for the default value
660 | let defaultDataString =
661 | JSON.stringify(_inlineScripts.state.sessionState.cards.piles[pile]);
662 |
663 | // Get the data-string to import
664 | const toImport = popups.input("Enter the data-string to import", defaultDataString);
665 | if (toImport === null) { return null; }
666 | if (toImport === defaultDataString) { return null; }
667 |
668 | // Run the import shortcut
669 | return expand("cards reorigin " + pile + " " + toImport);
670 | ```
671 | __
672 | ui cards import - User chooses a card-pile and enters a data-string representation of a card-pile.
673 | The data-string is turned into a card-pile and assigned to the card-pile the user chose.
674 |
675 |
676 | __
677 | ```
678 | ^ui cards? export$
679 | ```
680 | __
681 | ```js
682 | const pile = await userChoosesPileId("to export", "Card-pile not exported.");
683 | if (pile[0] == null) { return pile[1]; }
684 | return expand("cards export " + pile[0]);
685 | ```
686 | __
687 | ui cards export - User chooses a card-pile, then the card-pile is exported to a data-string, which is printed to the note.
688 |
--------------------------------------------------------------------------------
/mythicv2.sfile.md:
--------------------------------------------------------------------------------
1 | ---
2 | obsidianUIMode: preview
3 | ---
4 |
5 | Shortcuts for Mythic Variations 2. Mythic GME, along with it's "Variations 2" supplement, is an excellent "GM emulator" system for solo and GM'less gaming. It was designed by Tana Pigeon. You can find more info about Mythic Variations 2 at [wordmill games](http://wordmillgames.com/mythic-variations-2.html).
6 |
7 | This shortcut-file has a tutorial video available:
8 | - [Using the "mythicv2" shortcut-file to play Mythic Variations 2](https://www.youtube.com/watch?v=fvrxAg22lYg) (runtime 7:27)
9 |
10 | Incompatible with __mythicgme.sfile__. If __mythicgme.sfile__ comes before __mythicv2.sfile__ in the shortcut-list, then __mythicv2.sfile__ will be disabled.
11 |
12 | Uses __state.sfile__ shortcut-file (optional).
13 | It uses this to save & load the chaos value, the scene count, and "Details" mode.
14 |
15 | Uses __lists.sfile__ shortcut-file (optional).
16 | Adds and uses lists for pcs, npcs and threads.
17 | This requires that __lists.sfile__ comes before __mythicv2.sfile__ in the shortcut-list.
18 | If the pc, npc and thread lists have items, then the __event__ and __detail__ shortcuts will incorporate them into their results.
19 |
20 | This shortcut-file has supplementary shortcut files:
21 | __mythicv2_ui.sfile__ - Graphical UI versions of some of these shortcuts.
22 |
23 |
24 | __
25 | ```
26 | ^sfile setup$
27 | ```
28 | __
29 | ```js
30 | // Block loading if mythicgme is already setup (incompatible shortcut-files)
31 | if (_inlineScripts.inlineScripts.sfileIndices["mythicgme"])
32 | {
33 | print(
34 | "The mythicv2 shortcut-file is disabled as it is incompatible with " +
35 | "the mythicgme shortcut-file.");
36 | return true;
37 | }
38 |
39 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
40 |
41 | // Setup the state
42 | confirmObjectPath("_inlineScripts.state.sessionState.mythicv2.chaos", 4);
43 | confirmObjectPath("_inlineScripts.state.sessionState.mythicv2.scene", 1);
44 |
45 | // Setup the lists: pcs, npcs, threads
46 | confirmObjectPath(
47 | "_inlineScripts.state.sessionState.lists.pcs", { type: "basic", content: [] });
48 | confirmObjectPath(
49 | "_inlineScripts.state.sessionState.lists.npcs", { type: "basic", content: [] });
50 | confirmObjectPath(
51 | "_inlineScripts.state.sessionState.lists.threads",{ type: "basic", content: [] });
52 |
53 | // Setup the session-specific state
54 | confirmObjectPath("_inlineScripts.mythicv2.details", []);
55 |
56 | // Event callback - state.onReset
57 | confirmObjectPath(
58 | "_inlineScripts.state.listeners.onReset.mythicv2",
59 | function()
60 | {
61 | expand("mythicv2 reset noconfirm");
62 | });
63 | ```
64 | __
65 | Sets up this shortcut-file
66 |
67 |
68 | __
69 | ```
70 | ^sfile shutdown$
71 | ```
72 | __
73 | ```js
74 | // Event callback removal
75 | delete _inlineScripts.state?.listeners?.onReset?.mythicv2;
76 |
77 | // State removal
78 | delete _inlineScripts.state?.sessionState?.mythicv2;
79 | ```
80 | __
81 | Shuts down this shortcut-file
82 |
83 |
84 | __
85 | ```
86 | ^mythic(?:v2)? reset$
87 | ```
88 | __
89 | ```js
90 | // Confirm
91 | if (!popups.confirm("Confirm resetting the Mythic Version 2 system"))
92 | {
93 | return null;
94 | }
95 |
96 | // Reset
97 | return expand("mythicv2 reset noconfirm");
98 | ```
99 | __
100 | mythicv2 reset - Resets mythic state to defaults and displays scene heading.
101 |
102 |
103 | __
104 | ```
105 | ^mythic(?:v2)? reset noconfirm$
106 | ```
107 | __
108 | ```js
109 | const confirmObjectPath = _inlineScripts.inlineScripts.HelperFncs.confirmObjectPath;
110 |
111 | // Reset the state
112 | confirmObjectPath("_inlineScripts.state.sessionState.mythicv2");
113 | _inlineScripts.state.sessionState.mythicv2.chaos = 4;
114 | _inlineScripts.state.sessionState.mythicv2.scene = 1;
115 |
116 | // Reset the lists: pcs, npcs, threads
117 | confirmObjectPath("_inlineScripts.state.sessionState.lists");
118 | _inlineScripts.state.sessionState.lists.pcs = { type: "basic", content: [] };
119 | _inlineScripts.state.sessionState.lists.npcs = { type: "basic", content: [] };
120 | _inlineScripts.state.sessionState.lists.threads = { type: "basic", content: [] };
121 |
122 | // Make sure the session-specific state is still setup
123 | confirmObjectPath("_inlineScripts.mythicv2.details", []);
124 |
125 | // Return a heading for scene 1
126 | return "***\n\n\n### SCENE " +
127 | _inlineScripts.state.sessionState.mythicv2.scene + "\n- Setup:\n - ";
128 | ```
129 | __
130 | hidden - No-confirm reset
131 |
132 |
133 | __
134 | ```
135 | ^mythicv2 details ?(|[y|n])$
136 | ```
137 | __
138 | ```js
139 | // If the parameter is set, update the flag
140 | if ($1)
141 | {
142 | _inlineScripts.state.sessionState.mythicv2.showDetails = ($1 === "y");
143 | }
144 |
145 | // Return the current flag state
146 | return expFormat(
147 | "Mythic details are " + (_inlineScripts.state.sessionState.mythicv2.showDetails ?
148 | "__enabled__" : "__disabled__") + ".");
149 | ```
150 | __
151 | mythicv2 details {state: y OR n, default: ""} - If {state} is given, assigns it to the mythicv2 "details" mode. Otherwise, displays the current "details" mode.
152 | ***
153 |
154 | __
155 | __
156 | ```js
157 | // Make a roll from 1 to max. Store the result as a detail with the name parameter
158 | function roll(name, max)
159 | {
160 | return addDetails(name, Math.trunc(Math.random() * max + 1));
161 | }
162 |
163 | // Pick an item from array a. Store the result as a detail with the name parameter
164 | function aPick(name, a) { return a[roll(name, a.length)-1]; }
165 |
166 | // Pick an item from array a, weighted by element wIndex of the item. If theRoll is
167 | // passed, use that as the roll. Store the result as a detail with the name parameter
168 | function aPickWeight(name, a, wIndex, theRoll)
169 | {
170 | wIndex = wIndex || 1;
171 | theRoll = theRoll || roll("",a.last()[wIndex]);
172 | addDetails(name, theRoll);
173 | for (const item of a)
174 | {
175 | if (item[wIndex] >= theRoll)
176 | {
177 | return item;
178 | }
179 | }
180 | return a.last();
181 | }
182 |
183 | // Takes a variable # of arguments. Arguments are grouped into pairs. The first
184 | // item of the pair is a roll's name. The secondis the roll's value.
185 | function addDetails()
186 | {
187 | for (let i = 0; i < arguments.length; i+= 2)
188 | {
189 | if (!arguments[i]) { continue; }
190 | _inlineScripts.mythicv2.details.push(arguments[i] + "=" + arguments[i+1]);
191 | }
192 | return arguments[arguments.length - 1];
193 | }
194 |
195 | // Return a string representing the detail data gathered for the current action up to
196 | // this point. Each data point is a roll. If the flag is off, this returns "".
197 | function getDetails()
198 | {
199 | if (!_inlineScripts.state.sessionState.mythicv2.showDetails ||
200 | !_inlineScripts.mythicv2.details.length)
201 | {
202 | return "";
203 | }
204 | return "\n- _" + _inlineScripts.mythicv2.details.join(" ") + "_";
205 | }
206 |
207 | // Reset the details if the current shortcut is user-triggered (i.e. a new action)
208 | function clearDetailsIfUserTriggered()
209 | {
210 | if (expansionInfo.isUserTriggered)
211 | {
212 | _inlineScripts.mythicv2.details = [];
213 | }
214 | }
215 |
216 | // The chaos adjustment modifier is used in a few ways. This lets it's calculation
217 | // be simple to trigger and use.
218 | function getChaosAdjust(multiplier)
219 | {
220 | const chaos = Number(_inlineScripts.state.sessionState.mythicv2.chaos);
221 | const result =
222 | ( (chaos === 3) ? 2 : (chaos === 6) ? -2 : 0 ) * (multiplier || 1);
223 | return addDetails("chaosAdjust", result);
224 | }
225 | ```
226 | __
227 | Helper scripts
228 |
229 |
230 | __
231 | ```
232 | ^fate ?(|-4|-3|-2|-1|0|1|2|3|4) ?(|y|n)$
233 | ```
234 | __
235 | ```js
236 | // The parameter defaults to 0
237 | $1 = Number($1) || 0;
238 |
239 | // Start a new details list
240 | clearDetailsIfUserTriggered();
241 |
242 | // Data
243 | const ODDS = ["impossible","no way","very unlikely","unlikely","50/50","likely","very likely","sure thing","has to be"];
244 |
245 | // Get the wanted flag from the parameter
246 | let wanted = ($2==="n" ? false : true);
247 |
248 | // Make the critical rolls
249 | let fateRoll1 = roll("fateRoll1", 10);
250 | let fateRoll2 = roll("fateRoll2", 10);
251 | let chaosRoll = roll("chaosRoll", 10);
252 |
253 | // calculate the final roll result
254 | let result =
255 | fateRoll1 + fateRoll2 + addDetails("oddsAdjust", ($1) * 2) +
256 | getChaosAdjust(wanted ? 1 : -1);
257 | result = result > 10 ? "YES" : "NO";
258 |
259 | // The chaos roll triggered. Is it an extreme result, random event or both?
260 | let isChaotic = (chaosRoll <= _inlineScripts.state.sessionState.mythicv2.chaos);
261 | let isExtreme = isChaotic && !!(fateRoll1 % 2) && !!(fateRoll2 % 2);
262 | let isEvent = isChaotic && !(fateRoll1 % 2) && !(fateRoll2 % 2);
263 | if (isChaotic && fateRoll1 === fateRoll2) isExtreme = isEvent = true;
264 |
265 | // If extreme, add such to the result
266 | result = (isExtreme ? "EXTREME " : "") + result;
267 |
268 | // If random event, roll it and record the result
269 | let evtText = isEvent ? ( "\nevent - " + expand("event")[1] ) : "";
270 |
271 | // Return the roll result, along with any random event result
272 | return expFormat(
273 | "Fate check (" + ODDS[$1 + 4] + "):\n" + result + evtText + getDetails());
274 | ```
275 | __
276 | fate {odds: -4 TO 4 ("impossible" TO "has to be"), default: 0 ("50/50")} {wanted: y OR n, default: y} - Makes a fate check based on {odds}.
277 | This fate check is also based on {wanted}: the desired outcome. Used for the direction of the chaos modifier.
278 |
279 |
280 | __
281 | ```
282 | ^fate ?(.*)$
283 | ```
284 | __
285 | ```js
286 | // Parameter is case-insensitive
287 | $1 = $1.trim().toLowerCase();
288 |
289 | // Start a new details list
290 | clearDetailsIfUserTriggered();
291 |
292 | // Data
293 | const INPUT_ODDS =
294 | {
295 | "impossible": -4, "no way": -3, "very unlikely": -2, "unlikely": -1, "50/50": 0, "likely": 1, "very likely": 2, "sure thing": 3, "has to be": 4
296 | };
297 |
298 | // Pull the wanted flag from the parameter
299 | let wanted = " y";
300 | if ($1.endsWith(" y"))
301 | {
302 | $1 = $1.slice(0,-2);
303 | }
304 | else if ($1.endsWith(" n"))
305 | {
306 | wanted = " n";
307 | $1 = $1.slice(0,-2);
308 | }
309 |
310 | // Work out the input odds number by comparing the parameter to the data table
311 | const inputOdds = INPUT_ODDS[$1.trim()] || 0;
312 |
313 | // Rely on the other "fate" shortcut to finish the job
314 | return expand("fate " + inputOdds + wanted);
315 | ```
316 | __
317 | fate {odds: text, default: "50/50"} {wanted: y OR n, default: y} - Makes a fate check based on {odds}: a specific text such as "impossible", "sure thing", etc.
318 | This fate check is also based on {wanted}: the desired outcome. Used for the direction of the chaos modifier.
319 |
320 |
321 | __
322 | ```
323 | ^detail$
324 | ```
325 | __
326 | ```js
327 | // Start a new details list
328 | clearDetailsIfUserTriggered();
329 |
330 | // Data
331 | let outcomes = [ ["ANGER",4],["SADNESS",5],["FEAR",6],["THREAD NEGATIVE",7,"threads"],["PC NEGATIVE",8,"pcs"],["FOCUS NPC",9,"npcs"],["NPC POSITIVE",10,"npcs"],["FOCUS PC",11,"pcs"],["NPC NEGATIVE",12,"npcs"],["FOCUS THREAD",13,"threads"],["PC POSITIVE",14,"pcs"],["THREAD POSITIVE",15,"threads"],["COURAGE",16],["HAPPINESS",17],["CALM",99] ];
332 |
333 | // Make the detail roll
334 | let result = roll("roll1", 10) + roll("roll2", 10) + getChaosAdjust();
335 |
336 | // Pick from the weighted data table based on the detail roll
337 | result = aPickWeight("", outcomes, 1, result);
338 |
339 | // If the result references one of the lists, roll on the list and record the result
340 | let focus = result[2] ? expand("lists pick " + result[2]) : "";
341 | focus = (focus.length < 2) ? "" : (" (" + focus[1] + ")");
342 |
343 | // Return the detail roll, along with the item of the list, if applicable
344 | return expFormat("Detail:\n" + result[0] + focus + getDetails());
345 | ```
346 | __
347 | detail - Makes a detail check.
348 |
349 |
350 | __
351 | ```
352 | ^event$
353 | ```
354 | __
355 | ```js
356 | // Start a new details list
357 | clearDetailsIfUserTriggered();
358 |
359 | // Data
360 | let outcomes = [ ["REMOTE",7],["NPC ACTS",28,"npcs"],["NEW NPC",35,null,true],["THREAD ADVANCE",45,"threads"],["THREAD LOSS",52,"threads"],["THREAD END",55,"threads"],["PC NEGATIVE",67,"pcs"],["PC POSITIVE",75,"pcs"],["AMBIGUOUS",83],["NPC NEGATIVE",92,"npcs"],["NPC POSITIVE",100,"npcs"] ];
361 |
362 | // Get an item from the weighted data table
363 | let result = aPickWeight("eventRoll", outcomes);
364 |
365 | // If the result references one of the lists, roll on the list and record the result
366 | let focus = result[2] ? expand("lists pick " + result[2]) : "";
367 | focus = (focus.length < 2) ? "" : (" (" + focus[1] + ")");
368 |
369 | // Make and record a meaning roll
370 | let meaning = expand("meaning " + (result[3] ? "description" : "action"))[1];
371 |
372 | // Return the event roll result, along with the item of the list, if applicable
373 | return expFormat(
374 | [ "Event:\n", result[0] + focus + " - " + meaning, getDetails(), "" ]);
375 | ```
376 | __
377 | event - Makes an event check.
378 | ***
379 |
380 |
381 | __
382 | ```
383 | ^meaning(?:| action)$
384 | ```
385 | __
386 | ```js
387 | // Start a new details list
388 | clearDetailsIfUserTriggered();
389 |
390 | // Data
391 | let value1 = ["ATTAINMENT","STARTING","NEGLECT","FIGHT","RECRUIT","TRIUMPH","VIOLATE","OPPOSE","MALICE","COMMUNICATE","PERSECUTE","INCREASE","DECREASE","ABANDON","GRATIFY","INQUIRE","ANTAGONISE","MOVE","WASTE","TRUCE","RELEASE","BEFRIEND","JUDGE","DESERT","DOMINATE","PROCRASTINATE","PRAISE","SEPARATE","TAKE","BREAK","HEAL","DELAY","STOP","LIE","RETURN","IMMITATE","STRUGGLE","INFORM","BESTOW","POSTPONE","EXPOSE","HAGGLE","IMPRISON","RELEASE","CELEBRATE","DEVELOP","TRAVEL","BLOCK","HARM","DEBASE","OVERINDULGE","ADJOURN","ADVERSITY","KILL","DISRUPT","USURP","CREATE","BETRAY","AGREE","ABUSE","OPPRESS","INSPECT","AMBUSH","SPY","ATTACH","CARRY","OPEN","CARELESSNESS","RUIN","EXTRAVAGANCE","TRICK","ARRIVE","PROPOSE","DIVIDE","REFUSE","MISTRUST","DECEIVE","CRUELTY","INTOLERANCE","TRUST","EXCITEMENT","ACTIVITY","ASSIST","CARE","NEGLIGENCE","PASSION","WORK_HARD","CONTROL","ATTRACT","FAILURE","PURSUE","VENGEANCE","PROCEEDINGS","DISPUTE","PUNISH","GUIDE","TRANSFORM","OVERTHROW","OPPRESS","CHANGE"];
392 | let value2 = ["GOALS","DREAMS","ENVIRONMENT","OUTSIDE","INSIDE","REALITY","ALLIES","ENEMIES","EVIL","GOOD","EMOTIONS","OPPOSITION","WAR","PEACE","THE_INNOCENT","LOVE","THE_SPIRITUAL","THE_INTELLECTUAL","NEW_IDEAS","JOY","MESSAGES","ENERGY","BALANCE","TENSION","FRIENDSHIP","THE_PHYSICAL","A_PROJECT","PLEASURES","PAIN","POSSESSIONS","BENEFITS","PLANS","LIES","EXPECTATIONS","LEGAL_MATTERS","BUREAUCRACY","BUSINESS","A_PATH","NEWS","EXTERIOR_FACTORS","ADVICE","A_PLOT","COMPETITION","PRISON","ILLNESS","FOOD","ATTENTION","SUCCESS","FAILURE","TRAVEL","JEALOUSY","DISPUTE","HOME","INVESTMENT","SUFFERING","WISHES","TACTICS","STALEMATE","RANDOMNESS","MISFORTUNE","DEATH","DISRUPTION","POWER","A_BURDEN","INTRIGUES","FEARS","AMBUSH","RUMOR","WOUNDS","EXTRAVAGANCE","A_REPRESENTATIVE","ADVERSITIES","OPULENCE","LIBERTY","MILITARY","THE_MUNDANE","TRIALS","MASSES","VEHICLE","ART","VICTORY","DISPUTE","RICHES","STATUS_QUO","TECHNOLOGY","HOPE","MAGIC","ILLUSIONS","PORTALS","DANGER","WEAPONS","ANIMALS","WEATHER","ELEMENTS","NATURE","THE_PUBLIC","LEADERSHIP","FAME","ANGER","INFORMATION"];
393 |
394 | // Roll on the meaning tables
395 | let result = aPick("actionRoll1", value1) + " _(of)_ " + aPick("actionRoll2", value2);
396 |
397 | // Return the roll result
398 | return expFormat([ "Meaning (action):\n", result, getDetails(), "" ]);
399 | ```
400 | __
401 | meaning action - Rolls on the action meaning tables.
402 | - Alternative: __meaning__
403 |
404 |
405 | __
406 | ```
407 | ^meaning description$
408 | ```
409 | __
410 | ```js
411 | // Start a new details list
412 | clearDetailsIfUserTriggered();
413 |
414 | // Data
415 | let value1 = ["ABNORMALLY","ADVENTUROUSLY","AGGRESSIVELY","ANGRILY","ANXIOUSLY","AWKWARDLY","BEAUTIFULLY","BLEAKLY","BOLDLY","BRAVELY","BUSILY","CALMLY","CAREFULLY","CARELESSLY","CAUTIOUSLY","CEASELESSLY","CHEERFULLY","COMBATIVELY","COOLLY","CRAZILY","CURIOUSLY","DAINTILY","DANGEROUSLY","DEFIANTLY","DELIBERATELY","DELIGHTFULLY","DIMLY","EFFICIENTLY","ENERGETICALLY","ENORMOUSLY","ENTHUSIASTICALLY","EXCITEDLY","FEARFULLY","FEROCIOUSLY","FIERCELY","FOOLISHLY","FORTUNATELY","FRANTICALLY","FREELY","FRIGHTENINGLY","FULLY","GENEROUSLY","GENTLY","GLADLY","GRACEFULLY","GRATEFULLY","HAPPILY","HASTILY","HEALTHILY","HELPFULLY","HELPLESSLY","HOPELESSLY","INNOCENTLY","INTENSELY","INTERESTINGLY","IRRITATINGLY","JOVIALLY","JOYFULLY","JUDGEMENTALLY","KINDLY","KOOKILY","LAZILY","LIGHTLY","LOOSELY","LOUDLY","LOVINGLY","LOYALLY","MAJESTICALLY","MEANINGFULLY","MECHANICALLY","MISERABLY","MOCKINGLY","MYSTERIOUSLY","NATURALLY","NEATLY","NICELY","ODDLY","OFFENSIVELY","OFFICIALLY","PARTIALLY","PEACEFULLY","PERFECTLY","PLAYFULLY","POLITELY","POSITIVELY","POWERFULLY","QUAINTLY","QUARRELSOMELY","QUIETLY","ROUGHLY","RUDELY","RUTHLESSLY","SLOWLY","SOFTLY","SWIFTLY","THREATENINGLY","VERY","VIOLENTLY","WILDLY","YIELDINGLY"];
416 | let value2 = ["ABANDONED","ABNORMAL","AMUSING","ANCIENT","AROMATIC","AVERAGE","BEAUTIFUL","BIZARRE","CLASSY","CLEAN","COLD","COLORFUL","CREEPY","CUTE","DAMAGED","DARK","DEFEATED","DELICATE","DELIGHTFUL","DIRTY","DISAGREEABLE","DISGUSTING","DRAB","DRY","DULL","EMPTY","ENORMOUS","EXOTIC","FADED","FAMILIAR","FANCY","FAT","FEEBLE","FEMININE","FESTIVE","FLAWLESS","FRESH","FULL","GLORIOUS","GOOD","GRACEFUL","HARD","HARSH","HEALTHY","HEAVY","HISTORICAL","HORRIBLE","IMPORTANT","INTERESTING","JUVENILE","LACKING","LAME","LARGE","LAVISH","LEAN","LESS","LETHAL","LONELY","LOVELY","MACABRE","MAGNIFICENT","MASCULINE","MATURE","MESSY","MIGHTY","MILITARY","MODERN","EXTRAVAGANT","MUNDANE","MYSTERIOUS","NATURAL","NONDESCRIPT","ODD","PALE","PETITE","POOR","POWERFUL","QUAINT","RARE","REASSURING","REMARKABLE","ROTTEN","ROUGH","RUINED","RUSTIC","SCARY","SIMPLE","SMALL","SMELLY","SMOOTH","SOFT","STRONG","TRANQUIL","UGLY","VALUABLE","WARLIKE","WARM","WATERY","WEAK","YOUNG"];
417 |
418 | // Roll on the meaning tables
419 | let result =
420 | aPick("descriptorRoll1", value1) + " " + aPick("descriptorRoll2", value2);
421 |
422 | // Return the roll result
423 | return expFormat([ "Meaning (description):\n", result, getDetails(), "" ]);
424 | ```
425 | __
426 | meaning description - Rolls on the description meaning tables.
427 | ***
428 |
429 |
430 | __
431 | ```
432 | ^scene get$
433 | ```
434 | __
435 | ```js
436 | return expFormat(
437 | "The current scene is " + _inlineScripts.state.sessionState.mythicv2.scene + ".");
438 | ```
439 | __
440 | scene get - Shows the current scene.
441 |
442 |
443 | __
444 | ```
445 | ^scene (1|-1)$
446 | ```
447 | __
448 | ```js
449 | // Start a new details list
450 | clearDetailsIfUserTriggered();
451 |
452 | // Adjust the chaos by the parameter
453 | let result =
454 | ($1 === "1") ? expUnformat(expand("chaos++")) :
455 | ($1 === "-1") ? expUnformat(expand("chaos--")) :
456 | "Chaos is unchanged at " + expUnformat(expand("chaos"));
457 |
458 | // Prefix the scene heading with a bunch of space
459 | result += "\n\n\n***\n\n\n";
460 |
461 | // Advance the scene index
462 | _inlineScripts.state.sessionState.mythicv2.scene++;
463 |
464 | // Add the scene header
465 | result += "### SCENE " + _inlineScripts.state.sessionState.mythicv2.scene;
466 |
467 | // Roll scene check for scene control
468 | let chk = roll("sceneCheck", 10);
469 | if (chk <= _inlineScripts.state.sessionState.mythicv2.chaos)
470 | {
471 | // Odd roll? Modify the scene in some way
472 | if (chk % 2)
473 | {
474 | result += "\n- Scene modified";
475 | }
476 | // Even Roll? Replace the scene with a random event
477 | else
478 | {
479 | result += "\n- Scene replaced:\n" + " - event - " + expand("event")[1];
480 | }
481 | }
482 |
483 | // Return the scene header
484 | return result + getDetails() + "\n- setup:\n - ";
485 | ```
486 | __
487 | scene {chaos adjust: -1 OR 1} - Shifts the chaos value by {chaosAdjust}, then increments the current scene and run a scene check.
488 |
489 |
490 | __
491 | ```
492 | ^chaos$
493 | ```
494 | __
495 | ```js
496 | // Return the current chaos value, wrapped in a nice message
497 | return expFormat(
498 | [ "Chaos is __", _inlineScripts.state.sessionState.mythicv2.chaos, "__." ]);
499 | ```
500 | __
501 | chaos - Shows the current chaos value.
502 |
503 |
504 | __
505 | ```
506 | ^chaos--$
507 | ```
508 | __
509 | ```js
510 | // Lower the chaos value
511 | _inlineScripts.state.sessionState.mythicv2.chaos--;
512 |
513 | // Floor the chaos value (with expansion notifying user of the flooring)
514 | if (_inlineScripts.state.sessionState.mythicv2.chaos < 3)
515 | {
516 | _inlineScripts.state.sessionState.mythicv2.chaos = 3;
517 | return expFormat("Chaos remains at __3__ (hit minimum).");
518 | }
519 |
520 | return expFormat(
521 | "Chaos lowered to __" + _inlineScripts.state.sessionState.mythicv2.chaos + "__.");
522 | ```
523 | __
524 | chaos-- - Decreases the chaos value by 1 (minimum of 3).
525 |
526 |
527 | __
528 | ```
529 | ^chaos\+\+$
530 | ```
531 | __
532 | ```js
533 | // Raise the chaos value
534 | _inlineScripts.state.sessionState.mythicv2.chaos++;
535 |
536 | // Ceiling the chaos value (with expansion notifying user of the ceiling)
537 | if (_inlineScripts.state.sessionState.mythicv2.chaos > 6)
538 | {
539 | _inlineScripts.state.sessionState.mythicv2.chaos = 6;
540 | return expFormat("Chaos remains at __6__ (hit maximum).");
541 | }
542 |
543 | return expFormat(
544 | "Chaos raised to __" + _inlineScripts.state.sessionState.mythicv2.chaos + "__.");
545 | ```
546 | __
547 | chaos++ - Increases the chaos value by 1 (maximum of 6).
548 |
549 |
550 | __
551 | ```
552 | ^chaos=([3-6])$
553 | ```
554 | __
555 | ```js
556 | // Explicity set the chaos to the parameter
557 | _inlineScripts.state.sessionState.mythicv2.chaos = $1;
558 |
559 | return expFormat("Chaos set to __" + $1 + "__.");
560 | ```
561 | __
562 | chaos={value: 3 TO 6} - Sets the chaos value to {value}, an integer from 3 to 6.
563 | ***
564 |
565 |
566 | __
567 | ```
568 | ^descriptors?$
569 | ```
570 | __
571 | ```js
572 | // Start a new details list
573 | clearDetailsIfUserTriggered();
574 |
575 | // Make all rolls necessary for an NPC descriptor
576 | return expFormat(
577 | "Descriptors:\n- personality:\n - " + expand("meaning description")[1] +
578 | "\n- activity:\n - " + expand("meaning action")[1] + getDetails());
579 | ```
580 | __
581 | descriptors - Generates a personality and activity descriptor for an NPC.
582 |
583 |
584 | __
585 | ```
586 | ^disposition (-?[0-3])$
587 | ```
588 | __
589 | ```js
590 | // Start a new details list
591 | clearDetailsIfUserTriggered();
592 |
593 | // Data
594 | let outcomes =
595 | [ ["PASSIVE (-2)",5],["MODERATE (0)",10],["ACTIVE (+2)",15],
596 | ["AGGRESSIVE (+4)",99] ];
597 |
598 | // Make the base roll (separated from the descriptor adjust as it's used on its own)
599 | let base = roll("roll1", 10) + roll("roll2", 10);
600 |
601 | // Add the descriptor adjust to the base
602 | let result = base + addDetails("descriptorAdjust", Number($1) * 2);
603 |
604 | // Pick the outcome from the weighted data table based on the resulting roll
605 | result = aPickWeight("", outcomes, 1, result)[0];
606 |
607 | return expFormat("Disposition:\n. " + result + "\n. BASE = " + base + getDetails());
608 | ```
609 | __
610 | disposition {descriptor count: -3 TO 3} - Rolls for an NPC's disposition, modified by {descriptor count}, which represents the total of the NPC's activated descriptors.
611 | - Example: 2 positively activated descriptors and 1 negatively activated descriptor would make a {descriptor count} of __1+1+(-1) = 1__.
612 |
613 |
614 | __
615 | ```
616 | ^disposition (-?[0-3]) ([2-9]|1[0-9]|20)$
617 | ```
618 | __
619 | ```js
620 | // Start a new details list
621 | clearDetailsIfUserTriggered();
622 |
623 | // Data
624 | let outcomes = [
625 | ["PASSIVE (-2)",5],["MODERATE (0)",10],["ACTIVE (+2)",15],
626 | ["AGGRESSIVE (+4)",99] ];
627 |
628 | // In this version, of "disposition", the "base" is passed in.
629 | let result =
630 | addDetails("base", Number($2)) +
631 | addDetails("descriptorAdjust", Number($1) * 2);
632 | result = aPickWeight("", outcomes, 1, result);
633 |
634 | return expFormat("Disposition:\n. " + result[0] + "\n. BASE = " + $2 + getDetails());
635 | ```
636 | __
637 | disposition {descriptorCount: -3 TO 3} {base: 2 TO 20} - Displays the NPC disposition determined by the {base} disposition, modified by {descriptorCount}. {descriptor count} represents the total of the NPC's activated descriptors.
638 | - Example: 2 positively activated descriptors and 1 negatively activated descriptor would make a {descriptor count} of __1+1+(-1) = 1__.
639 |
640 |
641 | __
642 | ```
643 | ^action (-2|0|2|4)$
644 | ```
645 | __
646 | ```js
647 | // Start a new details list
648 | clearDetailsIfUserTriggered();
649 |
650 | // Data
651 | let outcomes1 = [ ["NEW ACTION BASED ON THEME",3],["CONTINUE CURRENT ACTION",5],["CONTINUE CURRENT ACTION, +2 DISPOSITION",6],["CONTINUE CURRENT ACTION, -2 DISPOSITION",7],[false,8,0],[false,9,-4],[false,10,4] ];
652 | let outcomes2 = [ ["TALK / EXPOSITION",6],["NEUTRAL, DISCONNECTED ACTION",8],["ACTION BENEFITS THE PC",10],["GIVE SOMETHING TO THE PC",11],["TRY TO END ENCOUNTER",12],["CHANGE THEME",13],["CHANGE \"%1\" DESCRIPTOR. IT IS ACTIVATED & DECIDES NEXT ACTION.",14,true],["ACT OUT OF SELF INTEREST",17],["TAKE SOMETHING",18],["HURT THE PC",99] ];
653 | let descriptors = ["IDENTITY","PERSONALITY","ACTIVITY"];
654 |
655 | // Roll on outcomes1 table
656 | result = aPickWeight("table1Roll", outcomes1);
657 |
658 | // If roll result is to be passed to outcomes2 table, roll on that
659 | if (!result[0])
660 | {
661 | // Make the formulated roll necessary for the outcomes2 table
662 | result =
663 | roll("table2Roll1", 10) + roll("table2Roll2", 10) +
664 | addDetails("dispositionAdjust", Number($1)) +
665 | addDetails("tableAdjust", result[2]);
666 |
667 | // Pull the rolled result from outcomes2 table
668 | result = aPickWeight("", outcomes2, 1, result);
669 |
670 | // If the outcomes2 roll results in "CHANGE \"%1\" DESCRIPTOR.", pick a
671 | // descriptor and add it to the output text.
672 | if (result[2])
673 | {
674 | result[0] = result[0].replace("%1", aPick("descriptorRoll", descriptors));
675 | }
676 |
677 | // Append a standard text for outcomes2 rolls
678 | result[0] += "\n+2/0/-2 DISPOSITION TO MATCH ACTION.";
679 | }
680 |
681 | return expFormat("Action:\n" + result[0] + getDetails());
682 | ```
683 | __
684 | action {dispositionAdjust: -2 OR 0 OR 2 OR 4} - Makes an NPC behavior check, modified by {dispositionAdjust}: the modifier of the NPC's disposition.
685 |
686 |
687 | __
688 | ```
689 | ^lists? pick (|[_a-zA-Z][_a-zA-Z0-9]*)(| [1-9][0-9]*)$
690 | ```
691 | __
692 | ```js
693 | return [];
694 | ```
695 | __
696 | hidden - A placeholder shortcut in case the shortcut-file lists.sfile isn't available.
697 |
--------------------------------------------------------------------------------