code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="48" height="48" version="1.1" inkscape:version="0.48.0 r9654">
  <defs id="defs4"/>
  <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="11.313708" inkscape:cx="27.203376" inkscape:cy="26.578528" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="1280" inkscape:window-height="968" inkscape:window-x="-4" inkscape:window-y="-4" inkscape:window-maximized="1">
    <inkscape:grid type="xygrid" id="grid2985" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true"/>
  </sodipodi:namedview>
  <g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-1004.3622)">
    <g id="g3000" transform="translate(-6,1003)">
      <path sodipodi:nodetypes="ccccc" inkscape:connector-curvature="0" id="path2987" d="m 30,2.3621826 -20,10.0000004 20,10 20,-10 z" style="fill:#f9f9f9;stroke:none"/>
      <path style="fill:#ececec;stroke:none" d="m 30,22.362183 0,25 20,-10 0,-25 z" id="path2989" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccc"/>
      <path sodipodi:nodetypes="ccccc" inkscape:connector-curvature="0" id="path2991" d="m 30,22.362183 0,25 -20,-10 0,-25 z" style="fill:#cccccc;stroke:none"/>
      <path inkscape:connector-curvature="0" id="path2993" d="M 30,2.3749996 10,12.375 l 0,25 20,10 20,-10 0,-25 L 30,2.3749996 z" style="fill:none;stroke:#4d4d4d;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
    </g>
  </g>
  <metadata>
    <rdf:RDF>
      <cc:Work>
        <dc:format>image/svg+xml</dc:format>
        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
        <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
        <dc:publisher>
          <cc:Agent rdf:about="http://openclipart.org/">
            <dc:title>Open Clip Art Library</dc:title>
          </cc:Agent>
        </dc:publisher>
        <dc:title>Sugar Cube icon</dc:title>
        <dc:date>2010-10-10T11:46:52</dc:date>
        <dc:description>A sugar cube.</dc:description>
        <dc:source>http://openclipart.org/detail/89407/sugar-cube-icon-by-jhnri4</dc:source>
        <dc:creator>
          <cc:Agent>
            <dc:title>jhnri4</dc:title>
          </cc:Agent>
        </dc:creator>
        <dc:subject>
          <rdf:Bag>
            <rdf:li>SVG</rdf:li>
            <rdf:li>block</rdf:li>
            <rdf:li>clip art</rdf:li>
            <rdf:li>clipart</rdf:li>
            <rdf:li>cube</rdf:li>
            <rdf:li>icon</rdf:li>
            <rdf:li>sugar</rdf:li>
            <rdf:li>white</rdf:li>
          </rdf:Bag>
        </dc:subject>
      </cc:Work>
      <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
        <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
        <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
        <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
      </cc:License>
    </rdf:RDF>
  </metadata>
</svg>


code:
/*
 * This is a TypeScript typings file with definitions an documentation of in-game structures.
 *
 * While documenting everything  here is optional, the definitions might be picked by IDE,
 * and, should TypeScript rewrite become a thing in future, would work as a static type check.
 */

declare interface ClothesItem {
	index: number;
	name: string;
	name_cap: string;
	/**
	 * Folder name
	 */
	variable: string;
	integrity: number;
	integrity_max: number;
	fabric_strength: number;
	reveal: number;
	bustresize: number;
	one_piece: number;
	strap: number;
	open: number;
	word: "a";
	state: string;
	state_base: string;
	state_top: string;
	state_top_base: string;
	plural: number;
	/**
	 * key in setup.colours.prefilters identifying preprocessing required for canvas renderer.
	 * default is "clothes"
	 */
	prefilter?: string;
	colour: string|0;
	colour_options: string[];
	colour_sidebar: 0|1;
	exposed: number;
	exposed_base: number;
	vagina_exposed: number;
	vagina_exposed_base: number;
	anus_exposed: number;
	anus_exposed_base: number;
	type: string[];
	set: string;
	gender: string;
	femininity: number;
	warmth: number;
	cost: number;
	description: string;
	shop: string[];
	accessory: number;
	accessory_colour: string|0;
	accessory_colour_options: string[];
	accessory_colour_sidebar: number;
	/**
	 * if 1, then accessory files are integrity-dependent "acc_(torn|tattered|frayed|full).png"
	 */
	accessory_integrity_img?: 0|1;
	high_img: 0|1;
	back_img: 0|1;
	/**
	 * Recolouring of back image
	 * * "" (default) - depending on colour_sidebar
	 * * "no" - do not recolour image
	 * * "primary" - use primary/main colour
	 * * "secondary" - use secondary/accessory colour
	 */
	back_img_colour?: ""|"no"|"primary"|"secondary";
	/**
	 * (For upper, over_upper, under_upper slots)
	 * 1 if has sleeve images, named (left|right)[_cover].png".
	 * Colouring depends on sleeve_colour property.
	 */
	sleeve_img: number;
	/**
	 * (For upper, over_upper, under_upper slots)
	 * 1 if has sleeve accessory images, named (left|right)[_cover]_acc.png".
	 * These images are not colored.
	 * Requires sleeve_img: 1.
	 */
	sleeve_acc_img: number;
	/**
	 * (For upper, over_upper, under_upper slots)
	 * Recolouring of sleeves images:
	 * * "" (default) - depending on colour_sidebar
	 * * "no" - do not recolour image
	 * * "primary" - use primary/main colour
	 * * "secondary" - use secondary/accessory colour
	 */
	sleeve_colour?: ""|"no"|"primary"|"secondary";
	breast_img: number;
	cursed: number;
	location: number;
	iconFile: string;
	accIcon: number;
	outfitPrimary: object;
	outfitSecondary: string[];
	notuck: number;
	/**
	 * (For head slots)
	 * if 1, this item has mask.png image to cut out hair & animal ears layers
	 */
	mask_img?: number;
	// TODO list and document other options
}

declare namespace setup {
	/**
	 * Slot names
	 */
	export let clothes_all_slots: string[];
	export namespace clothes {
		export let over_upper: ClothesItem[];
		export let over_lower: ClothesItem[];
		export let upper: ClothesItem[];
		export let lower: ClothesItem[];
		export let under_upper: ClothesItem[];
		export let under_lower: ClothesItem[];
		export let over_head: ClothesItem[];
		export let head: ClothesItem[];
		export let face: ClothesItem[];
		export let neck: ClothesItem[];
		export let hands: ClothesItem[];
		export let legs: ClothesItem[];
		export let feet: ClothesItem[];
		export let genitals: ClothesItem[];
	}
}

repo_name: Area/degrees

license: mit


code:
# Canvas renderer documentation

[[_TOC_]]

## Layer processing and composition

Example code that displays a single layer.

**JavaScript**:
```js
var canvas = Renderer.createCanvas(256, 256);
Renderer.composeLayers(canvas, [{
    src: "img/hair/sides/default/chest.png",
    desaturate: true,
    brightness: -0.3,
    blendMode: 'hard-light',
    blend: '#e49b67'
}]);
```
**SugarCube**:
```
<<canvasstart 256 256 1>>
<<canvaslayer 1 "img/hair/sides/default/chest.png" `{ desaturate: true, brightness: -0.3, blendMode: 'hard-light', blend: '#e49b67' }`>>
<<canvasdraw>>
```
The image will be processed in the following way:
![result](canvasmodel-demo1.png)

Explanation of layer processing options:

src: string

Image file path. Required.

desaturate: boolean

Convert to grayscale before processing. Optional, default false.

brightness: number

