code:
## Basic documentation for the limb access functions in the new limb system.

### ID system

The new system uses new IDs instead of the old ones.
```
limb       new  old
no limb:    0    1
natural:    1    0
basic:      2   -1
sex:        3   -2
beauty:     4   -3
combat:     5   -4
cybernetic: 6   -5
```

### Usage example:

```
<<if isAmputee($activeSlave)>>
    Slave is full amputee.
<</if>>
```

Most functions can be used like this, though some are more specialized.

### Functions:

#### Read-only functions:

* `isAmputee(slave)`:
    True if slave has no limbs, neither natural nor prosthetic.

* `hasAnyNaturalLimbs(slave)`:
    True if slave has at least one natural limb.

* `hasAnyProstheticLimbs(slave)`:
    True if slave has at least one prosthetic limb.

* `hasAnyLegs(slave)`:
    True if slave has at least one leg.

* `hasAnyArms(slave)`:
    True if slave has at least one arm.

* `hasAnyNaturalLegs(slave)`:
    True if slave has at least one leg that is natural

* `hasAnyNaturalArms(slave)`:
    True if slave has at least one arm that is natural

* `hasAnyProstheticArms(slave)`:
    True if slave has at least one arm that is prosthetic

* `hasBothLegs(slave)`:
    True if slave has both legs.

* `hasBothArms(slave)`:
    True if slave has both arms.

* `hasBothNaturalLegs(slave)`:
    True if slave has both legs and they are natural.

* `hasBothNaturalArms(slave)`:
    True if slave has both arms and they are natural.

* `hasAllLimbs(slave)`:
    True if slave has all limbs.

* `hasAllNaturalLimbs(slave)`:
    True if slave has all limbs and all are natural.

* `hasLeftArm(slave)`:
    True if slave has a left arm.

* `hasRightArm(slave)`:
    True if slave has a right arm.

* `hasLeftLeg(slave)`:
    True if slave has a left leg.

* `hasRightLeg(slave)`:
    True if slave has a right leg.

* `getLeftArmID(slave)`:
    Returns limb ID of the left arm.

* `getRightArmID(slave)`:
    Returns limb ID of the right arm.

* `getLeftLegID(slave)`:
    Returns limb ID of the left leg.

* `getRightLegID(slave)`:
    Returns limb ID of the right leg.

* `getLimbCount(slave, id)`:
    Returns count of specified limb ID.
    Can also be used to check for groups:
    101: any limbs that are not amputated
    102: prosthetic limbs off all kind
    103: sex-prosthetic
    104: beauty-prosthetic
    105: combat-prosthetic

    103-105 mean the sum of 3-5 and 6 respectfully.

* `getLegCount(slave, id)`:
    Returns count of legs with specified limb ID.

* `getArmCount(slave, id)`:
    Returns count of arms with specified limb ID.

#### String functions

* `idToDescription(id)`:
    Returns a very short description of the specified limb ID.
    0: "amputated";
    1: "healthy";
    2: "modern prosthetic";
    3: "advanced, sex-focused prosthetic";
    4: "advanced, beauty-focused prosthetic";
    5: "advanced, combat-focused prosthetic";
    6: "highly advanced cybernetic";

* `armsAndLegs(slave, arms, arm, legs, leg)`:
    Returns a string depending on the limbs a slave has. By default this is a
    variation of `arms and legs`, but this can be changed via parameters.

    Examples:
    `armsAndLegs(slave, "hands", "hand", "feet", foot)` returns `hands and feet` for a slave with all limbs,
    `hand and foot` for a slave with one limb each and `feet` for a slave with two legs, but no arms.

    Expects the slave to have at least one limb. Only the first parameter is mandatory, the rest defaults to the
    equivalent of `armsAndLegs(slave, "arms", "arm", "legs", "leg")`

#### Write-only functions

* `removeLimbs(slave, limb`:
    Removes a slave's limbs. Allowed values for limb: `"left arm"`, `"right arm"`, `"left leg"`, `"right leg"`, `"all"`.

* `attachLimbs(slave, limb, id)`:
    Attaches a limb of the specified id. Expects amputated limbs. Will overwrite existing limbs.
    Allowed values for limb: `"left arm"`, `"right arm"`, `"left leg"`, `"right leg"`, `"all"`.

* `configureLimbs(slave, limb, id)`:
    Sets the slave's limb to the specified id and sets all limb related variables to their correct values.
    Intended for use during slave generation and events.
    Allowed values for limb: `"left arm"`, `"right arm"`, `"left leg"`, `"right leg"`, `"all"`.

repo_name: Welptard/fc

path: devNotes/limb functions.md

license: mit


code:
# The Sanity Check

## Structure

The sanity check consists of two parts: The first part is a script with a number
of `git grep` calls using regex to find typical errors. The second parts main
function is checking the syntax of the .tw files.

There exist two versions of the second part:

1.  An older python script that only checks for correct closure of `<<if>>` and
	some other common SugarCube macros.

2.  A newer java program that checks for correct closure of all SugarCube macros
	and HTML tags and has some additional functionality.

It is generally advised to use the java check if possible.

## The Java Check

### Structure

The java check has 3 main parts:

1.  A checks for correct closure of all SugarCube macros and HTML tags in the
	.tw files.

2.  A check to find variable names that are only used once to find misspelled
	variables and leftovers from removed variables.

3.  A search through all files against a dictionary to find common misspellings
	and deprecated code.

Because it provides some functionality that overlap with the `git grep` calls,
the corresponding calls are ignored when this check is run since the java
program is generally faster and produces less false positives.

### Maintenance

In order to keep the number of false positives to a minimum this check has to be
regularly maintained:

1.  When new macros or HTML tags are introduced in the project code they have to
	be added to `devTools/javaSanityCheck/twineTags` or
	`devTools/javaSanityCheck/htmlTags`.

2.  When removing variables they often have a reference left in some kind of BC
	related code. This is a common example when a false positive occurs and that
	variable has to be added to `devTools/javaSanityCheck/ignoredVariables`.

3.  When fully removing variables that were ignored, they have to be removed
	from `devTools/javaSanityCheck/ignoredVariables` too.

4.  When adding variables it can happen that only one usage is identified by the
	check, which means that it throws a false positive. To remove this add the
	variable to `devTools/javaSanityCheck/ignoredVariables`. When the variable
	is used in more places later and found more than once it has to be removed
	again to keep the check as efficient as possible.

5.  When you find common missspellings you can add them to
	`devTools/dictionary_phrases.txt` or `devTools/dictionary_wholeWord.txt`.

6.  When a file produces so many false positives that they are impossible to
	clean up it may be added to `devTools/javaSanityCheck/excluded`.

### Source code

