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"`.


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 is a java program that checks for correct closure of all SugarCube macros and HTML tags and has some additional functionality.

## 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.

### 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 be found at [gitgud.io](https://gitgud.io/Arkerthan/twine-sanitycheck).

repo_name: Arkerthan/fc

license: mit


code:
# Writing scenes

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

```sugarcube
<<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 `()` - 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 miscellaneous 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:

```sugarcube
<<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 tips:

- Write your logic first, so you don't forget to close tags. In other words, it's better to do

```sugarcube
<<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.

- 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

```sugarcube
<<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.

- 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:

```sugarcube
<<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:

```sugarcube
<<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>>
```

- 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:
# 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: If it's the first time: `git submodule update --init --recursive`, afterwards `git submodule update` is enough.

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 -n -b 2`
* for debug version: `node build.js -n -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 1203114..645f68b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,39 @@
+# 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.
+
+## Differences to official SC2
+
+* Disable save compression to speed up saving/loading [cafc36bc](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/cafc36bcd41f41da0e16f28ddbd6d5780ea0eeb1)
+* Remember scroll position if passage stays the same [7acec77a](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/7acec77acab7ddcc0db7f1c6447486548b282628)
+* Ignore expired array [2ebd17ab](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/2ebd17ab69bf280c7005e16a4f3e63e8f83fd3e6)
+* Add option to overwrite saves [fcdacefa](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/fcdacefa772e6906b8baa9062929103558771251)
+* Use native JS instead of jQuery to speed up week end [8f030d88](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/8f030d884f9adf20b602f07c69fda74b645d87a8)
+* Add FCHost storage support [87089b02](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/87089b02779cef704d62591398ea3b2d1dbc9fce)
+* Don't generate passage excerpts if they aren't going to be used [d1da3d64](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/d1da3d643af1bd5460d9cad21b2dde168adabfc1)
+* Add stacktrace to set, print, if macros [e527368c](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/e527368c9e9dae423a9f3410f35f63ba40580b92)
+* Speedup passage change by removing unnecessary clone calls [0d953ca0](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/0d953ca037ec8b303c52957164fda634a39af2a9)
+* Split saves object into smaller objects to improve access speed [524fbff4](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/524fbff49a9eef3155f30ccedbca17cb033e3166), [a2cc3311](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/a2cc33118ce38deadfe97ca9178ee2f48cc84f49), [4d96c8df](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/4d96c8dfbe307578f5f6f64fc86f3e0e4ea233a1)
+* Re enable compression for non autosaves [0a1704ec](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/0a1704ec5945e8a0b541c6719016406189bbd97b)
+* Show version information in error messages [4f76dbc4](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/4f76dbc4370c04d6ea42ec120f0ebd5bb499267a)
+* Use render() instead of a local wikifier, making it possible to render DOM directly for certain special passages (storyCaption, ...) [e8af6b2f](https://gitgud.io/Arkerthan/sugarcube-2/-/commit/e8af6b2f60fa88f5111d1812593e0b0e47b2779f)
+
+## 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 cfe166c..78614d5 100644
--- a/build.js
+++ b/build.js
@@ -31,6 +31,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 25682ee..a1f5600 100644
--- a/src/lib/helpers.js
+++ b/src/lib/helpers.js
@@ -289,7 +289,7 @@ var { // eslint-disable-line no-var

 		for (let i = 0, iend = ids.length; i < iend; ++i) {
 			if (Story.has(ids[i])) {
-				new Wikifier(el, Story.get(ids[i]).processText().trim());
+				el.append(Story.get(ids[i]).render());
 				return el;
 			}
 		}
@@ -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 914d9a5..a82d423 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 27d3801..4c689bc 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 df25bb4..9272ede 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 3d2ea94..8f4dd7a 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);
 			}
 		}
 	});
@@ -815,7 +815,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 2f3b07d..eaeac6b 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 4835633..6e2c6e1 100644
--- a/src/passage.js
+++ b/src/passage.js
@@ -212,8 +212,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 29714a3..36b4252 100644
--- a/src/save.js
+++ b/src/save.js
@@ -44,12 +44,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
@@ -59,6 +113,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
@@ -83,10 +138,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;
 		}

@@ -104,29 +160,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;
 	}

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

-
 	/*******************************************************************************************************************
 		Autosave Functions.
 	*******************************************************************************************************************/
@@ -143,28 +208,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) {
@@ -172,25 +230,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');
 	}

@@ -210,14 +281,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;
 	}

@@ -230,9 +302,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;
 		}

@@ -240,31 +312,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) {
@@ -283,24 +343,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) {
@@ -308,21 +381,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')));
@@ -359,7 +434,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		const baseName     = filename == null ? Story.domId : getFilename(filename); // lazy equality for null
 		const saveName     = `${baseName}-${getDatestamp()}.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);
 	}