Adjust brightness before processing, from -1 (completely black) to +1 (completely white). Optional, default 0

contrast: number

Adjust contrast before processing, where 1 (default) is don't change, values in 0..1 reduce contrast (0 makes image gray) and values above 1 raise contrast.

blend: string

Blend color, CSS color string or gradient/pattern specification (see section at the bottom of the document). Optional, default none.

blendMode: string

Blend mode. Optional, default none. See globalCompositionOperation docs for list of modes

masksrc: string

Mask image file path. Optional. Result alpha = image alpha × mask alpha, so image pixels where mask is transparent will be removed. Mask color does not matter.

In addition to processing, layer has composition-related options that govern how multiple layers are composed into single image:

z: number

Display order, layers with higher numbers are rendered above others. Optional, default 0.

name: string

Layer name, for debugging. Optional

show: boolean

Render the layer. Optional, default true, and only exact "false" value prevents rendering.

alpha: number

Opacity from 0 (invisible) to 1 (opaque). Optional, default 1

dx: number, dy: number

Offset on the target canvas. Optional, default 0

width: number, height: number

Size of a single animation frame of the image. Optional, default auto-generated. Required if animated frame of this image is smaller than animated frame of target canvas.

and several others covered in [Animations](#Animations) section.

## CanvasModel

Instead of composing layers manually from game state, the recommended way to render is to use *CanvasModel* – a reusable set of layers that uses externally supplied options object to configure its layers. CanvasModel layers can have computable properties that take model options and return layer property value.

For example, `hair_sides` layer is defined as:

```js
Renderer.CanvasModels["demo_model"] = {
    ...
    layers: {
        "hair_sides": {
            srcfn(options) {
                return 'img/hair/sides/' + options.hair_sides_type + '/' + options.hair_sides_length + '.png'
            },
            zfn(options) {
                if (options.hair_sides_position === "front") {
                    return ZIndices.hairforwards
                } else {
                    return ZIndices.backhair
                }
            },
            showfn(options) {
                return !!options.hair && !!options.hair_sides_type
            },
            filters: ["hair"]
        },
```

Here, `filters` property is constant, while `src`, `z`, and `show` are computed from options. *Options* for this model would be: `hair`, `hair_sides_type`, and `hair_sides_position`.

If model layer has no `show` or `showfn` property, it will be hidden by default and has to be explicitly displayed with `<<showlayer LAYER_NAME>>`.

*Model filters* are used to set same set of properties to multiple layers at once, for example, hair or clothing colours. They have same type of layer properties object and are stored in `filters` object in model options.

So, a model with 1 layer as described above could be used as

**JavaScript**
```js
var model = Renderer.locateModel("demo_model");
var options = model.defaultOptions();
options.hair = true;
options.hair_sides_type = "default";
options.hair_sides_position = "front";
options.filters.hair = { desaturate: true, brightness: -0.3, blendMode: 'hard-light', blend: '#e49b67' }
var canvas = model.createCanvas();
model.render(options);
```
SugarCube
```
<<selectmodel "demo_model">>
<<set _modeloptions.hair to true>>
<<set _modeloptions.hair_sides_type to "default">>
<<set _modeloptions.hair_sides_position to "front">>
<<modelfilter "hair" `{ desaturate: true, brightness: -0.3, blendMode: 'hard-light', blend: '#e49b67' }`>>
<<rendermodel>>
```

Known hair colours are stored in setup.colours.hair_map and could be referenced like this instead:
```js
options.filters.hair = setup.colours.hair_map["lightbrown"].canvasfilter;
```
Or the model can have hair_colour option and do the lookup itself in its preprocess function:
```js
Renderer.CanvasModels["demo_model"] = {
    ...
    preprocess(options) {
        options.filters.hair = setup.colours.hair_map[options.hair_colour].canvasfilter;
    }
```

## Animations

There are two ways to animate canvases: generate a spritesheet and CSS-animate it, and JavaScript animations, updating canvas every frame.

For CSS animations you can control which sub-sprites are rendered using layer's `frames` property. It should be an array of size = number of animation frames, and its values are subsprite indices.
For example, if most layers have 2-frame idle animation, but as an exception, eyelids layer 2nd frame is "closed eyes" and you want it to always use frame 0 instead, add `frames: [0, 0]` property to it.
Static 1-frame images need no conversion or configuration for animation, they will be repeated for all frames.

JavaScript animations are stored in `Renderer.Animations` object:
```js
Renderer.Animations["idle"] = {
    keyframes: [{
        frame: 0,
        duration: 1000
    }, {
        frame: 1,
        duration: 1000
    }]
};
```
or, for animations with same frame count:
```js
Renderer.Animations["idle"] = {
    frames: 2,
    duration: 1000
}
```

Durations are in milliseconds and `frame` refers to sub-sprite in the layer sprite.
Animations are referenced by its name in the model layer's `animation` property, and can be functions (`animationfn(options) { ... }`).

### Animating layer properties

Animatable layer properties are: `show`, `alpha`, `blend`, `brightness`, `contrast`, `dx`, `dy`. They should be specified in animation keyframes.

Example:
```js
Renderer.Animations["idle_redblue"] = {
    keyframes: [{
        frame: 0,
        duration: 1000,
        blend: "#ff0000"
    }, {
        frame: 1,
        duration: 1000,
        blend: "#0000ff"
    }
};
```

## SugarCube widgets

### Model API

#### \<\<selectmodel NAME \[INSTANCE]>>
Select model and prepare for rendering
Parameter `INSTANCE` is id for caching between passages.
Do not render instance multiple times on same passage.

#### \<\<showlayer LAYERNAME \[...FILTERS]>>
Show layer and optionaly add filters

#### \<\<hidelayer LAYERNAME>>
Hide layer

#### \<\<modelfilter FILTERNAME FILTER>>
Set model filter object.
Will do a copy, so you can safely edit filter options

#### \<\<modelfilterset FILTERNAME OPTIONNAME OPTIONVALUE>>
Set model filter option

#### \<\<animatemodel \[CSSCLASS]>>
Render model and JS-animate it

#### \<\<rendermodel \[CSSCLASS] \[CSSANIM]>>
Render model as a static image
If `CSSANIM` is true (default false), render multiple frames for CSS animation

### Low-level API

#### \<\<canvasstart WIDTH HEIGHT FRAMES>>
Creates an off-screen `<canvas>` element and empty layers array.

#### \<\<canvaslayer Z SRC \[OPTIONS...]*>>
Prepares a layer to be rendered
* `Z`: layer z property
* `SRC`: layer src property
* `OPTIONS`: Extra layer option objects - they are merged, last has most priority

Alternative way to call:
```
<<run canvaslayer(...z|src|options)>>
````
Arguments are processed depending on their type: string is `src`, number is `z`, and object is layer options. Leftmost arguments have more priority.

#### \<\<canvasanimate \[CSS_CLASSES]>>
Insert HTML `<canvas>` element right here.
Render and animate previously prepared images into it.

#### \<\<canvasdraw \[FRAMECOUNT] \[CSS_CLASSES]>>
Insert HTML `<canvas>` element right here.
Render previously prepared images into it.

## Gradients & Patterns

Instead of fixed color string, layer's `blend` property can define a linear or radial gradient, or a pattern.

### Gradients

A gradient specification is a JSON object of structure:
* `gradient: "linear"|"radial"` - type of the gradient.
* `values: number[]` - gradient coordinates. For linear gradient: `[x0, y0, x1, y1]`; for radial gradient: `[x0, y0, r0, x1, y1, r1]`. The coordinates are in pixels, relative to canvas top left corner.
* `colors` - color stops, array of either pairs `[offset:number, color:string]` (where `offset` is position between 0 and 1), or simply `color` strings (in that case, `offset` is generated to create evenly spaced stops).

Example:
```js
T.modeloptions.hair_colour = "custom";
T.modeloptions.filters.hair = {
    blend: {
        gradient: "linear",
        values: [64, 64, 192, 192],
        colors: [ 'red', [0.25,'green'], 'blue' ]
        // ==   [ [0.0,'red'], [0.25,'green'], [1.0,'blue']]
    },
    blendMode: "hard-light",
    desaturate: true
}
```
will apply following gradient coloring to the layer:
![result](canvasmodel-demo2.png)

See createLinearGradient, createRadialGradient, and addColorStop documentation with more details and examples.

### Patterns

Patterns are images that are tiled horizontally and vertically, and applied to the layer with specified blend mode.

```js
T.modeloptions.worn_upper_colour = "custom";
T.modeloptions.filters.worn_lower_custom = {
    blend: { pattern:"wolfharmony" },
    blendMode:"hard-light"
}
```
Result:

![result](canvasmodel-demo3.png)

Pattern image in this example is ![pattern](../img/ui/wolfharmony.png). It is registered in `game/04-Variables/canvasmodel-patterns-lib.js` file.

It is also possible to define a procedurally generated pattern that takes parameters. See same file.


code:
# Android Build Instructions

## Required Tools

Before you start, you'll need...
  - `docker`

    The built docker image will provide all the tools for building the android apk and is required

### Optional Tools

  - android emulator tools (at minimum `adb`)(aka `Android Studio`)

    In order to test the produced apk, it's recommend to install android studio. If you don't want to install the entire IDE, I recommend following the instructions here https://stackoverflow.com/a/61150826. NOTE: at the time of this writing WSL users must install the windows version of android studio in order to actually use the emulator.

## Building

### Before your first build

**In the `devTools/androidsdk/image` folder**, run `docker build -t cordova-android:latest .`

Ensure that you've successfully compiled the game and that `Degrees of Lewdity.html` exists; use `FORCE_VERSION='' ./compile.sh` to generate it if you have pending changes.

### Building debug

Run `docker run -v <PROJECT_ROOT>:/src -it cordova-android npm run build-debug` in the project root. This will create an unsigned debug build for testing in the  `dist/` folder. Note, `<PROJECT_ROOT>` must be replaced with the absolute path to the project directory. This can be accomplished via
  - `` `pwd`:/src `` (linux bash)
  - `` %cd%:/src `` (windows)

### Building release

The release build process is almost identical to debug: `docker run -v <PROJECT_ROOT>:/src -it cordova-android npm run build-release`.

Note that building the release version requires a key (`/keys/dol.keystore`). Check the discord for details on how to get the official key.

> using an unofficial key will require the previous release to be uninstalled before proceeding: see `Installing` below

## Setting up the Emulator

An emulator is an easy way to test that the built apk works for a given version of android.

### Get the emulator image

Get the android 24 x86 image from the android-studio virtual device manager GUI (you can download it as part of creating a device)

### Create the emulator

Use the android-studio virtual device manager GUI. Be sure to select the correct image (see above). Additionally, be sure to select a device that supports Google Play (has a play icon in the GUI). For simplicity and consistency, use the 'Pixel 2', though feel free to experiment with other devices.

If you want to test against other android versions, note that the absolute minimum we can support with DoL is android-21. In addition, you'll have to find a way to update the default browser used by the device. Getting updates installed on pre-android-24 versions (and select other later android versions) is quite a bit more involved.

### **Update the emulator**
This is an important step, as without it you may be able to install and open the app in the emulator, but find it broken.

Start the emulator, and then open Google Play to update
1. Google play
2. Chrome browser

### Installing

Run `adb install -r <name of apk>` to easily install an apk on a currently running emulator or detected android device.

NOTE: If switching between release and/or debug version, it will likely be necessary to uninstall the previous version first using `adb uninstall com.vrelnir.DegreesOfLewdity`.


code:
Still to do

- Dancing
- Spa
- Farm Chickens Leave passage
- Other missed events where damage doesnt occur

- <<if $NPCList[_n].righthand is "overupperclothes">> combining hand checks in these cases
- replace older `.set is` values with the new `outfitChecks` variables

Widgets
- passagestrip
- covered
- lewdness
- stripobject
- outfit (and similar)
- pullup (and calls)

Remove debug checks
- Shop
- Wardrobe

Double check
- $worn.lower.name isnot "waist" for over varient
- vagina_exposed
- anus_exposed


code:
Incomplete widgets
- `pregnancyWatersBrokenPassout` - needs to be properly written
- `childrenEvents` and relating widgets, these are general events the player doesnt specifically control, needs more of them
- `childActivity` able to view the details of the children nearby, needs additional events, such as one of them crying and you being able to comfort them

Incomplete Events
- `Pregnancy ultrasound` and related passages - needs to be properly written
- `Pregnancy Birth Hospital Passout` and related birthing passages - needs to be properly written
- `Pharmacy Morning After pill` and related passages - needs to be properly written
- `PillCollectionSecondDosePregnancy` overdose effects, needs to be properly written and effects coded, partly done tho may need adjusting

Missing
- Named npc reactions
- A place for children to go to who are not wolves.
- Named Npc pregnancy, only partly supported right now.

Image related
- Add in pregnancy images
- Add in clothing specific changes to deal with images that dont work well with the belly

Could do with more
- General changes to events so that more recognise your pregnancy, including named npc's.
- Testing and adjusting the `pregnancyDailyEvent` widget, similar the the anal pregnancy messages, tho can also occur when waking up. Existing include `You feel some cramping near your vagina.`, `You feel some baby hiccups.` and others
- Brothal related changes, unsure on how to add something unique there - Scythe

Events which have been edited
:: Moor Vehicle
:: Penis Inspection
:: Pussy Inspection
:: Maths Competition Finish Skirt
:: Maths Competition Finish Shorts
school futa event where `"This is the one," the <<person1>><<person>> says. "The freak with a cock and pussy."` occurs
science event where the class watches a tv
school diving to prevent the player from diving
:: Science Undress
:: Street Panties Photo
:: Remove Futa Pregnancy
:: Farm Intro 7
farm_tending_events widget
:: Domus Model
:: Museum Duck 2 (to prevent the extreme scenes)
:: Museum Horse (to prevent the extreme scenes)
eventstemple widget
:: Livestock Intro Light
:: Livestock Intro Heavy 2
:: Livestock Milking Obey
:: Livestock Job Hypnosis 2 and related start in `:: Livestock Job`
livestock_overhear widget
:: Brothel - when Leighton spots you
:: Brothel Show Swarm - `Cum Bucket` pregnancy

Events to look into


code:
/**
 * As part of my various refactors, I ended up introducing a bunch of
 * global namespace-esk variables.
 *
 * Having a single spot to document them makes sense. By convention,
 * if you need to make a new top-level namespace, declare it here.
 */

/**
 * Declare everything in a root namespace, so that things can still be found
 * if shadowed, and for "documentation" purposes
 */
window.DOL = {
	// In pseudo-load order
	/**
	 * Mini application reporter app
	 * helps get detailed error messages to devs
	 * {@link ./01-error/error.js
	 */
	Errors: {},
	/**
	 * Registry of state schema, used for migrating Dol to new versions
	 * {@link ./02-version/.init.js
	 */
	Versions: {},
	Perflog: {},
	/**
	 * General purpose call stack containing the widget names as they are called.
	 */
	Stack: [],

	/** Patch to make javascript execution more consistent (see comment below) */
	State: State,
	/** Patch to make javascript execution more consistent (see comment below) */
	setup: setup,
	/** Patch to make javascript execution more consistent (see comment below) */
	Wikifier: Wikifier,
}
/* Make each of these namespaces available at the top level as well */
window.defineGlobalNamespaces = (namespaces) => {
	Object.entries(namespaces).forEach(([name, namespaceObject]) => {
		try {
			if (window[name] && window[name] !== namespaceObject) {
				console.warn(`Attempted to set ${name} in the global namespace, but it's already in use. Skipping this assignment. Existing Object:`, window[name])
			} else {
				/* Make it more difficult to shadow/overwrite things (users can still Object.defineProperty if they really mean it) */
				Object.defineProperty(window, name, { value: namespaceObject, writeable: false });
			}
		} catch (e) {
			if (window[name] !== namespaceObject) {
				console.error(`Failed to setup global namespace object ${name}. Attempting to continue. Source Error:`, e);
			}
		}
	});
}
defineGlobalNamespaces(DOL);