The source code can either be found at
[gitgud.io](https://gitgud.io/Arkerthan/twine-sanitycheck) or, in case the repo
is inaccessible, in a zip file at `devTools/javaSanityCheck/sources.zip`.


code:
Most writing is basically just plain text with branches for different cases.
Important cases to remember:
-Does the slave have a dick/vagina/tits?
-Does the PC have a dick/tits/vagina?
-If the slave has a dick and you plan to use it, can they get erect on their own?
-Is the PC pregnant?
-Is the slave mindbroken?
-Is the slave blind?
-Is the slave pregnant?
-If you penetrate an orifice, is the slave a virgin in that orifice?
-Can the slave move (i.e. is the slave amputated and/or have massive tits and/or a massive dick)?
-Does the slave like/hate trust/fear you?
-Does the slave have fetishes or personality/sexual quirks/flaws that would impact how they react?

It's important to handle every RELEVANT variation of the above cases. Depending on what you're writing, other cases may also warrant consideration; e.g. a breast-play scene will probably need to account for breast size and lactation a lot more than a generic fuck scene. If a scene only applies to a specific type of slave, you can restrict the cases you account for to the ones that are only possible for that type of slave.

There are, broadly speaking, two main ways to do this:
1. Write the same big block of text and then copy/paste it into each relevant case, and tweak the wording slightly.
 ++Less thinking required
 --LOTS of duplicate text; if you need to change something later, you have to change it in many places
2. Decompose the scene into smaller segments or sections that can each be swapped out for the slave type in question.
 ++MUCH easier maintenance
 --Have to figure out how to phrase the scene in a way that can be broken down into discrete chunks

You can also combine the two cases. For example, you may want a complex, interleaved set of <<if>> conditions for the common cases, and then have big blobs for the corner cases that are hard to handle.

It can also be easier if you split the scene into a "prep", "main", and "finish" section (these would all be the same writing block, just 2-3 <<if>> chains in sequence); that way, you could, for example, take all the various possible cases that aren't really unique, and narrate them so that, in the main section, you don't have to do so much explanatory writing for the scene to make sense overall. Then, if necessary, wrap up with the finish section. (See src/pregmod/fSlaveSelfImpreg.tw for an example of this approach)

All the variables for you and the slave are, generally, held in the variables $PC and $activeSlave, respectively. When the PC and the slave have the same attributes, they usually have the same name, but exactly what they do and mean can vary a little bit. RTFM for details. To access a specific variable, you do $var.attribName, so e.g. pregnancy is checked by $activeSlave.preg or $PC.preg. In rarer cases, you'll be dealing with an indexed entry in the $slaves array; if that's the case, you'd use $slaves[_u] or $slaves[$i] (or whatever) instead of $activeSlave. If a second (or third?) slave is present, it will be stored in another variable, the name of which will depend on the scene; for example, when impregnating a slave with another slave, the sperm donor is in $impregnatrix

Conditions are usually checked by
<<if CONDITION>>
	SHOW THIS TEXT
<<elseif CONDITION2>>
	SHOW THIS TEXT INSTEAD
<<else>>
	IF NEITHER CONDITION IS MET, SHOW THIS
<</if>>

Conditions are usually comparative (i.e. $a < $b or $b == 5 or $c != $d.e) and can be chained together with && (and) or || (or) and grouped with parentheses () - for example:
<<if($a > 1 || ($b == 2 && $c != $a))>>
lets you check that either variable A is greater than one, or both B equals two and C is not equal to A.
There are also a few misc functions that let you check things like whether a slave can get pregnant or whether two slaves are mutually fertile

Variable names are interpolated straight into the text, so if you have a slave named "Beth" then the text
"You fuck $activeSlave.slaveName" becomes "You fuck Beth"
You can also explicitly print a variable by doing <<print _varName>> (for temp variables) or <<print $varName>> (for global variables) which can be useful in various situations; it also lets you print the output of code expressions, like <<print $someNumericVar * 10>>.
If you want to change a variable, you do something like
<<set $activeSlave.devotion += 5>>
Which would, in this case, give a devotion increase of 5.

Color is changed with YOUR @@.colorname;COLORED TEXT@@ HERE

So a random (really stupid) example might be:
<<if $activeSlave.fetish == 'mindbroken'>>
	Special big block of mindbroken text here.
<<elseif isAmputee($activeSlave)>>
	Special big block of amputee text here.
<<if $PC.dick > 0>>
	<<set _wasVirgin = ''>>
	<<if $activeSlave.vagina > -1>>
		<<set _orifice = 'vagina'>>
		<<if $activeSlave.vagina == 0>>
			<<set _wasVirgin = 'vaginal'>>
		<</if>>
	<<else>>
		<<set _orifice = 'ass'>>
		<<if $activeSlave.anus == 0>>
			<<set _wasVirgin = "anal">>
		<</if>>
	<</if>>
	You fuck $activeSlave.slaveName's _orifice.
	<<if _wasVirgin != ''>>
		<<if $activeSlave.devotion > 50>>
			$He @@.hotpink;loves@@ losing $his <<print _wasVirgin>> virginity to you.
			<<set $activeSlave.devotion += 5>>
		<<else>>
			$He @@.mediumorchid;@@dislikes losing $his <<print _wasVirgin>> virginity to you.
			<<set $activeSlave.devotion -= 5>>
		<</if>>
	<</if>>
<<elseif $PC.vagina > 0 && $activeSlave.dick > 0>>
	$activeSlave.slaveName
	<<if $activeSlave.devotion > 50>>
		@@.hotpink;lovingly penetrates@@
		<<set $activeSlave.devotion += 5>>
	<<else>>
		indifferently hammers
	<</if>>
	your pussy.
<<elseif $activeSlave.vagina > -1>>
	Lesbian sex here.
<<else>>
	ERROR - PC doesn't have dick or vagina?
<</if>>

If you need to set a temp variable, prefix it with _ instead of $. This way, you don't go cluttering up the world with variables that only matter inside your little writing segment.

You can "hot-test" stuff by text editing your HTML copy of FC, but it's a little more tedious because you have to "escape" double quotes, <, >, and & with " < > and & respectively.

Some hints:
1. Write your logic first, so you don't forget to close tags. In other words, it's better to do
<<if $cond>
	TODO
<<else>>
	TODO
<</if>>
And then fill it in later than it is to end up with a situation where you have a dozen unclosed tags and you can't remember where they are or what they do.

2. For very simple stuff, it's fine to "inline" your stuff. For example, when penetrating a slave, doing "you fuck $his <<if $activeSlave.vagina > -1>>pussy<<else>>ass<</if>>" to show "pussy" or "ass" as necessary. However, if you need to do the same comparison a bunch of times, do something like
<<if $activeSlave.vagina > -1>>
	<<set _targetOrifice = "vagina">>
<<else>>
	<<set _targetOrifice = "asshole">>
<</if>>
And then, when you need it, do "you fuck $his _targetOrifice" in sixteen different places without having the pain in the ass of copy/pasting the same if/else clause every time.

3. INDENT YOUR LOGIC. USE TABS. I'm serious. Don't question me. It will make EVERYONE hate you, when they have to deal with your code, if it's not indented properly.
This is much easier to read:
<<if $cond1>>
	<<if $cond2>>
		whatever
	<<elseif $cond3>>
		whatever
	<<elseif $cond4>>
		<<if $cond5>>
			whatever
		<<else>>
			<<if $cond6>>
				whatever
			<</if>>
		<</if>>
	<<else>>
		whatever
		<<if $cond7>>
			<<if $cond8>>
				whatever
			<</if>>
		<</if>>
	<</if>>
<</if>>
than this:
<<if $cond1>>
<<if $cond2>>
whatever
<<elseif $cond3>>
whatever
<<elseif $cond4>>
<<if $cond5>>
whatever
<<else>>
<<if $cond6>>
whatever
<</if>>
<</if>>
<<else>>
whatever
<<if $cond7>>
<<if $cond8>>
whatever
<</if>>
<</if>>
<</if>>
<</if>>

4. Proof-read your shit before posting it. Spell-check wouldn't hurt.


code:
# Producing slave lists

The namespace `App.UI.SlaveList` provides functions for displaying lists of slaves and interacting with the slaves in those lists. These functions supersede (and extend) the `Slave Summary` passage. They provide a way to display a list, select a slave from a list, generate standard lists for a facility or a leader selection, as well as provide a way to customize output and interaction.

## General concept

The core function is `App.UI.SlaveList.render()`. It renders the list, assembling it from four pieces. The function is declared as follows:

```js
/**
 * @param {number[]} indices
 * @param {Array.<{index: number, rejects: string[]}>} rejectedSlaves
 * @param {slaveTextGenerator} interactionLink
 * @param {slaveTextGenerator} [postNote]
 * @returns {string}
 */
function (indices, rejectedSlaves, interactionLink, postNote)
```

and the type of the `slaveTextGenerator` callback is:

```js
/**
 * @callback slaveTextGenerator
 * @param {App.Entity.SlaveState} slave
 * @param {number} index
 * @return {string}
 */
```

To build the output, the `render()` function iterates over the `indices` array, which must contain indices from the `$slaves` array, and outputs each slave using the two callbacks. The first one (`interactionLink`) is used to generate the hyperlink for the slave name. Output from the second callback (if defined) will be printed after each slave summary. The array of rejected slaves is processed after that and its contents are printed out in a simple fashion. The function *does not sort* both arrays, neither `indices` nor `rejectedSlaves`; their elements are printed in the order, specified by the caller. The function' output has to be passed via the SugarCube wikifier, for example by printing in out using the `<<print>>` macro.

Let's consider a few examples.

## Implementation examples

### Basic printing

The most common behavior for the slave name links is to go to the `Slave Interact` passage after clicking its name. This is implemented by the `App.UI.SlaveList.SlaveInteract.stdInteract` function:

```js
/**
 * @param {App.Entity.SlaveState} slave
 * @param {number} index
 * @return {string}
 */
App.UI.SlaveList.SlaveInteract.stdInteract = (slave, index) =>
   App.UI.passageLink(SlaveFullName(slave), 'Slave Interact', `$activeSlave = $slaves[${index}]`);
```

The function gets both the slave object and its index in the `$slaves` array for convenience, and outputs a hyperlink in the HTML markup. which points to the `Slave Interact` passage and sets active slave to the slave with the given index, because `Slave Interact` operates with the current slave. In the SugarCube markup the function could look as follows:

```js
/**
 * @param {App.Entity.SlaveState} slave
 * @param {number} index
 * @return {string}
 */
App.UI.SlaveList.SlaveInteract.stdInteract = (slave, index) =>
  `<<link "${SlaveFullName(slave)}" "Slave Interact">><<set $activeSlave = $slaves[${index}]`;
```
Now we can print slave lists. Let's print first three from the `$slaves` array:

```
<<print App.UI.SlaveList.render(
   [0, 1, 2], [].
   App.UI.SlaveList.SlaveInteract.stdInteract
)>>
```

Let's print them in the reverse order:

```
<<print App.UI.SlaveList.render(
   [2, 1, 0], [].
   App.UI.SlaveList.SlaveInteract.stdInteract
)>>
```

and one more time omitting the second slave:

```
<<print App.UI.SlaveList.render(
   [0, 2],
   [{index: 1, rejects: ['Omitted for testing purposes']}].
   App.UI.SlaveList.SlaveInteract.stdInteract
)>>
```

### Post-notes

The post notes are little text pieces, printed after each slave. They can contain simple text, for example to inform that particular slave is special in a way, or provide additional action hyperlinks. The most common type of the post notes is a hyperlink for assigning or retrieving s slave from a facility. Here are their implementations:

```js
(slave, index) => App.UI.passageLink(`Send ${slave.object} to ${facility.name}`, "Assign", `$i = ${index}`);

(slave, index) => App.UI.passageLink(`Retrieve ${slave.object} from ${facility.name}`, "Retrieve", `$i = ${index}`);
```

With these blocks we can easily produce a list of slaves, working at a given facility `facility`.

First, let's get the indices of the workers:

```js
let facilitySlaves = facility.job().employeesIndices();
```

The `App.Entity.Facilities.Facility.job()` call returns a named job if it was given an argument, or the default job when argument was omitted. Pretty much every facility except the penthouse provides only a single job and we can safely omit the job name here.

Now just call the `render()` function:

```js
App.UI.SlaveList.render(facilitySlaves, [],
	App.UI.SlaveList.SlaveInteract.stdInteract,
   (slave, index) => App.UI.passageLink(`Retrieve ${slave.object} from ${facility.name}`, "Retrieve", `$i = ${index}`));
```

That's it, works with any facility.

Here we omitted a few details, like setting the `$returnTo` bookmark for the `Assign` and `Retrieve` passages, sorting the slaves lists, and a few more.

### Templates for common use cases

There are functions in the `App.UI.SlaveList` namespace for a few typical use cases.

Function | Purpose
----- | ----
`listSJFacilitySlaves()` | Creates a tabbed list for assigning and retrieving slaves t0/from a facility with a single job
`displayManager()` | Displays a facility manager with a link to change it or a note with a link to assign a manager
`stdFacilityPage()` | Combines the previous two functions to render the manager and employees
`penthousePage()` | Displays tabs with workers for the penthouse
`slaveSelectionList()` | Displays a list with filtering and sorting links for selecting a slave
`facilityManagerSelection()` | Specialization of the `slaveSelectionList()` to select a manager for a facility, which implements position requirements tests and highlights experienced slaves

### Fully custom lists

There are use cases that stand apart from all other, and there is no point in providing a template for them. In that case you can generate a totally custom list. Here is an example:

```
<<print App.UI.SlaveList.slaveSelectionList(
	s => !ruleSlaveExcluded(s, $currentRule),
	(s, i) => `<u><strong>${App.UI.passageLink(SlaveFullName(s), 'Rules Slave Exclude Workaround', `$activeSlave = $slaves[${i}]`)}</strong></u>`
)>>
```


code:
# Standalone JS Functions

Collection of custom functions without dependencies on FC specific variables/data structures.

## utilJS.js

* `arraySwap(array, a , b)`:
    Swaps two values in an array.

* `capFirstChar(string)`:
    Capitalizes the first character of `string`.

* `addA(word)`:
    Adds an `an ` if the first character is a vocal, otherwise `a `.

* `ordinalSuffix(i)`:
    Takes a number and appends the appropriate suffix.
    Example: `ordinalSuffix(1)` gives `1st`.

* `ordinalSuffixWords(i)`:
    Takes a number and returns the appropriate ordinal.
    Example: `ordinalSuffix(1)` gives `first`.
    For number greater than 19 identical to `ordinalSuffix(i)`

* `removeDuplicates(array)`:
    Takes an array and returns a new array without duplicate entries.

* `jsDef(x)`:
    Returns whether x is undefined. A JS port of SugarCube's def.

* `between(a, low, high)`:
    Returns `true` if `a` is between `low` and `high`, otherwise `false`.


code:
# Obtaining and building SugarCube for FC

## Intro

This brief how-to guides through patching and building [SugarCube](https://github.com/tmedwards/sugarcube-2)
for the Free Cities. SugarCube sources can be obtained locally by cloning its Git repository.

Prerequisites (listed NOT in the installation order, please read the list to the end first):

1. [Node.js](https://nodejs.org) with npm.
2. To build some packages, Node.js requires python and a C/C++ compiler. On Windows you may want to
install Visual Studio (or just the build tools) and Python 2.7 first. As of now, SugarCube does not
depend on any of such packages.

Windows: please choose to make the tools accessible from anywhere by allowing installers to modify
the PATH environment variable.

## Retrieving SugarCube sources and preparing build environment for it

The SC2 sources are a git submodule.

1. Get the sources: `git submodule update`

2. Download required JavaScript libraries that are used during the build.
    Run the node package manager (npm) in the repository: `npm install` or `npm update`.
    * You need to navigate into `submodules/sugarcube-2` first.

## Building

Run the build.js script. If you use Git bash, you can run the `build.js` file directly, otherwise run
it as `node build.js`. See `build.js -h` for options, but reasonable sets are the following.

* for release version: `node build.js -6 -b 2`
* for debug version: `node build.js -6 -b 2 -u -d`

Find result files in the `build` directory. After each build you have  to copy the release `format.js` to
`devTools/tweeGo/storyFormats/sugarcube-2`. Do not forget to `git add` the file.

Linux: If you have make installed, `make sugarcube` automatically builds and copies `format.js` to
`devTools/tweeGo/storyFormats/sugarcube-2` for easy testing.

## Contributing

1. Fork the [SC2 repo](https://gitgud.io/Arkerthan/sugarcube-2)
2. Navigate into submodules/sugarcube-2
    * Tip: Use two terminals, one in the main repo and one in the submodule.
    * You are now in the root of the submodule. Git works here (almost) the same as in the root of a standalone repo.
3. Add your fork as remote
4. `git submodule update` leaves you in a detached head state, so checkout fc and pull recent changes
5. Branch, modify, commit, push as usual.
6. Build and replace `devTools/tweeGo/storyFormats/sugarcube-2/format.js`
7. Create a new patch `git diff master <your branch> > sugarcube-fc-changes.patch` and move it to
    `devNotes/sugarcube stuff/`.
8. Commit and push in the main repo.
9. Open an MR in the SC2 repo **AND** in the main repo. Mark the MR in the main repo as **WIP**.
10. Once the MR in the SC2 repo is accepted do:
    *  In the submodule
    1. `git checkout fc`
    2. `git pull`
    *  In the main repo
    1. `git add submodules/sugarcube-2`
    2. `git commit --amend --no-edit`
    3. `git push -f`
11. Remove WIP flag from main repo.

Repeat steps 4 - 11.

## (OLD) Retrieving SugarCube sources and preparing build environment for it

Note: This is **NOT** recommended. Everything below is for documentation purposes only.

1. Open a terminal window where you want to clone the repository, and run the following command: `git clone https://github.com/tmedwards/sugarcube-2.git`. Change working directory into the cloned repository.

2. The last step we need is downloading required JavaScript libraries that are used during the build.
Run the node package manager (npm) in the repository: `npm install` or `npm update`

*CAUTION*: dependencies list (located in the package.json file in the repository root) may change from
commit to commit and it differs between branches! Make sure to install correct dependencies after switching working branch.

## Applying the FC patch

The FreeCities game relies on a few changes to the vanilla SugarCube code. The modified SC2 can be found [here](https://gitgud.io/Arkerthan/sugarcube-2).

If unavailable, it is possible to patch the official SC2 sources using the patch file. The SugarCube code changes from version to version that requires modification to the patch file, and therefore there are several of them, named after the highest SugarCube version they applies to. There are at least two different ways to manage the patches/changes, with the first one suitable for occasional users, while the second one works best if you maintain the SugarCube patch for the FC project or just want to keep building for more than a single SugarCube version.

### Single time patching

Find the correct patch in this repo and apply it to the sources:
`git apply <full path to sugarcube-fc-changes.patch>`

You are ready to build provided no errors were returned by `git apply`

### Maintaining the patch

#### Crating git branch with patched SugarCube sources

For this purpose we will create a git branch with the changes and then update it, [rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) it on top of the SugarCube master branch when updating is needed.

To start over, create a branch for the patched version (here it will be named "fc") and checkout it: `git checkout -b fc`. Now apply FC changes as in the previous section using `git apply`, but afterwards commit the changes.

*Caveat*: to apply the patch you have to use the version of the SugarCube sources it applies to. If the latest patch does not apply to the current SugarCube master branch, check the `git log` to find the commit id of the SugarCube release you have the patch for. Then branch from that commit: `git branch fc <commit-id>` and checkout the "fc" branch: `git checkout fc`. To update the "fc" branch to the latest SugarCube see below.

#### Updating git branch

To update the branch we will use rebasing. First, fetch changes from the remote SugarCube repo: `git fetch origin`. Check that you are on the "fc" branch (`git checkout fc` if you are not). Now try to rebase your "fc" branch on top of the upstream master branch: `git rebase origin/master`. If that succeeds, the new patched SugarCube sources are ready in the "fc" branch. Proceed to building.

If the rebasing resulted in a conflict, you have to resolve it first. Issue `git rebase --continue` to get the list of files with conflicts, look for conflict markers ('<<<<') in those files, make required changes, do not forget to save the files, and follow advices from `git rebase --continue` until you finally resolve all the conflicts and obtain a new patched sources.

Now you need to update the patch files in the FC repo. First, rename the old patch file: `git mv sugarcube-fc-changes.patch sugarcube-fc-changes-<the last version it applies to>.patch`. Now create an new patch and add it to the repository: `git diff master fc > sugarcube-fc-changes.patch`, copy it to this dir and `git add sugarcube-fc-changes.patch`.

## APPENDIX Lists required steps very briefly

1. Clone SugarCube repo: `git clone https://github.com/tmedwards/sugarcube-2.git`
2. Change active directory into the directory of the sugarcube clone.
3. Set active branch to "master": git checkout master
4. Run npm install in the repo dir.

*CAUTION*: Requited dependencies change during the project lifetime and vary from branch to branch;
you may need to run npm install again after some time, or after branch/tag change. Try to run it in
case of strange build errors.

The next is done in the directory where SC repo was cloned. Loop over required SugarCube versions:

### Setup:

1. `git reset --hard` to clean any local changes.
2. `git branch fc v2.30.1` (the tag for the version you have the latest patch for).
3. `git checkout fc`

### Update loop

1. `git fetch origin`
2. `git checkout fc`
3. `git rebase origin/master`
4. Conflict resolving, `git rebase --continue` and `git add` are needed.
5. `git diff master fc > sugarcube-fc-changes.patch`; `git add sugarcube-fc-changes.patch`.

### Build

1. `node build.js -6 -b 2` to build release version
2. `cp build/twine2/sugarcube-2/format.js <whenever you want>`
3. `node build.js -6 -b 2 -u -d` to build debug version
4. `cp dist/twine2/sugarcube-2/format.js <whenever you want to place the debug header>`


code:
diff --git a/README.md b/README.md
index 94f98a2..3dcb81a 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,23 @@
+# SC2 Fork
+
+This is a fork of SC2 containing all changes made to improve the [FC pregmod (nsfw)](https://gitgud.io/pregmodfan/fc-pregmod).
+
+Important branches:
+* `fc`: SC2 with our changes, this is what is used to build FC
+* `master`: Current SC2 version
+
+Warning: When cloning you will be on `master` initially. You want to checkout `fc` first before doing anything else.
+
+Building:
+1. `node build.js -6 -b 2` to build release version
+2. replaces `/devTools/tweeGo/storyFormats/sugarcube-2/format.js` and `devNotes/sugarcube stuff/<current SC2 version>-format.js`
+3. `node build.js -6 -b 2 -u -d` to build debug version
+4. replaces `devNotes/sugarcube stuff/<current SC2 version>-debug-format.js`
+
+If you believe to have found a bug, be sure to check if it was introduced with our changes before reporting them at the SC2 repo.
+
+## SC2 README; most of it stays the same, but be sure to change the url when cloning:
+
 # SugarCube v2

 [SugarCube](http://www.motoslave.net/sugarcube/) is a free (gratis and libre) story format for [Twine/Twee](http://twinery.org/).
diff --git a/build.js b/build.js
index 955b617..acd80f5 100644
--- a/build.js
+++ b/build.js
@@ -30,6 +30,7 @@ const CONFIG = {
 			'src/lib/jquery-plugins.js',
 			'src/lib/util.js',
 			'src/lib/simplestore/simplestore.js',
+			'src/lib/simplestore/adapters/FCHost.Storage.js',
 			'src/lib/simplestore/adapters/webstorage.js',
 			'src/lib/simplestore/adapters/cookie.js',
 			'src/lib/debugview.js',
diff --git a/src/lib/helpers.js b/src/lib/helpers.js
index ff3e30f..2fc3a0d 100644
--- a/src/lib/helpers.js
+++ b/src/lib/helpers.js
@@ -308,11 +308,11 @@ var { // eslint-disable-line no-var
 	/*
 		Appends an error view to the passed DOM element.
 	*/
-	function throwError(place, message, source) {
+	function throwError(place, message, source, stack) {
 		const $wrapper = jQuery(document.createElement('div'));
 		const $toggle  = jQuery(document.createElement('button'));
 		const $source  = jQuery(document.createElement('pre'));
-		const mesg     = `${L10n.get('errorTitle')}: ${message || 'unknown error'}`;
+		const mesg     = `${L10n.get('errorTitle')}: ${message || 'unknown error'} ${Config.saves.version}`;

 		$toggle
 			.addClass('error-toggle')
@@ -346,6 +346,14 @@ var { // eslint-disable-line no-var
 				hidden        : 'hidden'
 			})
 			.appendTo($wrapper);
+		if (stack) {
+			const lines = stack.split('\n');
+			for (const ll of lines) {
+				const div = document.createElement('div');
+				div.append(ll.replace(/file:.*\//, '<path>/'));
+				$source.append(div);
+			}
+		}
 		$wrapper
 			.addClass('error-view')
 			.appendTo(place);
diff --git a/src/lib/jquery-plugins.js b/src/lib/jquery-plugins.js
index 792b01f..1811eaf 100644
--- a/src/lib/jquery-plugins.js
+++ b/src/lib/jquery-plugins.js
@@ -43,14 +43,9 @@
 		return function () {
 			const $this = jQuery(this);

-			// Exit if the element is disabled.
-			//
-			// NOTE: This should only be necessary for elements which are not disableable
-			// per the HTML specification as disableable elements should be made inert
-			// automatically.
-			if ($this.ariaIsDisabled()) {
-				return;
-			}
+			const dataPassage = $this.attr('data-passage');
+			const initialDataPassage = window && window.SugarCube && window.SugarCube.State && window.SugarCube.State.passage;
+			const savedYOffset = window.pageYOffset;

 			// Toggle "aria-pressed" status, if the attribute exists.
 			if ($this.is('[aria-pressed]')) {
@@ -59,6 +54,11 @@

 			// Call the true handler.
 			fn.apply(this, arguments);
+
+			const doJump = function(){ window.scrollTo(0, savedYOffset); }
+			if ( dataPassage && (window.lastDataPassageLink === dataPassage || initialDataPassage === dataPassage))
+				doJump();
+			window.lastDataPassageLink = dataPassage;
 		};
 	}

diff --git a/src/lib/simplestore/adapters/FCHost.Storage.js b/src/lib/simplestore/adapters/FCHost.Storage.js
new file mode 100644
index 0000000..34581b1
--- /dev/null
+++ b/src/lib/simplestore/adapters/FCHost.Storage.js
@@ -0,0 +1,171 @@
+/***********************************************************************************************************************
+
+	lib/simplestore/adapters/FCHost.Storage.js
+
+	Copyright Â© 2013â€“2019 Thomas Michael Edwards <thomasmedwards@gmail.com>. All rights reserved.
+	Use of this source code is governed by a BSD 2-clause "Simplified" License, which may be found in the LICENSE file.
+
+***********************************************************************************************************************/
+/* global SimpleStore, Util */
+
+SimpleStore.adapters.push((() => {
+	'use strict';
+
+	// Adapter readiness state.
+	let _ok = false;
+
+
+	/*******************************************************************************************************************
+		_FCHostStorageAdapter Class.
+        Note that FCHost is only intended for a single document, so we ignore both prefixing and storageID
+	*******************************************************************************************************************/
+	class _FCHostStorageAdapter {
+		constructor(persistent) {
+			let engine = null;
+			let name   = null;
+
+			if (persistent) {
+				engine = window.FCHostPersistent;
+				name   = 'FCHostPersistent';
+			}
+			else {
+			    engine = window.FCHostSession;
+				name   = 'FCHostSession';
+			}
+
+			Object.defineProperties(this, {
+				_engine : {
+					value : engine
+				},
+
+				name : {
+					value : name
+				},
+
+				persistent : {
+					value : !!persistent
+				}
+			});
+		}
+
+		/* legacy */
+		get length() {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.length : Number]`); }
+
+			return this._engine.size();
+		}
+		/* /legacy */
+
+		size() {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.size() : Number]`); }
+
+			return this._engine.size();
+		}
+
+		keys() {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.keys() : String Array]`); }
+
+			return this._engine.keys();
+		}
+
+		has(key) {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.has(key: "${key}") : Boolean]`); }
+
+			if (typeof key !== 'string' || !key) {
+				return false;
+			}
+
+			return this._engine.has(key);
+		}
+
+		get(key) {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.get(key: "${key}") : Any]`); }
+
+			if (typeof key !== 'string' || !key) {
+				return null;
+			}
+
+			const value = this._engine.get(key);
+
+			return value == null ? null : _FCHostStorageAdapter._deserialize(value); // lazy equality for null
+		}
+
+		set(key, value) {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.set(key: "${key}", value: \u2026) : Boolean]`); }
+
+			if (typeof key !== 'string' || !key) {
+				return false;
+			}
+
+			this._engine.set(key, _FCHostStorageAdapter._serialize(value));
+
+			return true;
+		}
+
+		delete(key) {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.delete(key: "${key}") : Boolean]`); }
+
+			if (typeof key !== 'string' || !key) {
+				return false;
+			}
+
+			this._engine.remove(key);
+
+			return true;
+		}
+
+		clear() {
+			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.clear() : Boolean]`); }
+
+			this._engine.clear();
+
+			return true;
+		}
+
+		static _serialize(obj) {
+			return JSON.stringify(obj);
+		}
+
+		static _deserialize(str) {
+			return JSON.parse(str);
+		}
+	}
+
+
+	/*******************************************************************************************************************
+		Adapter Utility Functions.
+	*******************************************************************************************************************/
+	function adapterInit() {
+		// FCHost feature test.
+		function hasFCHostStorage() {
+			try {
+			    if (typeof window.FCHostPersistent !== 'undefined')
+			        return true;
+			}
+			catch (ex) { /* no-op */ }
+
+			return false;
+		}
+
+		_ok = hasFCHostStorage();
+
+		return _ok;
+	}
+
+	function adapterCreate(storageId, persistent) {
+		if (!_ok) {
+			throw new Error('adapter not initialized');
+		}
+
+		return new _FCHostStorageAdapter(persistent);
+	}
+
+
+	/*******************************************************************************************************************
+		Module Exports.
+	*******************************************************************************************************************/
+	return Object.freeze(Object.defineProperties({}, {
+		init   : { value : adapterInit },
+		create : { value : adapterCreate }
+	}));
+})());
diff --git a/src/lib/simplestore/adapters/webstorage.js b/src/lib/simplestore/adapters/webstorage.js
index 75408a3..3993e1e 100644
--- a/src/lib/simplestore/adapters/webstorage.js
+++ b/src/lib/simplestore/adapters/webstorage.js
@@ -125,7 +125,7 @@ SimpleStore.adapters.push((() => {
 			return value == null ? null : _WebStorageAdapter._deserialize(value); // lazy equality for null
 		}

-		set(key, value) {
+		set(key, value, compression = true) {
 			if (DEBUG) { console.log(`[<SimpleStore:${this.name}>.set(key: "${key}", value: \u2026) : Boolean]`); }

 			if (typeof key !== 'string' || !key) {
@@ -133,7 +133,8 @@ SimpleStore.adapters.push((() => {
 			}

 			try {
-				this._engine.setItem(this._prefix + key, _WebStorageAdapter._serialize(value));
+				this._engine.setItem(this._prefix + key,
+					_WebStorageAdapter._serialize(value, this.persistent && compression));
 			}
 			catch (ex) {
 				/*
@@ -188,12 +189,15 @@ SimpleStore.adapters.push((() => {
 			return true;
 		}

-		static _serialize(obj) {
-			return LZString.compressToUTF16(JSON.stringify(obj));
+		static _serialize(obj, compression) {
+			if (compression) {
+				return LZString.compressToUTF16(JSON.stringify(obj));
+			}
+			return JSON.stringify(obj);
 		}

 		static _deserialize(str) {
-			return JSON.parse(LZString.decompressFromUTF16(str));
+			return JSON.parse((!str || str[0] == "{") ? str : LZString.decompressFromUTF16(str));
 		}
 	}

diff --git a/src/macros/macrocontext.js b/src/macros/macrocontext.js
index bd211cd..712e9e1 100644
--- a/src/macros/macrocontext.js
+++ b/src/macros/macrocontext.js
@@ -277,8 +277,8 @@ var MacroContext = (() => { // eslint-disable-line no-unused-vars, no-var
 			this._debugViewEnabled = false;
 		}

-		error(message, source) {
-			return throwError(this._output, `<<${this.displayName}>>: ${message}`, source ? source : this.source);
+		error(message, source, stack) {
+			return throwError(this._output, `<<${this.displayName}>>: ${message}`, source ? source : this.source, stack);
 		}
 	}

diff --git a/src/macros/macrolib.js b/src/macros/macrolib.js
index 7383bd0..af57d75 100644
--- a/src/macros/macrolib.js
+++ b/src/macros/macrolib.js
@@ -90,7 +90,7 @@
 				Scripting.evalJavaScript(this.args.full);
 			}
 			catch (ex) {
-				return this.error(`bad evaluation: ${typeof ex === 'object' ? ex.message : ex}`);
+				return this.error(`bad evaluation: ${typeof ex === 'object' ? `${ex.name}: ${ex.message}` : ex}`, null, ex.stack);
 			}

 			// Custom debug view setup.
@@ -351,7 +351,7 @@
 				}
 			}
 			catch (ex) {
-				return this.error(`bad evaluation: ${typeof ex === 'object' ? ex.message : ex}`);
+				return this.error(`bad evaluation: ${typeof ex === 'object' ? `${ex.name}: ${ex.message}` : ex}`, null, ex.stack);
 			}
 		}
 	});
@@ -807,7 +807,7 @@
 				}
 			}
 			catch (ex) {
-				return this.error(`bad conditional expression in <<${i === 0 ? 'if' : 'elseif'}>> clause${i > 0 ? ' (#' + i + ')' : ''}: ${typeof ex === 'object' ? ex.message : ex}`); // eslint-disable-line prefer-template
+				return this.error(`bad conditional expression in <<${i === 0 ? 'if' : 'elseif'}>> clause${i > 0 ? ' (#' + i + ')' : ''}: ${typeof ex === 'object' ? `${ex.name}: ${ex.message}` : ex}`, null, ex.stack); // eslint-disable-line prefer-template
 			}
 		}
 	});
diff --git a/src/markup/wikifier.js b/src/markup/wikifier.js
index bc8b018..df7285c 100644
--- a/src/markup/wikifier.js
+++ b/src/markup/wikifier.js
@@ -222,7 +222,7 @@ var Wikifier = (() => { // eslint-disable-line no-unused-vars, no-var
 		}

 		outputText(destination, startPos, endPos) {
-			jQuery(destination).append(document.createTextNode(this.source.substring(startPos, endPos)));
+			destination.appendChild(document.createTextNode(this.source.substring(startPos, endPos)));
 		}

 		/*
diff --git a/src/passage.js b/src/passage.js
index 73959ec..b299722 100644
--- a/src/passage.js
+++ b/src/passage.js
@@ -222,8 +222,10 @@ var Passage = (() => { // eslint-disable-line no-unused-vars, no-var
 			const frag = document.createDocumentFragment();
 			new Wikifier(frag, this.processText(), options);

-			// Update the excerpt cache to reflect the rendered text.
-			this._excerpt = Passage.getExcerptFromNode(frag);
+			// Update the excerpt cache to reflect the rendered text, if we need it for the passage description
+			if (Config.passages.descriptions == null) {
+				this._excerpt = Passage.getExcerptFromNode(frag);
+			}

 			return frag;
 		}
diff --git a/src/save.js b/src/save.js
index b6cca52..adbf7f1 100644
--- a/src/save.js
+++ b/src/save.js
@@ -38,12 +38,66 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return false;
 		}

-		let saves   = savesObjGet();
-		let updated = false;
+
+		// convert the single big saves object into the new smaller objects
+		const saves = storage.get('saves');
+		if (saves !== null) {
+			storage.delete('saves'); // Make sure we have enough space to store the saves in the new format
+			const container = Dialog.setup('Backup');
+			const message = document.createElement('span');
+			message.className = 'red';
+			message.append('Due to changes to the saves system your existing saves were converted. Backup all saves' +
+				' that are important to you as they may have been lost during conversion. Once you close this dialog' +
+				' it will be IMPOSSIBLE to get your old saves back.');
+			container.append(message);
+			const index = indexGet();
+			// we store the index every time we store a save, so in case we exceed local storage only the
+			// last save is lost
+			if (saves.autosave !== null) {
+				container.append(savesLink('autosave', saves.autosave));
+				index.autosave = { title : saves.autosave.title, date : saves.autosave.date };
+				try {
+					storage.set('autosave', saves.autosave);
+					indexSave(index);
+				} catch (ex) {
+					storage.delete('autosave');
+					index.autosave = null;
+					indexSave(index);
+				}
+			}
+			for (let i = 0; i < saves.slots.length; i++) {
+				if (saves.slots[i] !== null) {
+					container.append(savesLink(`slot${i}`, saves.slots[i]));
+					index.slots[i] = { title : saves.slots[i].title, date : saves.slots[i].date };
+					try {
+						storage.set(`slot${i}`, saves.slots[i]);
+						indexSave(index);
+					} catch (ex) {
+						storage.delete(`slot${i}`);
+						index.slots[i] = null;
+						indexSave(index);
+					}
+				}
+			}
+			Dialog.open();
+		}
+
+		function savesLink(name, save) {
+			const div = document.createElement('div');
+			const a = document.createElement('a');
+			a.append(`Backup ${name}`);
+			a.onclick = () => {
+				exportToDisk(`backup-${name}`, {}, save);
+			};
+			div.append(a);
+			return div;
+		}
+
+		/* All SC2 compatibility converters are disabled, too much work */

 		/* legacy */
 		// Convert an ancient saves array into a new saves object.
-		if (Array.isArray(saves)) {
+		/* if (Array.isArray(saves)) {
 			saves = {
 				autosave : null,
 				slots    : saves
@@ -53,6 +107,7 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		/* /legacy */

 		// Handle the author changing the number of save slots.
+		/*
 		if (Config.saves.slots !== saves.slots.length) {
 			if (Config.saves.slots < saves.slots.length) {
 				// Attempt to decrease the number of slots; this will only compact
@@ -77,10 +132,11 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var

 			updated = true;
 		}
+		 */

 		/* legacy */
 		// Update saves with old/obsolete properties.
-		if (_savesObjUpdate(saves.autosave)) {
+		/* if (_savesObjUpdate(saves.autosave)) {
 			updated = true;
 		}

@@ -98,29 +154,39 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		/* /legacy */

 		// If the saves object was updated, then update the store.
-		if (updated) {
+		/* if (updated) {
 			_savesObjSave(saves);
 		}
+		*/

-		_slotsUBound = saves.slots.length - 1;
+		_slotsUBound = indexGet().slots.length - 1;

 		return true;
 	}

-	function savesObjCreate() {
+	function indexCreate() {
 		return {
 			autosave : null,
 			slots    : _appendSlots([], Config.saves.slots)
 		};
 	}

-	function savesObjGet() {
-		const saves = storage.get('saves');
-		return saves === null ? savesObjCreate() : saves;
+	function indexGet() {
+		const index = storage.get('index');
+		return index === null ? indexCreate() : index;
+	}
+
+	function indexSave(index) {
+		return storage.set('index', index);
 	}

 	function savesObjClear() {
-		storage.delete('saves');
+		storage.delete('autosave');
+		const index = indexGet();
+		for (let i = 0; i < index.slots.length; i++) {
+			storage.delete(`slot${i}`);
+		}
+		storage.delete('index');
 		return true;
 	}

@@ -128,7 +194,6 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		return autosaveOk() || slotsOk();
 	}

-
 	/*******************************************************************************************************************
 		Autosave Functions.
 	*******************************************************************************************************************/
@@ -137,28 +202,21 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 	}

 	function autosaveHas() {
-		const saves = savesObjGet();
-
-		if (saves.autosave === null) {
-			return false;
-		}
-
-		return true;
+		return indexGet().autosave !== null;
 	}

 	function autosaveGet() {
-		const saves = savesObjGet();
-		return saves.autosave;
+		return storage.get('autosave');
 	}

 	function autosaveLoad() {
-		const saves = savesObjGet();
+		const autosave = autosaveGet();

-		if (saves.autosave === null) {
+		if (autosave === null) {
 			return false;
 		}

-		return _unmarshal(saves.autosave);
+		return _unmarshal(autosave);
 	}

 	function autosaveSave(title, metadata) {
@@ -166,25 +224,38 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return false;
 		}

-		const saves        = savesObjGet();
 		const supplemental = {
 			title : title || Story.get(State.passage).description(),
 			date  : Date.now()
 		};
+		const index = indexGet();
+		index.autosave = supplemental;
+		try {
+			indexSave(index);
+		} catch (ex) {
+			_storageAlert();
+			return false;
+		}

 		if (metadata != null) { // lazy equality for null
 			supplemental.metadata = metadata;
 		}

-		saves.autosave = _marshal(supplemental, { type : Type.Autosave });
-
-		return _savesObjSave(saves);
+		try {
+			return storage.set('autosave', _marshal(supplemental, { type : Type.Autosave }), false);
+		} catch (ex) {
+			index.autosave = null;
+			indexSave(index);
+			_storageAlert();
+			return false;
+		}
 	}

 	function autosaveDelete() {
-		const saves = savesObjGet();
-		saves.autosave = null;
-		return _savesObjSave(saves);
+		const index = indexGet();
+		index.autosave = null;
+		indexSave(index);
+		return storage.delete('autosave');
 	}

@@ -204,14 +275,15 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return 0;
 		}

-		const saves = savesObjGet();
+		const index = indexGet();
 		let count = 0;

-		for (let i = 0, iend = saves.slots.length; i < iend; ++i) {
-			if (saves.slots[i] !== null) {
-				++count;
+		for (let i = 0; i < index.slots.length; i++) {
+			if (index.slots[i] !== null) {
+				count++;
 			}
 		}
+
 		return count;
 	}

@@ -224,9 +296,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return false;
 		}

-		const saves = savesObjGet();
+		const index = indexGet();

-		if (slot >= saves.slots.length || saves.slots[slot] === null) {
+		if (slot >= index.slots.length || index.slots[slot] === null) {
 			return false;
 		}

@@ -234,31 +306,19 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 	}

 	function slotsGet(slot) {
-		if (slot < 0 || slot > _slotsUBound) {
-			return null;
-		}
-
-		const saves = savesObjGet();
-
-		if (slot >= saves.slots.length) {
-			return null;
+		if (slotsHas(slot)) {
+			return storage.get(`slot${slot}`);
 		}

-		return saves.slots[slot];
+		return null;
 	}

 	function slotsLoad(slot) {
-		if (slot < 0 || slot > _slotsUBound) {
-			return false;
-		}
-
-		const saves = savesObjGet();
-
-		if (slot >= saves.slots.length || saves.slots[slot] === null) {
-			return false;
+		if (slotsHas(slot)) {
+			return _unmarshal(storage.get(`slot${slot}`));
 		}

-		return _unmarshal(saves.slots[slot]);
+		return false;
 	}

 	function slotsSave(slot, title, metadata) {
@@ -277,24 +337,37 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return false;
 		}

-		const saves = savesObjGet();
+		const index = indexGet();

-		if (slot >= saves.slots.length) {
+		if (slot >= index.slots.length) {
 			return false;
 		}

 		const supplemental = {
-			title : title || Story.get(State.passage).description(),
-			date  : Date.now()
+			title: title || Story.get(State.passage).description(),
+			date: Date.now()
 		};

+		index.slots[slot] = supplemental;
+		try {
+			indexSave(index);
+		} catch (ex) {
+			_storageAlert();
+			return false;
+		}
+
 		if (metadata != null) { // lazy equality for null
 			supplemental.metadata = metadata;
 		}

-		saves.slots[slot] = _marshal(supplemental, { type : Type.Slot });
-
-		return _savesObjSave(saves);
+		try {
+			return storage.set(`slot${slot}`, _marshal(supplemental, { type : Type.Slot }));
+		} catch (ex) {
+			index.slots[slot] = null;
+			indexSave(index);
+			_storageAlert();
+			return false;
+		}
 	}

 	function slotsDelete(slot) {
@@ -302,21 +375,23 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			return false;
 		}

-		const saves = savesObjGet();
+		const index = indexGet();

-		if (slot >= saves.slots.length) {
+		if (slot >= index.slots.length) {
 			return false;
 		}

-		saves.slots[slot] = null;
-		return _savesObjSave(saves);
+		index.slots[slot] = null;
+		indexSave(index);
+
+		return storage.delete(`slot${slot}`);
 	}

 	/*******************************************************************************************************************
 		Disk Import/Export Functions.
 	*******************************************************************************************************************/
-	function exportToDisk(filename, metadata) {
+	function exportToDisk(filename, metadata, marshaledSave) {
 		if (typeof Config.saves.isAllowed === 'function' && !Config.saves.isAllowed()) {
 			if (Dialog.isOpen()) {
 				$(document).one(':dialogclosed', () => UI.alert(L10n.get('savesDisallowed')));
@@ -360,7 +435,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		const baseName     = filename == null ? Story.domId : legalizeName(filename); // lazy equality for null
 		const saveName     = `${baseName}-${datestamp()}.save`;
 		const supplemental = metadata == null ? {} : { metadata }; // lazy equality for null
-		const saveObj      = LZString.compressToBase64(JSON.stringify(_marshal(supplemental, { type : Type.Disk })));
+		const saveObj      = LZString.compressToBase64(JSON.stringify(
+			marshaledSave == null ? _marshal(supplemental, { type : Type.Disk }) : marshaledSave
+		));
 		saveAs(new Blob([saveObj], { type : 'text/plain;charset=UTF-8' }), saveName);
 	}

@@ -434,10 +511,17 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		return saveObj.metadata;
 	}

-
 	/*******************************************************************************************************************
 		Utility Functions.
 	*******************************************************************************************************************/
+	function _storageAlert() {
+		if (Dialog.isOpen()) {
+			$(document).one(':dialogclosed', () => UI.alert('Local storage full, delete saves or increase local storage'));
+		} else {
+			UI.alert('Local storage full, delete saves or increase local storage');
+		}
+	}
+
 	function _appendSlots(array, num) {
 		for (let i = 0; i < num; ++i) {
 			array.push(null);
@@ -446,31 +530,8 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		return array;
 	}

-	function _savesObjIsEmpty(saves) {
-		const slots = saves.slots;
-		let isSlotsEmpty = true;
-
-		for (let i = 0, iend = slots.length; i < iend; ++i) {
-			if (slots[i] !== null) {
-				isSlotsEmpty = false;
-				break;
-			}
-		}
-
-		return saves.autosave === null && isSlotsEmpty;
-	}
-
-	function _savesObjSave(saves) {
-		if (_savesObjIsEmpty(saves)) {
-			storage.delete('saves');
-			return true;
-		}
-
-		return storage.set('saves', saves);
-	}
-
 	function _savesObjUpdate(saveObj) {
-		if (saveObj == null || typeof saveObj !== 'object') { // lazy equality for null
+		if (saveObj == null || typeof saveObj !== "object") { // lazy equality for null
 			return false;
 		}

@@ -623,9 +684,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 			Save Functions.
 		*/
 		init  : { value : savesInit },
-		get   : { value : savesObjGet },
 		clear : { value : savesObjClear },
 		ok    : { value : savesOk },
+		index : { value : indexGet },

 		/*
 			Autosave Functions.
diff --git a/src/state.js b/src/state.js
index 184c92b..d445630 100644
--- a/src/state.js
+++ b/src/state.js
@@ -104,7 +104,7 @@ var State = (() => { // eslint-disable-line no-unused-vars, no-var
 		}

 		if (_expired.length > 0) {
-			stateObj.expired = [..._expired];
+			stateObj.expired = [];
 		}

 		if (_prng !== null) {
@@ -224,8 +224,8 @@ var State = (() => { // eslint-disable-line no-unused-vars, no-var
 	*/
 	function momentCreate(title, variables) {
 		return {
-			title     : title == null ? '' : String(title),       // lazy equality for null
-			variables : variables == null ? {} : clone(variables) // lazy equality for null
+			title     : title == null ? '' : String(title), // lazy equality for null
+			variables : variables == null ? {} : variables   // lazy equality for null
 		};
 	}

@@ -510,7 +510,7 @@ var State = (() => { // eslint-disable-line no-unused-vars, no-var
 			return [];
 		}

-		const delta = [clone(historyArr[0])];
+		const delta = [historyArr[0]];

 		for (let i = 1, iend = historyArr.length; i < iend; ++i) {
 			delta.push(Diff.diff(historyArr[i - 1], historyArr[i]));
diff --git a/src/ui.js b/src/ui.js
index 8402bb8..415e74c 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -254,7 +254,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
 				return $btn;
 			}

-			const saves  = Save.get();
+			const index  = Save.index();
 			const $tbody = jQuery(document.createElement('tbody'));

 			if (Save.autosave.ok()) {
@@ -272,7 +272,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
 					.text('A') // '\u25C6' Black Diamond
 					.appendTo($tdSlot);

-				if (saves.autosave) {
+				if (index.autosave) {
 					// Add the load button.
 					$tdLoad.append(
 						createButton('load', 'ui-close', L10n.get('savesLabelLoad'), 'auto', () => {
@@ -282,13 +282,13 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var

 					// Add the description (title and datestamp).
 					jQuery(document.createElement('div'))
-						.text(saves.autosave.title)
+						.text(index.autosave.title)
 						.appendTo($tdDesc);
 					jQuery(document.createElement('div'))
 						.addClass('datestamp')
 						.html(
-							saves.autosave.date
-								? `${new Date(saves.autosave.date).toLocaleString()}`
+							index.autosave.date
+								? `${new Date(index.autosave.date).toLocaleString()}`
 								: `<em>${L10n.get('savesUnknownDate')}</em>`
 						)
 						.appendTo($tdDesc);
@@ -324,7 +324,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
 					.appendTo($tbody);
 			}

-			for (let i = 0, iend = saves.slots.length; i < iend; ++i) {
+			for (let i = 0, iend = index.slots.length; i < iend; ++i) {
 				const $tdSlot = jQuery(document.createElement('td'));
 				const $tdLoad = jQuery(document.createElement('td'));
 				const $tdDesc = jQuery(document.createElement('td'));
@@ -333,9 +333,10 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
 				// Add the slot ID.
 				$tdSlot.append(document.createTextNode(i + 1));

-				if (saves.slots[i]) {
-					// Add the load button.
+				if (index.slots[i]) {
+					// Add the save & load buttons.
 					$tdLoad.append(
+						createButton('save', 'ui-close', L10n.get('savesLabelSave'), i, Save.slots.save),
 						createButton('load', 'ui-close', L10n.get('savesLabelLoad'), i, slot => {
 							jQuery(document).one(':dialogclosed', () => Save.slots.load(slot));
 						})
@@ -343,13 +344,13 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var

 					// Add the description (title and datestamp).
 					jQuery(document.createElement('div'))
-						.text(saves.slots[i].title)
+						.text(index.slots[i].title)
 						.appendTo($tdDesc);
 					jQuery(document.createElement('div'))
 						.addClass('datestamp')
 						.html(
-							saves.slots[i].date
-								? `${new Date(saves.slots[i].date).toLocaleString()}`
+							index.slots[i].date
+								? `${new Date(index.slots[i].date).toLocaleString()}`
 								: `<em>${L10n.get('savesUnknownDate')}</em>`
 						)
 						.appendTo($tdDesc);


code:
#!/bin/sh

# setup:
# add this to crontab:
# */15 * * * * cd ~/FC/fc-pregmod && git pull --ff-only origin pregmod-master > ~/FC/git_pull.log 2>&1
# and do "ln -s ~/FC/fc-pregmod/devTools/BuildAndIPFSify.sh .git/hooks/post-merge"
# NOTE: you may need to define XDG_RUNTIME_DIR in your crontab

# TODO: add logic to figure out if we should use ipfs-cluster instead of using the local instance directly.
#			if we use ipfs-cluster we probably don't need to warm ipfs.io's cache.

rm bin/*.html

# if this script is used as the post-merge hook then
# a) git pull has pulled something
# b) $PWD is fc-pregmod
if [ "$(basename "$0")" != "post-merge" ]; then
	# cd to fc-pregmod based on where this script is
	cd "$(readlink -f "$(dirname "$0")")/.." || exit 1
	git pull
	# if there are new html files then git probably pulled something and ran this script as a post-merge hook
	if ls bin/*.html; then
		exit 0
	fi
fi

# If we've done this before then unpin the previous hash so IPFS can GC it if it needs to
if [ -r ../IPFS_hash.txt ]; then
	ipfs pin rm --recursive=true "$(cut -d : -f 2 ../IPFS_hash.txt | tr -d ' ')"
fi

./compile || exit 1

# Keep the build time from changing the hash of the file
sed -Ei -e '/^  \* Built on .+$/d' bin/*.html

# add the date of the last commit to the file, but don't use colons because Windows (still?) doesn't like them
mv bin/*.html "bin/FC pregmod $(git log -1 --format='%cd' --date='format:%F %H-%M').html"

# include the unembedded vector art
ipfs_hash="$(ipfs add -w -Q -r bin/*.html resources)"
echo "IPFS Folder Hash: ${ipfs_hash}" > ../IPFS_hash.txt
ipfs name publish "$ipfs_hash"

# when it's done it will print something like "Published to $your_pubkey: /ipfs/$ipfs_hash"
# You can view the folder at http://127.0.0.1:8080/ipns/$your_pubkey

# make ipfs.io cache the files
# $XDG_RUNTIME_DIR SHOULD be defined, but there are cases where it wouldn't be
if [ -z "${XDG_RUNTIME_DIR+x}" ]; then
	echo "\$XDG_RUNTIME_DIR is unset, bailing"
	exit 2
fi

# throw it into a file so we can loop over lines, not "strings delimited by whitespace"
find resources bin -print | grep -ve '.gitignore' | sed -e 's|bin/||' | grep -Ee '.+\.svg' -e '.html' > "${XDG_RUNTIME_DIR}/files.list"

# ipfs PeerID, it's user specific
PeerID="$(ipfs config show | grep -e 'PeerID' | cut -d: -f 2 | tr -d ' "')"
while IFS= read -r item
do
	echo "https://ipfs.io/ipns/${PeerID}/${item}"
done < "${XDG_RUNTIME_DIR}/files.list" | xargs --max-procs=10 --max-args=1 --replace curl --silent --show-error --range 0-499 --output /dev/null '{}'

rm "${XDG_RUNTIME_DIR}/files.list"


code:
#!/bin/bash
echo "Use temp [0], dump file to current directory [1] or overwrite file in place [2]" && read varname1
echo "Use upstream [1] or origin [0]" && read varname2
if [[ $varname2 == 0 ]];then
	echo "Use master [1] or current branch [0]." && read varname3
else
	varname3=1;
fi
echo "Dry run? 1:0" && read varname4

if [[ $varname2 == 1 ]];then
	varname2='pregmodfan'
else
	varname2=$(git remote show origin|grep -e /|head -n 1|sed s#git@ssh.gitgud.io:#https://gitgud.io/#|cut -c 32-|sed s#/fc-pregmod.git##)
fi
if [[ $varname3 == 1 ]];then
	varname3='pregmod-master'
else
	varname3=$(git rev-parse --abbrev-ref HEAD)
fi

if [[ $varname4 == 1 ]];then
 echo "https://gitgud.io/$varname2/fc-pregmod/raw/$varname3/"
else
	for ((i=1; i<=$#; i++))
	do
		if [[ $varname1 == 0 ]]; then
			wget -q -P /tmp/ https://gitgud.io/$varname2/fc-pregmod/raw/$varname3/${!i}
		elif [[ $varname1 == 1 ]]; then
			wget -q https://gitgud.io/$varname2/fc-pregmod/raw/$varname3/${!i}
		else
			curl -s https://gitgud.io/$varname2/fc-pregmod/raw/$varname3/${!i} > ${!i}
		fi
	done
fi


code:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

	<UsingTask TaskName="ConcatFiles" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
		<ParameterGroup>
			<Inputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
			<BaseDir ParameterType="System.String" Required="true" />
			<Output ParameterType="System.String" Required="true" />
		</ParameterGroup>
		<Task>
			<Using Namespace="System"/>
			<Using Namespace="System.IO"/>
			<![CDATA[
				using (StreamWriter output = new StreamWriter(Output)) {
					int bdl = BaseDir.Length;
					foreach (ITaskItem item in Inputs) {
						string inp = item.GetMetadata("FullPath");
						string rp = inp.StartsWith(BaseDir) ? inp.Substring(bdl) : inp; // a simple relative path
						output.WriteLine("/* Source: {0} */", rp);
						output.Write(File.ReadAllText(inp));
						output.WriteLine();
					}
				}
			]]>
		</Task>
	</UsingTask>

	<UsingTask TaskName="StripComments" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
	<ParameterGroup>
		<InputFilename ParameterType="System.String" Required="true" />
		<OutputFilename ParameterType="System.String" Required="true" />
	</ParameterGroup>
	<Task>
		<Using Namespace="System" />
		<Using Namespace="System.IO" />
		<Using Namespace="System.Text.RegularExpressions" />

		<![CDATA[
				var blockComments = @"/\*(.*?)\*/";
				var lineComments = @"//(.*?)\r?\n";
				var strings = @"""((\\[^\n]|[^""\n])*)""";
				var verbatimStrings = @"@(""[^""]*"")+";
				File.WriteAllText(
					OutputFilename,
					Regex.Replace(File.ReadAllText(InputFilename),
						blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings,
						me => {
							if (me.Value.StartsWith("/*") || me.Value.StartsWith("//"))
								return me.Value.StartsWith("//") ? "\n" : "";
							// Keep the literal strings
							return me.Value;
						},
						RegexOptions.Singleline
					)
				);
			]]>

	</Task>
	</UsingTask>

	<UsingTask TaskName="GetToolPath" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
		<ParameterGroup>
			<ToolName Required="true" />
			<LocalPath />
			<ToolPath Output="true" />
			<ToolFound ParameterType="System.Boolean" Output="true" />
		</ParameterGroup>
		<Task>
			<Reference Include="Microsoft.Win32.Registry" />

		</Task>
	</UsingTask>

	<Target Name="FindGitWindows" Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">
		<GetToolPath ToolName="git">
			<Output TaskParameter="ToolPath" PropertyName="GitExe"/>
			<Output TaskParameter="ToolFound" PropertyName="GitExeFound"/>
		</GetToolPath>
	</Target>

	<Target Name="FindGitUnix" Condition=" '$([MSBuild]::IsOSUnixLike())' " >
		<PropertyGroup>
			<GitExe>git</GitExe>
		</PropertyGroup>
		<Exec Command="$(GitExe) help > /dev/null" IgnoreExitCode="True">
			<Output TaskParameter="ExitCode" PropertyName="GitExeExitCode" />
		</Exec>
		<PropertyGroup>
			<GitExeFound Condition=" '$(GitExeExitCode)' == '0' " >true</GitExeFound>
			<GitExeFound Condition=" '$(GitExeExitCode)' != '0' " >false</GitExeFound>
		</PropertyGroup>
	</Target>

	<Target Name="FindGit" DependsOnTargets="FindGitWindows;FindGitUnix" />
</Project>


code:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Win32;

/*
 Copyright (c) 2014, LoreSoft
 This class was taken from https://github.com/loresoft/msbuildtasks/ and is licensed under BSD-2 license
*/
internal static class ToolPathUtil
{
	public static bool SafeFileExists(string path, string toolName)
	{
		return SafeFileExists(Path.Combine(path, toolName));
	}

	public static bool SafeFileExists(string file)
	{
		Console.WriteLine(String.Format("Testing {0}", file));
		try { return File.Exists(file); }
		catch { } // eat exception

		return false;
	}

	public static string MakeToolName(string name)
	{
		return (Environment.OSVersion.Platform == PlatformID.Unix) ?
			name : name + ".exe";
	}

	public static string FindInRegistry(string toolName)
	{
		try
		{
			RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + toolName, false);
			if (key != null)
			{
				string possiblePath = key.GetValue(null) as string;
				if (SafeFileExists(possiblePath))
					return Path.GetDirectoryName(possiblePath);
			}
		}
		catch (System.Security.SecurityException) { }

		return null;
	}

	public static string FindInPath(string toolName)
	{
		string pathEnvironmentVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
		string[] paths = pathEnvironmentVariable.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
		foreach (string path in paths)
		{
			if (SafeFileExists(path, toolName))
			{
				return path;
			}
		}

		return null;
	}

	public static string FindInProgramFiles(string toolName, params string[] commonLocations)
	{
		foreach (string location in commonLocations)
		{
			string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), location);
			if (SafeFileExists(path, toolName))
			{
				return path;
			}
		}

		return null;
	}

	public static string FindInLocalPath(string toolName, string localPath)
	{
		if (localPath == null)
			return null;

		string path = new DirectoryInfo(localPath).FullName;
		if (SafeFileExists(localPath, toolName))
		{
			return path;
		}

		return null;
	}
}

public class GetToolPath : Microsoft.Build.Utilities.Task
{
	public virtual string ToolName { get; set; }
	public virtual string LocalPath { get; set; }
	public virtual string ToolPath { get; set; }
	public virtual bool Success { get; set; }

	public override bool Execute()
	{
		this.ToolPath = null;
		this.Success = false;
		string tool = ToolPathUtil.MakeToolName(ToolName);

		string toolDir =
					ToolPathUtil.FindInPath(tool) ??
					ToolPathUtil.FindInRegistry(tool) ??
					ToolPathUtil.FindInProgramFiles(tool, tool) ??
					ToolPathUtil.FindInProgramFiles(tool, Path.Combine(tool, "bin")) ??
					ToolPathUtil.FindInLocalPath(tool, LocalPath);

		if (toolDir == null)
		{
			throw new Exception(String.Format("Could not find {0}. Looked in PATH locations and various common folders inside Program Files as well as LocalPath.", tool));
		}

		Console.WriteLine("Found {0} at {1}", tool, toolDir);
		ToolPath = Path.Combine(toolDir, tool);
		Success = true;
		return Success;
	}
}


code:
#!/bin/ksh
mega-login $1 $2 #U=$1 P=$2 rDir=$3 lDir=$4 Reqs:MEGAcmd,git
while true;do gen=0;cd $4;
	if [[ `echo $?` -gt 0 ]];then mkdir -p $4&&git clone -q --depth 1 https://gitgud.io/pregmodfan/fc-pregmod.git $4&&cd $4
		if [[ ! `mega-ls $3|cut -c21-24|paste -sd,` =~ `git log|head -1|cut -c8-11` ]];then gen=1;fi #stackoverflow.com/a/15394738
	fi
	git fetch -q && if [ `git rev-list ...origin|wc -l` -gt 0 ];then git pull -q&&gen=1;fi #stackoverflow.com/a/17192101
	if [[ $gen > 0 ]];then rm bin/*;./compile.sh -q&&cd bin/
		mv *.h* FC-`git log -1 --format=%cd --date=format:%m-%d-%Y-%H-%M`-`git log|head -1|cut -c8-11`.html
		mega-put -c *.h* $3&&mega-rm $3`mega-ls $3|head -n 1`
	fi
clear;sleep 15m;done


code:
# Script to convert SC passages to JS.
# lowercasedonkey, here.  This script was written for the "PythonScript" plugin for notepad++.  https://github.com/bruderstein/PythonScript.  Not a python guy, but if works somewhere else, great!
# At the moment, you can't "rerun it" on JS.  Only run the code on SC, not the output.  It won't update the JS.

# Update slave references
editor.rereplace(r"\$activeSlave", r"slave")
editor.rereplace(r"\$slaves\[\$i\]", r"slave")

# Update all references
editor.rereplace(r"\$", r"V\.")

# Line breaks for embedded code, beautify later
editor.rereplace(r"([a-zA-Z@].*?)<<", r"$1\n<<")
editor.rereplace(r">>([a-zA-Z,\.@ ].*?)", r">>\n\t$1")

# Clean up print
editor.rereplace(r"<<print (.*)>>", r"V.\1")
editor.rereplace(r"<<= (.*?)>>", r"V\.\1")

# Raw text
editor.rereplace(r"	([a-zA-Z@\.,`'; ].*)", r"	r.push\(`\1`\);")

# If / else
editor.rereplace(r"<<if (.*?)>>", r"if \1 {")
editor.rereplace(r"<<elseif (.*?)>>", r"} else if \(\1\) {")
editor.rereplace(r"<<else>>", r"} else {")
editor.rereplace(r"<</if>>", r"}")

#fix paren
editor.rereplace(r"([	\n])if ([^\(].*) {", r"\1if \(\2\) {")
editor.rereplace(r"([	\n])else if ([^\(].*) {", r"\1 else if \(\2\) {")

# run can happen directly
editor.rereplace(r"<<run (.*?)>>", r"\1;")

# Clean up unset
editor.rereplace(r"<<unset (.*)>>", r"delete \1;")

# Set
editor.rereplace(r"<<set (.*?)>>", r"$1;")

#Switch
editor.rereplace(r"<<switch (.*?)>>", r"switch \(\1\) {")
editor.rereplace(r"<</switch>>", r"}")
editor.rereplace(r"<<case (.*?)>>", r"case \1:")
editor.rereplace(r"<<default>>", r"default:")

# SC markup
editor.rereplace(r"@@\.(.*?);(.*?)@@", r"\2")
editor.rereplace(r"@@\.(.*?);", r"")
editor.rereplace(r"@@", r"")
#remove incompatible comment style
editor.rereplace(r"/%", r"/*")
editor.rereplace(r"%/", r"*/")

# JS
editor.rereplace(r" == ", r" === ")
editor.rereplace(r" != ", r" !== ")

#fix pronouns
editor.rereplace(r"V\.Hers", r"\${Hers}")
editor.rereplace(r"V\.hers", r"\${hers}")
editor.rereplace(r"V\.He", r"\${He}")
editor.rereplace(r"V\.he", r"\${he}")
editor.rereplace(r"V\.Himself", r"\${Himself}")
editor.rereplace(r"V\.himself", r"\${himself}")
editor.rereplace(r"V\.Him", r"\${Him}")
editor.rereplace(r"V\.him", r"\${him}")
editor.rereplace(r"V\.His", r"\${His}")
editor.rereplace(r"V\.his", r"\${his}")
editor.rereplace(r"V\.Girl", r"\${Girl}")
editor.rereplace(r"V\.girl", r"\${girl}")
editor.rereplace(r"V\.Loli", r"\${Loli}")
editor.rereplace(r"V\.loli", r"\${loli}")
editor.rereplace(r"V\.Woman", r"\${Woman}")
editor.rereplace(r"V\.woman", r"\${woman}")

#fix pronouns
editor.rereplace(r"_He2", r"\${_He2}")
editor.rereplace(r"_he2", r"\${_he2}")
editor.rereplace(r"_Him2", r"\${_Him2}")
editor.rereplace(r"_him2", r"\${_him2}")
editor.rereplace(r"_His2", r"\${_His2}")
editor.rereplace(r"_his2", r"\${_his2}")
editor.rereplace(r"_Hers2", r"\${_Hers2}")
editor.rereplace(r"_hers2", r"\${_hers2}")
editor.rereplace(r"_Himself2", r"\${_Himself2}")
editor.rereplace(r"_himself2", r"\${_himself2}")
editor.rereplace(r"_Girl2", r"\${_Girl2}")
editor.rereplace(r"_girl2", r"\${_girl2}")
editor.rereplace(r"_Loli2", r"\${_Loli2}")
editor.rereplace(r"_loli2", r"\${_loli2}")
editor.rereplace(r"_Woman2", r"\${_Woman2}")
editor.rereplace(r"_woman2", r"\${_woman2}")
editor.rereplace(r"_Hers2", r"\${_Hers2}")
editor.rereplace(r"_hers2", r"\${_hers2}")

#look for variables embedded in literals
editor.rereplace(r"(	r.push[^;]*?)(V\.[^ ,<'`]*)", r"\1${\2}")
editor.rereplace(r"(	r.push[^;]*?)(slave\.[^ ,<'`]*)", r"\1${\2}")
editor.rereplace(r"(	r.push[^;]*?)(_S\.[^ ,<'`]*)", r"\1${\2}")
editor.rereplace(r"(	r.push[^;]*?)( _[^ ,<'`]*)", r"\1${ \2}")

#Delay fixing _S to simplify detection if it's embedded.
editor.rereplace(r"_S\.", r"S.")

#Cleanup
editor.rereplace(r"\${He}adGirl", r"V.HeadGirl")
editor.rereplace(r"\.}", r"}.")
editor.rereplace(r"\${slave}", r"slave")

path: devTools/Script to convert SC to JS/SC to JS 1.py


code:
#!/bin/sh

tmpJoinedFile=`mktemp --suffix=.js`

files=$(find js src -name '*.js' -print)
files=$(echo "$files" | sort)
for f in $files; do
	printf "\n/* ${f} */\n" >> "$tmpJoinedFile"
	cat "$f" >> "$tmpJoinedFile"
done

node -c "$tmpJoinedFile"

test $? -eq 0 && rm "$tmpJoinedFile"


code:
@echo off
:: Concatenates files from dir %1 specified with wildcard %2 and outputs result to %3

:: temporary codepage change to avoid mojibake
FOR /f "tokens=2 delims=:." %%x IN ('CHCP') DO SET _CP=%%x
CHCP 65001 > NUL

:: TODO Proper temp file instead of bin\list.txt
IF EXIST %3 DEL %3
SET _LISTFILE=bin\list.txt

:: Collect sorted list of files
ECHO "Ignore the first line to skip the BOM" >%_LISTFILE%
(FOR /R "%~1" %%F IN (%2) DO echo "%%F") | sort >>%_LISTFILE%

:: If we have powershell available, strip the absolute path information
:: If not, fail silently (which is fine, but will leak path information into the built file)
powershell -command "" 2> NUL
IF %ERRORLEVEL% EQU 0 powershell -command "(get-content -literalPath \"%CD%\%_LISTFILE%\" -encoding utf8) -replace [regex]::escape(\"%CD%\"),'' -replace '\"','' ^| set-content -encoding utf8 -literalPath \"%CD%\%_LISTFILE%\""

:: Append the files
(FOR /F "skip=1 usebackq delims=" %%F IN (`type "%_LISTFILE%"`) DO (
	echo /* %%F */ >> %3
	copy /b %3+"%CD%%%F" %3 1>NUL
	)
)

DEL %_LISTFILE%
CHCP %_CP% > NUL


code:
#!/bin/sh

# $1: source dir
# $2: name wildcard
# #3: destination file

rm -f "$3"
files=$(find "$1" -name "$2" -print)
files=$(echo "$files" | sort)
for f in $files; do
	printf "\n/* ${f#$1} */\n" >> "$3"
	cat "$f" >> "$3"
done


code:
#!/bin/sh

#converts files in the current dir

# collect files with [script] tag
FILES=`grep '\[script\]' -H -R * | cut -f 1 -d ':'`
for f in $FILES; do
	# create new name. Old name can have any of the following suffixes:
	# _JS.tw  JS.tw .tw
	nf="${f%_JS.tw}"
	nf="${nf%JS.tw}"
	nf="${nf%.tw}"
	# we might end up with an empty name, then set dir name as the file name
	[ -z "$(basename $nf)" -o "${nf: -1}" = "/" ] && nf="${nf}$(basename ${nf})"
	# append the new suffix
	nf="${nf}.js"
	echo "renaming ${f} -> ${nf}"
	# rename the file
	git mv "${f}" "${nf}" || echo "renaming ${f} failed"
	# strip Twee header from it
	sed -i 1d "${nf}" || echo "stripping Twee header failed for ${nf}"
	# strip leading empty line '/./,$!d'
	sed -i '/./,$!d' "${nf}" || echo "Stripping leading empty blank lines failed for ${nf}"
done


code:
# Define phrases that should be searched in src/ on every call of the java based
# sanityCheck here.
# Phrases will be found whenever that exact phrase appears, regardless of
# surroundings or overlapping.
# Format is: searchPhrase#errorMessage
# Output is: Found searchPhrase; Correct: errorMessage
#cash +=#Should be cashX() ## cash used by corporate code,
#cash -=#Should be cashX() ## too many false positives
#rep +=#Should be repX() ## unused because .rep is used by facility code
#rep -=#Should be repX() ## -> many false positives
cash+=#Should be cashX()
cash-=#Should be cashX()
rep+=#Should be repX()
rep-=#Should be repX()
attepmts#attempts
youreslf#yourself
advnaces#advances
canAcheive#canAchieve
setBellySize#SetBellySize
SetbellySize#SetBellySize
setbellySize#SetBellySize
bellypreg#bellyPreg
bellyimplant#bellyImplant
bellyfluid#bellyFluid
carress#caress
hormonebalance#hormoneBalance
fetishknown#fetishKnown
recieve#receive
recieves#receives
$slave[#Should be $slaves[
can not#cannot
Coca Cola#Coca-Cola
dosen't#doesn't
aircrafts'#aircraft's
outift#outfit
earWear#earwear
eyeWear#eyewear
curiousity#curiosity
intensly#intensely
quicly#quickly
avalibe#available
randomise#randomize
protudes#protrudes
disapearing#disappearing