@@ -433,7 +510,6 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 		return saveObj.metadata;
 	}

-
 	/*******************************************************************************************************************
 		Event Functions.
 	*******************************************************************************************************************/
@@ -485,6 +561,14 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
 	/*******************************************************************************************************************
 		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);
@@ -493,31 +577,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;
 		}

@@ -666,9 +727,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 e21149c..ecef60f 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
 		};
 	}

diff --git a/src/ui.js b/src/ui.js
index bc62bf5..ca312f2 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:
# Useful Functions

## Assay Functions

```js
isSlim(slave) // Returns if slave is considered slim or not by arcology standards.

isStacked(slave) // Returns if slave is considered stacked (big T&A) or not.

isModded(slave) // Tallies a slave's tats and piercings and returns if slave is considered heavily modded or not.

isUnmodded(slave) // Returns if slave is (relatively) unmodded. Some leeway.

isXY(slave) // Returns if a slave has a dick. (This needs review, it's far outdated for what it is used for.)

isPreg(slave) // Returns if a slave looks pregnant.

isNotPreg(slave) // Returns if slave has no sizable belly.

isPure(slave) // Returns if slave has not been surgically enhanced (to a noticeable degree).

isSurgicallyImproved(slave) // Returns if slave has been surgically enhanced with boob, butt and lip implants and also has a small waist.

PiercingScore(slave) // Returns int representing degree of piercings. Higher means more piercings.

TatScore(slave) // Returns int representing degree of tattooing. Higher means more tattoos.

canImproveIntelligence(slave) // Returns if slave intelligence can be improved with Psychostimulants

canImproveHeight(slave) // Returns if slave height can be improved with growth stimulants

sameAssignmentP(slave, slave) // Returns if slaves are on the same assignment.

haveRelationshipP(slave1, slave2) // Returns if slave1 is in a relationship with slave2.

isRivalP(slave1, slave2) // Returns if slave1 is in a rivalry with slave2.

supremeRaceP(slave) // Returns if slave is of the superior race. (Should only be used in conjunction with racial supremacy.)

inferiorRaceP(slave) // Returns if slave is of the inferior race. (Should only be used in conjunction with racial subjugation.)

isLeaderP(slave) // Returns if slave is in a leadership assignment.

isMotherP(slave1, slave2) // Returns if slave2 is slave1's mother.

isFatherP(slave1, slave2) // Returns if slave2 is slave1's father.

isParentP(slave1, slave2) // Returns if slave2 is either of slave1's parents.

sameDad(slave1, slave2) // Returns if slave1 and slave2 have the same father.

sameMom(slave1, slave2) // Returns if slave1 and slave2 have the same mother.

areTwins(slave1, slave2) // Returns if slave1 and slave2 are twins.

areSisters(slave1, slave2) // Returns sister status of slave1 and slave2 (1 // twins, 2 // sisters, 3 // half-sisters)

areRelated(slave1, slave2) // Returns if slave1 and slave2 are related.

totalRelatives(slave) // Returns the number of relatives slave has.

mutualChildren(slave1, slave2) // Returns if slave1 and slave2 have children together.

isSlaveAvailable(slave) // Returns if slave is available and not confined someplace.

assignmentVisible(slave) // Returns whether a slave's current assignment is shown in Main. Often used as a proxy for "penthouse slave".

randomRelatedSlave(slave) // Returns a random relative of slave if possible.

randomRelatedAvailableSlave(slave) // Returns a random available relative of slave if possible.

randomSister(slave) // Returns a random sister of slave if possible.

randomTwinSister(slave) // Returns a random available twin of slave if possible.

randomAvailableSister(slave) // Returns a random available sister of slave if possible.

randomDaughter(slave) // Returns a random child of slave if possible.

randomDaughter(slave) // Returns a random available child of slave if possible.

randomParent(slave) // Returns a random parent of slave if possible.

randomAvailableParent(slave) // Returns a random available parent of slave if possible.

totalPlayerRelatives(PC) // Returns the number of relatives the player has.

isSexuallyPure(slave) // Returns if the slave has (possibly) never had sex.

canGetPregnant(slave) // If the slave is fucked right now, could she get pregnant?

canBreed(slave1, slave2) // Returns if slave1 and slave2 are capable of breeding with each other.

canImpreg(slave1, slave2) // Returns if slave2 can impregnate slave1. PC works as an argument as well.

canFemImpreg(slave1, slave2) // Returns if slave2 can squirt cum and impregnate slave1. Assumes a slave does not have a dick. PC works as an argument as well.

isFertile(slave) // Returns if the actor is capable of having children.

canAchieveErection(slave) // Returns if the slave can get an erection. (Not blocked by chastity.)

canPenetrate(slave) // Returns if the slave can penetrate successfully.

canSee(slave) // Returns if the slave can see.

canHear(slave) // Returns if the slave can hear.

canSmell(slave) // Returns if the slave can smell.

canTaste(slave) // Returns if the slave can taste.

canHold(slave) // Returns if the slave can use both arms.

canWalk(slave) // Returns if the slave can walk unassisted.

canStand(slave) // Returns if the slave can stand unassisted.

canMove(slave) // Returns if the slave is capable of moving themselves.

isHindered(actor) // Returns if the actor's movement is impeded for any reason.

canTalk(slave) // Returns if the slave can talk.

canDoAnal(slave) // Returns if the slave can currently have anal sex.

canDoVaginal(slave) // Returns if the slave can currently have vaginal sex.

tooFatSlave(slave) // Returns if the slave is too fat to move.

tooBigBreasts(slave) // Returns if the slave's breasts are too big for her to move.

tooBigBelly(slave) // Returns if the slave's belly is too big for her to move.

tooBigBalls(slave) // Returns if the slave's balls are too big for her to move.

tooBigDick(slave) // Returns if the slave's dick is too big for her to move.

tooBigButt(slave) // Returns if the slave's butt is too big for her to move.

milkAmount(slave) // Returns the slave's expected milk output in liters.

App.Facilities.Farmyard.foodAmount(slave) // Returns the slave's expected food output in kilograms.

cumAmount(slave) // Returns the slave's expected cum output in deciliters.

isVegetable(slave) // Returns if the slave is mindbroken.

overpowerCheck(slave, PC) // Returns an integer that represents the chance of a slave overpowering the player.

canLift(actor1, actor2) // Returns if actor2 is capable of picking up and carrying actor1.

heelLength(slave) // Returns the length of a slave's heels should she be wearing any
```

## Player Functions

```js
onBedRest(actor) // Returns if the actor is on mandatory bedrest or just incapable of leaving their bed. (Mostly for player use.)
```

## Display Functions

```js
properTitle() // Returns the player's proper title. (customTitle, Sir, Ma'am)

properMaster() // Returns the slave's title for Master when WrittenMaster() is inappropriate. (customTitle, Master, Mistress)

WrittenMaster(slave) // Returns a slave's title for the player and sets lisping. Returns $activeSlave if not given an argument.

Spoken(slave, speech) // Returns speech with lisp if slave lisps. Replaces `<<say>>s` with Spoken(slave, "says").

SlaveFullName(slave) // Returns the slave's full name.

PlayerName() // Returns the player's full name.

PCTitle() // Returns the player's full title.

PoliteRudeTitle(slave) // Returns the slave's title for the player they hate.

SlaveTitle(slave) // Returns the slave's descriptive title.

relativeTerm(slave1, slave2) // Returns the term for slave2's relation to slave1. (daughter, mother, etc.)

vaginaDesc(slave) // Returns the description of the slave's vagina.

dickDesc(slave) // Returns the description of the slave's penis.

ballsDesc(slave) // Returns the description of the slave's testicles.

clitDesc(slave) // Returns the description of the slave's clitoris.

anusDesc(slave) // Returns the description of the slave's anus.

boobsDesc(slave) // Returns the size descriptor of the slave's breasts.

boobsDescLong(slave) // Returns the full description of the slave's breasts.

buttDesc(slave) // Returns the description of the slave's bottom.

lipsDesc(slave) // Returns the description of the slave's lips.

bellyDesc(slave, withNoun, pregReference) // Returns the description of the slave's belly.

penetrationTool(slave, includeClit) // Returns the description of the part of body that a slave/PC uses to penetrate, if he can, or a description for his dildo.

relationshipChecks [script] All work as expected with <<if X.rivalryTarget == $slaves[$i].ID>> preceding them.
 rivalryTerm(id) // Returns the rivalry term for the input. e.g. lines 99-100 of brothelReport.
  //<<if $Madam.rivalryTarget == $slaves[$i].ID>>
   //$He forces $his <<print rivalryTerm($Madam)>>, to service all the men in the brothel.
  //Would print 'She forces her growing rival, to service all the men in the brothel.'

 relationshipTerm(id) Returns the long form relationship term for the input. e.g. lines 147-148 of saRules.
  //<<if $slaves[$i].relationship > 0>>
   //$He often asks to save these breaks so $he can spend them with $his <<print relationshipTerm($slaves[$i])>>.
  //Would print '$He often asks to save these breaks so $he can spend them with $his friend.'

 relationshipTermShort(id) Prints the short form of the above. e.g. line 321 of slaveInteract.
  //`"Fuck $him with $his <<print relationshipTermShort($activeSlave)>> <<= SlaveFullName($slaves[_si])>>"`
  //Would print 'Fuck $him with $his BFF <<= SlaveFullName($slaves[_si])>>'

 PCrelationshipTerm(id) Prints the relationship term for the input (relative to the PC) if the relationship is < -1.
  //<<if $slaves[$i].relationship < -1>>
   //$He loves being your <<print PCrelationshipTerm($slaves[$i])>>.
  //Would print '$He loves being your wife.'

 contextualIntro(context, actor, insertComma=false) // Introduces an actor by using any meaningful relationship(s) with an already on-screen actor, and their name.
  //Returns strings like: "your husband John", "his growing rival and mother Alice", or "her best friend and twin sister Carla".
  //If there is no known relationship between them, returns the name alone. If insertComma is true, it will generate "her father, Dave" instead of "her father Dave".
  //Use this function instead of just printing the slave's name when you'd like to let the player to know if two actors are related, even though it's not going to have any mechanical impact on the scene.

bellyAdjective(slave) // Returns a string describing her belly size.

lispReplace(string) // Returns the string with a lisp.

nippleColor(slave) // Returns the slave's nipple color.

UtilJS [script]
 num() // Returns the value thousand separated with ',' if $formatNumbers > 0 else provides the raw value. Returns an integer if $showNumbers == 0, numbers up to a preset max as words if $showNumbers == 1, or only words if $showNumbers == 2.
  //line 138 of src/SpecialForce/Report.tw, '...focused their <<print num($SFUnit.Troops)>> troops'
  //if $formatNumbers > 0 'focused their 1,589 troops' else 'focused their 1589 troops'
  //if $showNumbers == 0 'focused their 1,589 troops', if $showNumbers == 1 'focused their 1,589 troops' (unless the max is set to more than 1,589), else 'focused their one thousand five hundred eighty-nine troops'

 cashFormat() // uses the above function to return the value thousand separated with ',' if $formatNumbers > 0 else provides the raw value. either way prepends ¤ (the fc domination) symbol.
  //line 157 of the previously listed file, '...totaling @@.yellowgreen;<<print cashFormat(_SFIncome)>>@@'
  //if $formatNumbers > 0 'totaling @@.yellowgreen;¤1,500,000@@' else 'totaling @@.yellowgreen;¤1500000@@'

 isFloat() // Checks if value is float.

 isInt() // Checks if value is an integer.

 numberWithCommas() // Currently unused.

 jsRandom() // JS equivalent of SugarCube's random().

 jsRandomMany() // JS equivalent of SugarCube's randomMany().

 jsEither() // This function wants an array // which explains why it works like array.random(). Give it one or you'll face a NaN. JS equivalent of SugarCube's either() and array.random().

 deepCopy() // This function is alternative to clone // usage needed if nested objects present. Slower but result is separate object tree, not with reference to source object.

 hashChoice() // hashes provided input.

 hashSum() // totals provided input and then hashes.

 arr2obj() // Converts an array to an object. e.g. line 250 of :: init Nationalities [silently]
  <<set $nationalities = arr2obj(setup.baseNationalities)>>

 hashPush() //Note really sure where input is being pushed to.

 weightedArray2HashMap()

 def() // Returns whether the input is defined, similar to SugarCube's def.
```

## Core Slave Functions

```js
newSlave(slave) // Adds slave object to main slave array. Do not use without care!

getSlave(ID) // Returns the slave object with the matching ID.

getPronouns(slave) // Returns an object containing a slave's pronouns.

generatePronouns(slave) // Sets slave's pronouns.

fetishChangeChance(slave) // Returns an int between 0,100 as the chance of a slave's fetish shifting to a new one.

SlaveSort(slaveArray) // Sorts the slaveArray array and sets indices.

slaveSortMinor(slaveArray) // Alphabetically sorts the slaveArray array and returns it.

faceIncrease(slave, amount) // Increases slave's .face by amount and returns a comment if it passes a threshold.

assignJob(slave, assignment) // Assigns slave to assignment. Mandatory for assigning to facilities.

removeJob(slave, assignment) // Removes slave from assignment to "rest". Mandatory for removing from facilities.

GenerateNewSlave(sex) // Generates a new slave of sex. Replaces <<include "Generate __ Slave">>

setPregType(actor) // Returns a random ovum count based off actor values and other factors to be set as .pregType.

removeActiveSlave() // Removes $activeSlave from $slaves. Do not use without care!

SetBellySize(slave) // Sets slave's belly size.(pregnancy+inflation+implant)

SoftenBehavioralFlaw(slave) // Replaces the slave's behavioral flaw with the corresponding quirk.

SoftenSexualFlaw(slave) // Replaces the slave's sexual flaw with the corresponding quirk.

//Increases the slave's skill by value or 1. Returns a string if the skill is boosted over a threshold.
SkillIncrease.Oral(slave, value)
SkillIncrease.Vaginal(slave, value)
SkillIncrease.Anal(slave, value)
SkillIncrease.Whore(slave, value)
SkillIncrease.Entertain(slave, value)

surgeryAmp(slave, part) // Clean up variables connected to a specific body part that was amputated. For limbs see below.

// limb can be: "left arm", "right arm", "left leg", "right leg", "all"
removeLimbs(slave, limb) // Remove limb(s) and clean up connected variables.
attachLimbs(slave, limb, id) // Attach limb(s). Expects amputated limbs, will overwrite existing.

//UtilJS [script]
 Height.mean(nationality, race, genes, age) // returns the mean height for the given combination and age in years (>=2).
 Height.mean(nationality, race, genes) // returns the mean adult height for the given combination.
 Height.mean(slave) // returns the mean (expected) height for the given slave.

 Height.random(nationality, race, genes, age) // returns a random height using the skew-normal distribution around the mean height for the given arguments.
 Height.random(nationality, race, genes) // returns a random height for the given combination of an adult, as above.
 Height.random(slave[, options]) // returns a random height for the given slave, as above.

 Height.forAge(height, age, genes) // returns the height adapted to the age and genes.
 Height.forAge(height, slave) // returns the height adapted to the slave's age and genes.

 heightToEitherUnit() // takes an int in centimeters e.g. $activeSlave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`

 Height.config(configuration) // configures the random height generator globally and returns the current configuration.

 Intelligence.random(options) // returns a random intelligence. If no options are passed, the generated number will be on a normal distribution with mean 0 and standard deviation 45.

 getSlaveDevotionClass(slave) // returns the trust of the target as text. e.g. if ('mindbroken' == slave.fetish) return 'mindbroken';

 getSlaveTrustClass(slave) // returns the trust of the target as text. e.g. if (slave.trust < -95) return 'extremely-terrified';
```

## Health Functions

```js
setHealth(slave, condition, shortDamage, longDamage, illness, tired) // Sets the health (primarily) of new slaves, it helps ensure the desired values do not immediately kill the slave and corrects them if needed

improveCondition(slave, value) // Basic way to improve the health of a slave, this updates the slave's 'condition' value and their overall 'health' value.

healthDamage(slave, value) // Basic way to reduce the health of a slave, this updates the slave's 'shortDamage' value and their overall 'health' value.

healthPenalty(slave) // Checks illness and tired state in order to provide the slave with a productivity penalty.

illness(slave) // A start of the 'end week loop' function to see if a slave has gotten ill this week, or perhaps recovered, got worse, etc.

endWeekHealthDamage // An end of the 'end week loop' function to move shortDamage to the appropriate longer term variables and such.
```

## Sex Functions

```js
knockMeUp(actor, chance, hole, fatherID, displayOverride) // Attempts to impregnate actor.

VCheck.Anal(count) // Increments $activeSlave's anal count by count and attempts to take virginity. Defaults to 1.

VCheck.Vaginal(count) // Increments $activeSlave's vaginal count by count and attempts to take virginity. Defaults to 1.

VCheck.Both(countAnal, countBoth) // Attempts to increment $activeSlave's anal count by countAnal and attempts to increment vaginal by countBoth. Defaults to 1. Attempts to take virginities.

VCheck.Simple(count) // Calls either VaginalVCheck or VCheck.Anal() count times.

VCheck.Partner(countAnal, countBoth) // Attempts to increment $partner's anal count by countAnal and attempts to increment vaginal by countBoth. Defaults to 1. Attempts to take virginities.

SimpleSexAct.Player(slave, count) // Runs a player on slave sex act count times. (randomly chooses hole based off availability.)

SimpleSexAct.Slave(slave, count) // Runs a slave on slave sex act count times. (randomly chooses hole based off availability.)

SimpleSexAct.Slaves(slave1, slave2, count) // Runs a slave2 on slave1 sex act count times. (randomly chooses hole based off availability.)

//UtilJS [script]

 dickToInchString() // takes a dick value e.g. $activeSlave.dick, returns a string in the format 6 inches

 dickToCM() // takes a dick value e.g. $activeSlave.dick, returns an int of the dick length in cm

 ballsToInchString() // takes a ball value e.g. $activeSlave.balls, returns a string in the format 3 inches

 ballsToCM() // takes a ball value e.g. $activeSlave.balls, returns an int of the ball size in cm

 dickToEitherUnit() // takes a dick value e.g. $activeSlave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`

 ballsToEitherUnit() // takes a ball value e.g. $activeSlave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
```

## Pregnancy Functions

```js
WombInit($slave) // before first pregnancy, at slave creation, of as backward compatibility update.

WombImpregnate($slave, $fetus_count, $fatherID, $initial_age) // should be added after normal impregnation code, with already calculated fetus count. ID of father // can be used in future for processing children from different fathers in one pregnancy. Initial age normally 1 (as .preg normally set to 1), but can be raised if needed. Also should be called at time as broodmother implant add another fetus(es), or if new fetuses added from other sources in future (transplanting maybe?)

WombProgress($slave, $time_to_add_to_fetuses) // after code that update $slave.preg, time to add should be the same.

$isReady = WombBirthReady($slave, $birth_ready_age) // how many children ready to be birthed if their time to be ready is $birth_ready_age (40 is for normal length pregnancy). Return int // count of ready to birth children, or 0 if no ready exists.

$children = WombBirth($slave, $birth_ready_age) // for actual birth. Return array with fetuses objects that birthed (can be used in future) and remove them from womb array of $slave. Should be called at actual birth code in SugarCube. fetuses that not ready remained in womb (array).

WombFlush($slave) // clean womb (array). Can be used at broodmother birthstorm or abortion situations in game. But birthstorm logically should use WombBirth($slave, 35) or so before // some children in this event is live capable, others is not.

TerminatePregnancy($slave) // end a pregnancy early, clean the womb, update the belly size, and automatically set an appropriate postpartum length.

$slave.bellyPreg = WombGetVolume($slave) // return double, with current womb volume in CC // for updating $slave.bellyPreg, or if need to update individual fetuses sizes.

findFather(ID) // searches for the ID given and returns an object or undefined
```

## Release Functions

```js
sexAllowed // returns true if both slaves are allowed to have sex with each other. All non-assignment sex should check here first; use disobedience() to determine if the slave or slaves involved will ignore the rules.

hasPartnerSex // returns true if the slave has a romantic partner, the relationship is sexual, and they are allowed to sex. Unless you are specifically checking the theoretical rule, check this instead of rules.release.partner.

hasFamilySex // returns true if the slave has a close family member, they are allowed to have sex, and the player is OK with seeing the result. Unless you are specifically checking the theoretical rule, check this instead of rules.release.family.

hasNonassignmentSex // returns true if the slave is having some kind of sex while off-duty with a partner other than the PC. This function provides the answer to "can I mention this slave having an off-duty sexual encounter of some kind."

releaseRestricted // returns true if the slave has some kind of rule limiting the off-duty sex they have. If it returns false, the slave is completely free to fuck whomever they want.
```

## Other Functions

```js
//UtilJS [script]
 cmToInchString() // takes an integer e.g. $activeSlave.hLength, returns a string in the format 10 inches

 cmToFootInchString() // takes an integer e.g. $activeSlave.height, returns a string in the format 6'5"

 lengthToEitherUnit() // takes an int in centimeters e.g. $activeSlave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`

 ValidateFacilityDecoration() // checks the value of the associated variable and it if it infinite i.e. NA the text description is reset back to standard.
  /* decoration should be passed as "facilityDecoration" in quotes. For example, ValidateFacilityDecoration("brothelDecoration"). The quotes are important, do not pass it as a story variable. */

 stretchedAnusSize() // Calculates the minimum anus size to accommodate a particular dick size

 FSChangePorn() // // Currently unused, widget version routes directly through FSChange()

 HackingSkillMultiplier() // outputs a value based off of the PC's hacking skill.

 upgradeMultiplierArcology() // outputs a value based off of the PC's engineering skill.

 upgradeMultiplierMedicine() // outputs a value based off of the PC's medicine skill.

 upgradeMultiplierTrade() // outputs a value based off of the PC's trading skill.

 passageLink() // Creates a HTML element with custom SugarCube attributes which works as a passage link

 SkillIncrease() // Depreciates the SugarCube functions.

 disobedience() // Returns a 0-100 value indicating likelihood of a slave ignoring the rules.

 penetrativeSocialUse() // Returns the percentage (0-100) of the social acceptance of the slaves penetrating citizens.