/**
 * Patches to make javascript execution more consistent
 * OR: Why we alias SugarCube.State as State:
 *
 *
 * When sugarcube executes code, it makes the global value `State`
 * the sugarcube state, but other globals like `SugarCube.State` don't exist
 *
 * By making this alias, if you simply use `State` to refer to state, it does not
 * matter if your code is executing inside of sugarcube or not. It'll always just work
 *
 * To elaborate further, we don't use `SugarCube.State` here as global initialisation is considered
 * sugarcube execution. `SugarCube.State` (currently) does not exist, but `State` (note, *not* window.State)
 * exists.
 */
/** Uncomment the following lines to get a better idea about how sugarcube makes certain globals available */
/*
function sugarCubeGlobals() {
	return {
		"State": typeof State !== 'undefined' ? State : '',
		"SugarCube": typeof SugarCube !== 'undefined' ? SugarCube : '',
		"SugarCube.State": typeof SugarCube !== 'undefined' ? (SugarCube || {}).State || '' : '',
		"window.State": window.State || '',
		"window.SugarCube": window.SugarCube || '',
		"window.SugarCube.State": (window.SugarCube || {}).State || ''
	}
}
$(document).on(':passageinit', () => {
	console.log(`:passageinit:`, sugarCubeGlobals());
});
$(document).on(':passageend', () => {
	console.log(`:passageend:`, sugarCubeGlobals());
	setTimeout(() => {
		console.log(`:passageend [after]:`, sugarCubeGlobals());
	})
})
*/


code:
#error-reporter-container {
	display: block;
}
#error-reporter-container h3 {
	margin-top: 0;
}
#error-reporter-container .hidden {
	display: none !important;
}
#error-reporter-container .pane {
	min-height: 10em;
	border: 1px solid red;
	padding: 1em;
}
#error-reporter-container .messages {
	max-height: 50vh;
	overflow-y: auto;
	margin-bottom: 0.5em;
}
#error-reporter-container .copy-area {
	flex-grow: 1;
	flex-basis: 0;
	margin: 0 1em;
	max-height: 8em;
}
#error-reporter-container .pane > .actions {
	display: flex;
	flex-flow: row nowrap;
	justify-content: start;
	align-items: center;
}
#error-reporter-container .button {
	display: inline-block;
	user-select: none;
	padding: 1em;
	cursor: pointer;
	background-color: darkslategray;
	align-self: center;
	flex: 0 0 auto;
}
#error-reporter-container .message-entry {
	display: flex;
	flex-flow: row nowrap;
	justify-content: stretch;
	align-items: center;
	margin-bottom: 1em;
	padding: 0.5em 1em;
	/*border: 1px solid rgba(255, 255, 255, 0.8);*/
}
#error-reporter-container .message-entry .actions {
	flex-grow: 0;
	flex-shrink: 0;
	flex-basis: auto;
}
#error-reporter-container .message-entry .message {
	flex-grow: 1;
	flex-basis: 0;
}


code:
/**
 * Error reporter utility that provides enriched error messages to the user as a simple sugarcube plugin app
 */
Errors.config = {
	// TODO: link this to an environment variable
	// for the time being, it can be enabled on console via `Errors.config.debug = true`
	debug: false,
	maxLogs: 100,
	showReporterSelector: '.error-reporter-btn'
}
Errors.log = []
Errors.registerMessage = (message, copyData) => {
	while (Errors.log.length >= Errors.config.maxLogs) Errors.log.shift();
	let error = {message, copyData};
	Errors.log.push(error);
	return error;
}
Errors.report = (message, copyData) => {
	let error;
	try {
		error = Errors.registerMessage(message, copyData);
	} catch(e) {
		console.error(`Failed to append an error log. Something went really wrong: `, message, copyData, e);
		alert(`A critical error occurred. Please report this issue to the devs. [Errors.report/registerMessage]`);
		return;
	}
	try {
		let showBtn = document.querySelector(Errors.config.showReporterSelector);
		if (showBtn) showBtn.classList.remove('hidden');
		if (Errors.Reporter.visible()) {
			if (Errors.log.length < Errors.config.maxLogs) {
				Errors.Reporter.messagesContainer().append(Errors.Reporter.createEntry(error));
			} else {
				Errors.Reporter.update();
			}
		}
	} catch(e) {
		console.error(`Failed to render and open in Error console. \nRendering error: `, e, `\n\nError log: `, Errors.log);
		alert(`A critical error occurred. Please report this issue to the devs. [Errors.report/displayMessage]`);
	}
}

{ /** Reporter DOM */
	/**
	 * These are utilities for making a plain old javascript "app" that displays and manages errors
	 */
	// making an additional namespace for this app. Note the scoping
	const Reporter = Errors.Reporter = {};

	Reporter.visible = function () {
		return !Reporter.reporterContainer().classList.contains('hidden');
	}
	Reporter.reporterContainer = function getReporter() {
		let reporterContainer = document.getElementById('error-reporter-container');
		if (!reporterContainer) {
			reporterContainer = document.createElement('div');
			reporterContainer.id = 'error-reporter-container';
			reporterContainer.className = "hidden";
			reporterContainer.innerHTML = `

Errors:

Close

Clear

Copy

						<textarea class="copy-area hidden"></textarea>

`;
			document.querySelector('#story').insertAdjacentElement('afterbegin', reporterContainer);
		}
		return reporterContainer;
	}
	Reporter.messagesContainer = () => {
		return Reporter.reporterContainer().querySelector(`.messages`);
	}
	Reporter.paneContainer = () => {
		return Reporter.reporterContainer().querySelector(`.pane`);
	}
	Reporter.copyArea = () => {
		return Reporter.reporterContainer().querySelector('.copy-area')
	}
	Reporter.toggle = () => {
		if (Reporter.visible()) {
			Reporter.hide();
		} else {
			Reporter.show();
		}
	}
	Reporter.show = () => {
		Reporter.reporterContainer().classList.remove('hidden');
		Reporter.update();
	}
	Reporter.update = () => {
		const reports = Reporter.messagesContainer();
		reports.innerHTML = '';
		for (let error of Errors.log) {
			reports.append(Reporter.createEntry(error));
		}
	}
	Reporter.hide = (andClear) => {
		Reporter.reporterContainer().classList.add('hidden');
		Reporter.copyArea().classList.add('hidden')
		if (andClear) {
			Errors.log.splice(0);
			let showBtn = document.querySelector(Errors.config.showReporterSelector);
			if (showBtn) showBtn.classList.add('hidden');
		}
	}
	Reporter.createEntry = (error) => {
		const div = document.createElement("div");
		div.className = "message-entry";
		div.innerHTML = `<div class="message"></div>`;
		// This ensures that only text content is reported
		div.querySelector(".message").textContent = error.message;
		return div;
	}
	Reporter.copyAll = function() {
		const copyButton = Reporter.reporterContainer().querySelector('.button.copy');
		const copyArea = Reporter.copyArea();
		try {
			copyArea.textContent = JSON.stringify(Errors.log.map(e=>[e.message,e.copyData]), null, 2);
			copyArea.classList.remove('hidden');
			copyArea.focus();
			copyArea.select();
			document.execCommand('copy')
			copyButton.textContent = 'Copied!'
		} catch(e) {
			copyButton.textContent = 'Copy Failed :('
			alert(`Failed to copy to the clipboard. Try manually copying from the textarea, or if nothing has appeared, directly from Errors.log in the developer console.`)
		}
		setTimeout(() => {
			copyButton.textContent = 'Copy';
		}, 5000)
	}
}