```


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
branch='pregmod-master'
usr='pregmodfan'

echo "Use temp [0], dump file to current directory [1] or overwrite file in place [2]" && read opt
echo "Use upstream [1] or origin [0]" && read usrSelection
echo "Dry run? 1, 0" && read dryRun

if [[ $usrSelection == 0 ]];then
	usr=$(git remote show origin|grep -e /|head -n 1 | sed s'/Fetch URL: //' | sed s%/fc-pregmod.git%% | sed s%git@ssh.gitgud.io:%% || sed %https://gitgud.io/%%)
	echo "Use master [1] or current branch [0]." && read targetBranch
	if [[ $targetBranch == 0 ]];then branch=$(git rev-parse --abbrev-ref HEAD); fi
fi

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


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;")

editor.rereplace(r"<<(Master)>>", r"${$1}")
editor.rereplace(r"<<(say)>>", 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.")

#drop spaces
editor.rereplace(r"r.push\(` ", r"r.push\(`")
editor.rereplace(r" `\);", r"`\);")

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

# Compress speech macros, like <<s>>
editor.rereplace(r"`\);\n[	]*<<([^\}]*)>>\n[	]*r\.push\(`", r"$1")

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
SET _SORT_CMD=sort

:: If SORT_MEM is defined, use memory option for SORT
IF DEFINED SORT_MEM SET _SORT_CMD=%_SORT_CMD% /m %SORT_MEM%

:: 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_CMD% >>%_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:
@echo off
pushd %~dp0
embed_favicon.py
popd
pause


code:
#!/usr/bin/env python3

'''
Script for embedding favicons into a HTML Header.

Script file is expected to reside in devTools directory.

Note: This does not actually check the image file's contents for size detection.

Usage:
python3 embed_favicon.py [FC_pregmod.html]
'''

import sys
import os
import re
import base64

# file extensions eligible for use as favicons and their mimetype
ext2mimetype = {
	'.png': 'image/png',
	'.ico': 'image/x-icon'
}

# reads a file, turns it into a data uri
def data_uri_from_file(filename, mimetype):
	data = open(filename, 'rb').read()
	base64data = base64.b64encode(data).decode('ascii')
	out = 'data:%s;base64,%s' % (mimetype, base64data)
	return out

if __name__ == "__main__":
	# find project root directory path
	# (script file is expected to reside in devTools)
	project_root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
	html_path = ''
	if (len(sys.argv) > 1):
		html_path = sys.argv[1]
	else:
		# path to bin/FC_pregmod.html
		html_path = os.path.join(
			project_root_path,
			'bin/FC_pregmod.html'
		)
	# path to directory containing all favicons to embed
	favicons_source_path = os.path.join(
		project_root_path,
		'resources/raster/favicon/'
	)
	# walk directory for all files
	favicons_paths = [
		os.path.join(dirpath, filename)
		for dirpath, dirnames, filenames in os.walk(favicons_source_path)
		for filename in filenames
	]
	# ignore files with unknown extensions
	favicons_paths = [f for f in favicons_paths
					  if f[-4:] in ext2mimetype.keys()]

	# prepare embedded data
	size_from_filename = re.compile(r'([0-9]+)\....$')
	favicons_html = []
	for fp in favicons_paths:
		print('Found favicon source file "%s".' % (fp))
		# get mimetype by file extension
		mimetype = ext2mimetype[fp[-4:]]
		if (mimetype == 'image/x-icon'):
			# assume sizes in ico
			sizes = '16x16 32x32 64x64'
		else:
			# guess icon size from file name
			size = size_from_filename.search(fp).group(1)
			sizes = '%sx%s' % (size, size)
		data = data_uri_from_file(fp, mimetype)
		favicons_html.append(
			# prepare html with favicon data embedded
			'<link rel="icon" type="%s" sizes="%s" href="%s">\n' % (
				mimetype, sizes, data
			)
		)

	# modify header file
	with open(html_path, 'r+', encoding='utf-8') as hf:
		print('Rewriting "%s"...' % (html_path))
		lines_in = hf.readlines()  # read whole file
		lines_out = []
		for line in lines_in:
			# embed favicons into head
			if (line.startswith('</head>')):
				lines_out.extend(favicons_html)
			# remove all currently embedded favicons
			if (not (line.startswith('<link') and 'icon' in line)):
				lines_out.append(line)
		hf.seek(0)  # move to beginning of file
		hf.write(''.join(lines_out))  # overwrite with new data
		hf.truncate()  # remove trailing old data

	print('Finished.')


code:
<!-- Added to the <head> of the html file -->
<link rel="shortcut icon" type="image/x-icon" href="resources/raster/favicon/arcologyVector.ico">


code:
Full sources for easy import into an IDE can be found [here](https://gitgud.io/Arkerthan/twine-sanitycheck).

`sources.zip` is a backup in case the repo is inaccessible.


code:
# The same as dictionary_phrases.txt, just case sensitive.
bellypreg#bellyPreg
bellyimplant#bellyImplant
bellyfluid#bellyFluid
earWear#earwear
eyeWear#eyewear
setBellySize#SetBellySize
SetbellySize#SetBellySize
setbellySize#SetBellySize
hormonebalance#hormoneBalance
fetishknown#fetishKnown