code:
Versions.registry = [];
Versions.log = [];
Versions.recordStep = (step) => {
	Versions.log.push(step);
}
// this will be updated automatically
Versions.latest = 0;
Versions.register = (versionId, migration) => {
	console.log(`Registering version schema`, typeof versionId, versionId, migration);
	if (versionId !== (Versions.registry.length + 1)) {
		// If we get all versions in order, we don't have to try to
		// verify them after the fact.
		Errors.report(`Invalid migration registration. Next expected version id was ${Versions.registry.length + 1}, but got ${versionId} instead. Version migrations might not work as expected`)
	}
	Versions.registry[versionId - 1] = {
		migration, versionId
	}
	Versions.latest = Math.max(Versions.latest, versionId);
}
Versions.migrate = (originalVersion, state) => {
	Versions.log = [];
	let currentVersion = originalVersion;
	let migrationFailed = false;
	let nextMigration = Versions.registry[currentVersion];
	while(nextMigration && !migrationFailed) {
		migrationFailed = nextMigration.migration(originalVersion, state);
		currentVersion = nextMigration.versionId;
		nextMigration = Versions.registry[currentVersion];
	};
	if (migrationFailed) {
		Errors.report(`One or more errors occurred while attempting to migrate from schema version ${originalVersion} to the latest`, Versions.log);
	}
	if (currentVersion !== Versions.latest && !migrationFailed) {
		Errors.report(`Migration steps missing for schema version: ${originalVersion}`, Versions.log)
	}
	return currentVersion;
}
/**
 * Utility for migrations to use. Make more robust migrations + provides logging in case of later errors
 *
 * Usage:
 * Versions.stepper(originalVersion, <youridhere>)
 *  .step(() => ...)
 *	  // option; critical = true;
 *	  // if it fails nothing else will execute and the migration will be marked as a failure
 *  .step(() => ..., { critical: true })
 *	  // option; name = <string>
 *	  // provides a human readable name for the purpose of logging and error reporting
 *  .step(() => ..., { name: 'my specific step' })
 *	  // option; requires = <function>
 *	  // ensures that the step did what was intended to be done.
 *	  // this is useful in that if the update step fails, but we're already in a good state
 *	  // then we know we can (probably) continue successfully, despite the earlier failure
 *  .step(() => ..., { requires: () => ... })
 *
 * Note, by convention, a single stepper should be used
 */
Versions.Stepper = class Stepper {
	constructor(originalVersion, currentStage) {
		this.originalVersion = originalVersion;
		this.currentStage = currentStage;
		this.nextStepIdNumber = 0;
		this.shouldContinue = true;
	}
	step(statement, { critical, requires, name }={}) {
		const errors = [];
		let requirementsComplete = !requires;
		if (this.shouldContinue) {
			if (name === undefined) { name = statement.toString(); }
			const stepId = `${this.currentStage}-${this.nextStepIdNumber++}`
			try {
				statement();
			} catch(e) {
				errors.push(e);
			} finally {
				try {
					requirementsComplete = requires();
				} catch(e) {
					errors.push(e);
				}
			}
			Versions.recordStep({
				stepId,
				originalVersion: this.originalVersion,
				currentStage: this.currentStage,
				name,
				errors
			})
			this.shouldContinue = requirementsComplete && !(errors.length > 0 && critical);
		}
		return this;
	}
}
Versions.stepper = (originalVersion, currentStage) => {
	return new Versions.Stepper(originalVersion, currentStage);
}

$(document).on(":archiverevive", function (e) {
	const state = e.archive.state;
	const version = state.variables.version;

	if (!version || version.schema === undefined) {
		// The sugarcube baseline version has yet to be applied. Move to a special passage to
		// apply it. The user will be able to resume afterwords.
		if (state.title !== 'Upgrade Waiting Room' && state.title !== 'Start') {
			console.log(`Schema version number not found. Banishing the player to the waiting room.`)
			state.variables.navigation = state.variables.navigation || { }
			state.variables.navigation.stack = state.variables.navigation.stack || [];
			state.variables.navigation.stack.push(state.title);
			state.title = 'Upgrade Waiting Room';
		}
	} else if (version.schema !== Versions.latest) {
		console.log(`Migrating game state from ${schemaVersion} to ${Versions.latest}`);
		state.variables.schemaVersion = Versions.migrate(schemaVersion === undefined ? 0 : schemaVersion, state);
		// modified history, be sure to notify the framework by returning the history object
		// TODO: apply class transformations to objects in state *after* migrations are complete
	}
});


code:
/**
 * By convention, we list the migrations in a version file where the migration was introduced
 *
 * This convention is a 0 padded migration number (in this case 0001), followed by the game version
 *
 * Note that this convention is loosely enforced -- we'll throw an error if you attempt to save a
 * migration from an earlier version
 */
Versions.register(1, (originalVersion, state) => {
	// IMPORTANT: WHEN WE DO MIGRATION, DO NOT USE SUGARCUBE
	// sugarcube maybe in an invalid state when this happens and/or
	// your script might not behave as you intend

	// This is a double edge sword. It means that we can always
	// expect migrations to be done before any sugarcube executes
	// *but* that means all migrations should be done as pure javascript

	// In addition, this should be before any other complex logic.
	// This means that classes stored in state will not be revived at this point
	// (any class solution should keep that in mind)

	// This migration is a good template. It serves as a marker for the baseline
	// version. It will (most likely) never be executed, as the baseline migration
	// will happen in sugarcube (unlike the other migrations)
	return Versions.stepper(originalVersion, 1)
		.step(() => {
			// this is a no-op step
		}, { name: 'placeholder'}).shouldContinue;
})


code:
:: Upgrade Waiting Room

Welcome to Degrees of Lewdity <<print StartConfig.version>>. We're making a few changes to your game so it supports this latest version.

Remember to update your save if things appear to be working.

<<silently>>
<<variablesVersionUpdate>>
<<pregnancyVar>>

<<if $reducedLineHeight is true>><<addclass "#passages" "reducedLineHeight">><</if>>
<<if $zoom isnot 100>><<run zoom($zoom)>><</if>>
<<set $runWardrobeSanityChecker to true>>
<</silently>>

Looks like we're done already.

[[Resume|$navigation.stack.last()][$navigation.stack.pop()]]


code:
window.inDOM = function(node) {
	return document.body.contains(node)
}

/**
 * Simple observable value framework, somewhat inspired by Vue.js
 *
 * @example
 * var text = new ObservableValue("hello");
 * text.subscribe( (newText, oldText) => console.log('Text changed from', oldText, 'to', newText) );
 * text.bindText(document.getElementById("text"));
 * text.value = "hello world";
 */
class ObservableValue {
	constructor(defaultValue) {
		this._value = defaultValue;
		this._listeners = [];
		this._mutating = false; // we are in process of changing value
		this._nextValues = null; // queued value changes
	}
	/**
	 * Receive notifications on value change.
	 * If the onChange function returns exactly false, stop notifications.
	 * Exact equality is checked, so void-returning functions are fine too.
	 *
	 * Be careful with changing values in the listener!
	 * @param {function} onChange function(newValue, oldValue):any
	 */
	subscribe(onChange) {
		this._listeners.push(onChange);
	}
	/**
	 * Don't notify {@param onChange} listener anymore
	 */
	unsubscribe(onChange) {
		let index = this._listeners.indexOf(onChange);
		if (index >= 0) this._listeners.splice(index, 1);
	}

	/**
	 * Subscribe and auto-unsubscribe when element gets removed from DOM.
	 * @param {Node} element
	 * @param {function} onchange function(newValue, oldValue)
	 * @return {Node} element
	 */
	domSubscribe(element, onchange) {
		this.subscribe((newValue,oldValue)=>{
			if (onchange(newValue,oldValue) === false) return false;
			return inDOM(element);
		});
		return element
	}
	/**
	 * Bind element's textContent to this value.
	 * @param {Node} element
	 * @return {Node} element
	 */
	bindText(element) {
		element.textContent = this._value;
		this.domSubscribe(element,(newValue)=>{
			element.textContent = newValue;
		});
		return element;
	}
	_notifyListeners(newValue,oldValue) {
		const remove = [];
		const listeners = this._listeners.slice();
		for (let listener of listeners) {
			try {
				let keep = listener(newValue, oldValue);
				if (keep === false) remove.push(listener);
			} catch (e) {
				remove.push(listener);
				console.error("Exception in ObservableValue listener: ",e,"listener is ",listener);
			}
		}
		for (let listener of remove) {
			this.unsubscribe(listener);
		}
	}
	/**
	 * Get current value
	 */
	get value() {
		return this._value;
	}
	/**
	 * Change value, notifying subscribers
	 */
	set value(newValue) {
		if (this._mutating) {
			this._nextValues.push(newValue);
			return;
		}

		let oldValue = this._value;
		if (oldValue === newValue) return;
		this._value = newValue;

		this._mutating = true;
		this._nextValues = [];

		this._notifyListeners(newValue,oldValue);

		let n = 0;
		while (this._nextValues.length > 0) {
			if (n++ > 999) {
				console.error("Possible endless loop in ObservableValue ",this);
				break;
			}
			oldValue = newValue;
			newValue = this._nextValues.shift();
			this._notifyListeners(newValue,oldValue);
		}
		this._mutating = false;
		this._nextValues = null;
	}
}

/**
 * Modify obj, wrapping every property into an ObservableValue. Not deep!
 * @param {object} obj
 * @return {object} obj
 */
ObservableValue.fromObject = function(obj) {
	for (let key of Object.keys(obj)) {
		obj[key] = new ObservableValue(obj[key]);
	}
	return obj;
}
window.ObservableValue = ObservableValue;


code:
// Time in milliseconds, float with microsecond precision if available.
const millitime = (typeof performance === 'object' && typeof performance.now === 'function') ?
	function() { return performance.now() } : function() { return new Date().getTime() };
DOL.Perflog.millitime = millitime;

/**
 * Widget performance records for one passage.
 * Key = widget name, value = {
 *  name: widget name,
 * 	n: number of widget calls,
 *  total: Sum of execution times including internal widget calls, milliseconds
 *  own: Sum of execution times excluding internal widget calls, milliseconds
 * }.
 */
var wpRec = {}; // Widget performance records
const wpSelf = { name: "__perflog_logWidgetEnd", n: 0, total: 0, own: 0 };
wpRec[wpSelf.name] = wpSelf;
/**
 * Global widget performance records
 */
DOL.Perflog.globalRec = {};
DOL.Perflog.lastRec = {};
DOL.Perflog.enabled = true;
DOL.Perflog.self = true; // Record own performance
/**
 * Performance stack entry = { t0: widget start time, i: internal widget duration }
 */
const wpStack = []; // Widget performance stack
DOL.Perflog.nPassages = 0;
$(document).on(':passageend', function() {
	if (!DOL.Perflog.enabled) return;
	try {
		if (DOL.Perflog.self) DOL.Perflog.logWidgetStart("__perflog_passageEnd")
		DOL.Perflog.nPassages++;
		for (var name in wpRec) {
			var rec = wpRec[name];
			var grec = DOL.Perflog.globalRec[name];
			if (!grec) {
				DOL.Perflog.globalRec[name] = rec;
			} else {
				grec.n += rec.n;
				grec.total += rec.total;
				grec.own += rec.own;
			}
		}
		DOL.Perflog.lastRec = wpRec;
		wpRec = {};
	} finally {
		if (DOL.Perflog.self) DOL.Perflog.logWidgetEnd("__perflog_passageEnd")
	}
});
DOL.Perflog.logWidgetStart = function(widgetName) {
	if (!DOL.Perflog.enabled) return;
	wpStack.push({
		name: widgetName,
		t0: millitime(),
		i: 0
	});
}
/**
 * Record that widget `widgetName` executed in `totaltime` ms,
 * including `internaltime` internal widget calls
 * @param {string} widgetName
 * @param {number} totaltime
 * @param {number=0} internaltime
 */
DOL.Perflog.logWidgetTime = function(widgetName, totaltime, internaltime) {
	if (typeof internaltime !== "number") internaltime = 0;
	const prevPerflog = wpStack[wpStack.length - 1];
	if (prevPerflog) {
		prevPerflog.i += totaltime;
	}
	var perfrec = wpRec[widgetName];
	if (!perfrec) perfrec = wpRec[widgetName] = {
		name:widgetName,
		n:0,
		total:0,
		own:0
	};
	perfrec.n++;
	perfrec.total += totaltime;
	perfrec.own += totaltime - internaltime;
}
DOL.Perflog.logWidgetEnd = function(widgetName) {
	if (!DOL.Perflog.enabled) return;
	const time = millitime();
	const perflog = wpStack[wpStack.length - 1];
	if (!perflog || perflog.name !== widgetName) {
		Errors.report("Inconsistent widget performance stack", wpStack);
		return;
	}
	wpStack.pop();
	DOL.Perflog.logWidgetTime(widgetName, time-perflog.t0, perflog.i);
	if (DOL.Perflog.self) {
		const selfDt = millitime() - time
		wpSelf.n++;
		wpSelf.own += selfDt;
		wpSelf.total += selfDt;
	}
}

/**
 * Returns a "nice" string representation of a number without too much decimal digits.
 * @param {number} x
 */
function niceround(x) {
	if (Math.floor(x) === x) return x; // Integers
	if (-1 < x && x < 1) {
		// Small number, round to 0.001 instead
		return Math.round(x*1000)/1000;
	}
	return Math.round(x*10)/10;
}
/**
 * Return performance report. Can be viewed by DevTools table() function.
 *
 * Returns array of objects:
 *  `name`: Widget name
 *  `n`: Times widget called
 *  `total`: Sum of widget execution time, including inner widgets, ms
 *  `own`: Sum of widget execution time, excluding inner widgets, ms
 *  `totalp1`: Average widget execution time, including inner widgets, ms
 *  `ownp1`: Average widget execution time, excluding inner widgets, ms
 *  `npp`: Average time widget called per passage
 *  `totalpp`: Average total time per passage
 *  `ownpp`: Average own time per passage
 *
 * @param {object} options
 * @param {string} [options.sort='total'] Property to sort entries by
 * @param {number} [options.limit=20] Max number of entries to report, 0 to no limit
 * @param {boolean} [options.global=true] Report widgets from all recorded history or from last passage only.
 * @param {boolean} [options.round=true] Round to 0.1 precision
 * @param {string} [options.filter=null] A regular expression pattern that will be used to filter widgets
 */
DOL.Perflog.report = function (options) {
	options = Object.assign({
		sort: 'own',
		limit: 20,
		global: true,
		round: true,
		filter: null
	}, options);
	const numfn = options.round ? niceround : (x)=>x;
	var entries;
	if (options.global) {
		const np = DOL.Perflog.nPassages;
		// Add 'per passage' metrics
		entries = Object.values(DOL.Perflog.globalRec).map(e=>({
			name: e.name,
			n: e.n,
			total: numfn(e.total),
			own: numfn(e.own),
			npp: numfn(e.n/np),
			totalpp: numfn(e.total/np),
			ownpp: numfn(e.own/np),
			totalp1: numfn(e.total/e.n),
			ownp1: numfn(e.own/e.n)
		}));
	} else {
		entries = Object.values(DOL.Perflog.lastRec).map(e=>({
			name: e.name,
			n: e.n,
			total: numfn(e.total),
			own: numfn(e.own),
			totalp1: numfn(e.total/e.n),
			ownp1: numfn(e.own/e.n),
		}));
	}
	if (options.filter) {
		var matcher = new RegExp(options.filter)
		entries = entries.filter(x => typeof x.name === 'string' && matcher.test(x.name))
	}
	var comparator;
	const sort = options.sort;
	switch (sort) {
		case 'own':
		case 'total':
		case 'n':
		case 'npp':
		case 'ownpp':
		case 'totalpp':
		case 'totalp1':
		case 'ownp1':
			comparator = (a,b)=>b[sort] - a[sort];
			break;
		case 'name':
		default:
			comparator = (a,b)=> a.name < b.name ? -1 : a.name > b.name ? +1 : 0;
	}
	entries.sort(comparator);
	if (options.limit > 0 && entries.length > options.limit) entries.splice(options.limit);
	return entries;
}


code:
Object.defineProperties(window, {
	V: {
		get: function() {
			return State.variables;
		}
	},
	T: {
		get: function() {
			return State.temporary;
		}
	},
	C: {
		value: {}
	}
});


code:
:: StoryData
{
"ifid": "A6AFB52D-38F2-4CB5-86A8-54BF665670D6",
"format": "SugarCube",
"format-version": "2.23.4"
}

:: StoryTitle
Degrees of Lewdity

:: Start [nosave]
<<if $intro is undefined>>
	<<gameStartOnly>>
<</if>>
<<effects>>

<<if $images is 1>>

<</if>>

This work of fiction contains content of a sexual nature and is inappropriate for minors. All characters depicted are at least 18 years of age. Everything is consensual role play, and any animals are actually people in costumes.

Save files are stored in your browser's cache. Save to file or text in the "Export/Import" tab in "Saves" to avoid losing them.

If the image on the "Feat Boosts" tab is broken, please ensure that you have properly extracted the zip file.
/*
<<if $images is 1>>
	 If you have, you may want to overwrite the "img" folder with one from a fresh download of the game.

<</if>>
*/

The Character and Feat Boosts tabs can only be accessed at the start of the game. Other settings can be changed in your room.

<<set $settingsExitPassage to "Start2">>
<<initsettings>>
<<settings>>

:: Start2

<<variablesStart2>>
<<effects>>

Welcome to the alpha of Degrees of Lewdity!

If you want to avoid trouble, dress modestly and stick to safe, well-lit areas. Nights are particularly dangerous. Dressing lewd will attract attention, both good and bad.

The new school year starts tomorrow at <<ampm 9 00>>. The bus service is the easiest way to get around town. Don't forget your uniform!

<<link [[Next|Orphanage Intro]]>><</link>>

<<if $debug is 1>>
	These options start with cheats enabled and £5000

	<<link [[Standard start|Orphanage Intro]]>><<cheatStart>><</link>>

	<<link [[School Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><</link>>

	<<link [[Science Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><<pass 2 hours>><</link>>

	<<link [[Maths Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><<pass 3 hours>><</link>>

	<<link [[English Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><<pass 4 hours>><</link>>

	<<link [[History Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><<pass 6 hours>><</link>>

	<<link [[Swimming Start|Oxford Street]]>><<cheatStart>><<pass 1 day>><<pass 7 hours>><</link>>

	<<link [[Testing Room]]>><<cheatStart>><<upperstrip>><<lowerstrip>><<underlowerstrip>><</link>>

	<<link [[Robin Low Trauma Low Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 0>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 2>><<trauma 60>><</link>>

	<<link [[Robin Low Trauma Medium Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 50>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 2>><<trauma 60>><</link>>

	<<link [[Robin Low Trauma High Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 90>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 2>><<trauma 60>><</link>>

	<<link [[Robin High Trauma Low Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 0>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Robin High Trauma Medium Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 50>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Robin High Trauma High Love|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Robin")].love to 90>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Robin After Paying Police With Money|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $robinpolicepay to 1>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Robin After Paying Police With Body|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $robinpolicebody to 1>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Robin's Debt Paid|Robin's Room Entrance]]>><<cheatStart>><<npc Robin>><<endevent>><<set $robinpaid to 1>><<set $NPCName[$NPCNameList.indexOf("Robin")].init to 1>><<set $trauma to ($traumamax / 7) * 4>><<trauma 60>><</link>>

	<<link [[Winter Start|Orphanage Intro]]>><<set $days to 92>><<set $yeardays to 92>><<set $monthday to 1>><<set $month to "december">><<set $season to "winter">><<set $weather to "snow">><</link>> (Begins the game at the start of winter. For testing.)

	<<link [[Christmas Start|Orphanage Intro]]>><<npc Kylar>><<person1>><<endevent>><<set $NPCName[$NPCNameList.indexOf("Kylar")].state to "active">><<set $days to 115>><<set $yeardays to 115>><<set $monthday to 23>><<set $month to "december">><<set $season to "winter">><<set $weather to "snow">><<set $money += 50000>><</link>> (Begins the game two days before Christmas. For testing.)

<</if>>


code:
Config.history.controls = false;

Config.history.maxStates = 1;

State.prng.init()

window.versionUpdateCheck = true;
window.saveUpdateCheck = true;
window.onLoadUpdateCheck = false;

Config.saves.onLoad = function (save) {
	window.onLoadUpdateCheck = true;
}

Config.saves.onSave = function (save) {
	new Wikifier(null, '<<updateFeats>>');
}

/*LinkNumberify and images will enable or disable the feature completely*/
/*debug will enable or disable the feature only for new games*/
window.StartConfig = {
	"debug": false,
	"enableImages": true,
	"enableLinkNumberify": true,
	"version": "0.3.6.1",
	"mod": "CM",
}

/* convert version string to numeric value */
let tmpver = StartConfig.version.replace(/[^0-9.]+/g, "").split(".");
window.StartConfig.version_numeric = tmpver[0]*1000000 + tmpver[1]*10000 + tmpver[2]*100 + tmpver[3]*1;

config.saves.autosave = "autosave";

Config.saves.isAllowed = function () {
	if (tags().includes("nosave")) {
		return false;
	}
	return true;
};

importStyles("style.css")
	.then(function () {
		console.log("External Style Sheet Active")
	})
	.catch(function (err) {
		console.log("External Style Sheet Missing");
	});

console.log("Game Version:", StartConfig.version);

l10nStrings.errorTitle = StartConfig.version + " Error";

/**
 * Not a configuration, but we are overriding a basic part of sugarcube
 *
 * Provides a magic variable `$_` that creates a custom scope for the current
 * widget invocation
 *
 * NOTE: we basically steal sugarcube code as code reuse is more difficult
 * than it's worth in this instance. Be advised that updating sugarcube
 * may break this
 */
const VIRTUAL_CURRENT = "_";
const vStack = []
const vContext = []
const d = JSON.stringify.bind(JSON);
let devOptions = {
	trace: false,
	invocationId: false
}
// setTimeout as Sugarcube may not have finished making it's State object yet,
// and any lifecycle hooks (that might make this less hackish), are
// non-obvious if they exist
setTimeout(() => State.variables.devOptions = devOptions)
// We declare some global debug utils that other code is free to use
// Note that enabling trace will display all widget calls
// Note that clog is currently non-configured and will always be invoked
// TODO: add more granular debug log levels if needed
function clog() {
	console.log(`${State.passage}:${d(vContext)}`, ...arguments)
}
function trace() {
	if (devOptions.trace) {
		clog(...arguments)
	}
}
function allMagical() {
	return Object.keys(State.variables).filter(key => key.startsWith(VIRTUAL_CURRENT) && key != VIRTUAL_CURRENT)
}
let uniqueInvocation = 0;
function widgetHandler(widgetName, contents) {
	let argsCache;
	trace('declaring fn', widgetName);
	return function () {
		DOL.Stack.push(widgetName);
		const context = devOptions.invocationId
			? `${State.passage}:${widgetName}:${uniqueInvocation++}`
			: `${State.passage}:${widgetName}`;
		trace('invoking', context);
		vContext.push(context);
		// Custom code
		DOL.Perflog.logWidgetStart(widgetName);
		const newFrame = {};
		State.variables[VIRTUAL_CURRENT] = newFrame;
		vStack.push(newFrame);
		/**
		 * place the previous invocation's magical objects on the stack
		 * It's an error if the prior frame doesn't exist
		 *  note: that would mean the $_var was defined in the body,
		 *  as we clean our local magic variables
		 */
		const priorFrame = vStack[vStack.length - 2]
		const magicals = allMagical();
		if (magicals.length > 0) {
			trace(`saving ${d(magicals)} to ${d(priorFrame)}`)
		}
		if (priorFrame !== undefined) {
			magicals.forEach(key => {
				priorFrame[key] = State.variables[key]
				delete State.variables[key]
			})
		} else if (magicals.length > 0) {
			console.warn(`Found variables: ${JSON.stringify(magicals)} declared in main`)
		}
		// End custom code

		// Cache the existing value of the `$args` variable, if necessary.
		if (State.variables.hasOwnProperty('args')) {
			argsCache = State.variables.args;
		}
		State.variables.args = [...this.args];
		State.variables.args.raw = this.args.raw;
		State.variables.args.full = this.args.full;
		this.addShadow('$args');

		try {
			// Set up the error trapping variables.
			const resFrag = document.createDocumentFragment();
			const errList = [];

			// Wikify the widget contents. add nobr behavior -ng
			new Wikifier(resFrag, contents.replace(/^\n+|\n+$/g, '').replace(/\n+/g, ' '));

			// Carry over the output, unless there were errors.
			Array.from(resFrag.querySelectorAll('.error')).forEach(errEl => {
				errList.push(errEl.textContent);
			});

			if (errList.length === 0) {
				this.output.appendChild(resFrag);
			}
			else {
				console.error(`Error rendering widget ${widgetName}`, errList);
				return this.error(`${V.args.length > 0 ? '($args=[' + V.args.full + ']): ' : ''}error${errList.length > 1 ? 's' : ''} within widget contents (${errList.join('; ')})`);
			}
		}
		catch (ex) {
			console.error(`Error executing widget ${widgetName} ${V.args.length > 0 ? 'with arguments "'+ V.args.full +'"' : ''}`, ex); return this.error(`cannot execute widget: ${ex.message}`);
		}
		finally {
			// Custom code
			DOL.Stack.pop();
			vStack.pop();
			vContext.pop();
			State.variables[VIRTUAL_CURRENT] = priorFrame
			const magicals = allMagical();
			if (magicals.length > 0) {
				trace(`cleaning up ${d(magicals)}`)
				magicals.forEach(key => {
					// don't pollute the global namespace
					delete State.variables[key]
				})
			}
			if (priorFrame !== undefined && Object.keys(priorFrame).length > 0) {
				trace(`restoring ${d(priorFrame)}`)
				// restore prior frame
				Object.assign(State.variables, priorFrame)
			}
			DOL.Perflog.logWidgetEnd(widgetName);
			// End custom code
			if (typeof argsCache !== 'undefined') {
				State.variables.args = argsCache;
			}
			else {
				delete State.variables.args;
			}
		}
	};
}
Macro.delete('widget');
Macro.add('widget', {
	tags: null,

	handler() {
		if (this.args.length === 0) {
			return this.error('no widget name specified');
		}

		const widgetName = this.args[0];

		if (Macro.has(widgetName)) {
			if (!Macro.get(widgetName).isWidget) {
				return this.error(`cannot clobber existing macro "${widgetName}"`);
			}

			// Delete the existing widget.
			Macro.delete(widgetName);
		}

		try {
			Macro.add(widgetName, {
				isWidget: true,
				handler: widgetHandler(widgetName, this.payload[0].contents)
			});

			// Custom debug view setup.
			if (Config.debug) {
				this.createDebugView(
					this.name,
					`${this.source + this.payload[0].contents}<</${this.name}>>`
				);
			}
		}
		catch (ex) {
			return this.error(`cannot create widget macro "${widgetName}": ${ex.message}`);
		}
	}
});
// delete parser that adds unneeded line breaks -ng
Wikifier.Parser.delete("lineBreak");

/* ToDo: implement the dolls system, uncomment during and when its setup
importScripts([
	"img/dolls/NameValueMaps.js",
	"img/dolls/dollUpdater.js",
	"img/dolls/dollLoader.js",
	"img/dolls/DollHouse.js",
	"img/dolls/FDoll.js",
]).then(function () {
	console.log("Dolls scripts running");
})
.catch(function (err) {
	console.log(err);
});*/


code:
:: Widgets Version Info [widget]
<<widget "versioninfo">>

Degrees of Lewdity <<print StartConfig.version>>

"It's Planned" edition

Updates and more information can be found at [[https://vrelnir.blogspot.com/| "https://vrelnir.blogspot.com/"]]

Support development at [[https://subscribestar.adult/vrelnir| "https://subscribestar.adult/vrelnir"]]

'Lewdcraft' mod by Area.

Don't report bugs in this mod to Vrelnir, it's more than likely my fault instead.

<</widget>>