Skip to content
Snippets Groups Projects
rulesAssistantOptions.tw 80.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • vas's avatar
    vas committed
    :: Rules Assistant Options [script]
    
    vas's avatar
    vas committed
    // jshint esversion: 6
    // jshint browser: true
    
    vas's avatar
    vas committed
    // rewrite of the rules assistant options page in javascript
    // uses an object-oriented widget pattern
    // wrapped in a closure so as not to polute the global namespace
    // the widgets are generic enough to be reusable; if similar user interfaces are ported to JS, we could move the classes to the global scope
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    window.rulesAssistantOptions = (function() {
    
    vas's avatar
    vas committed
    	"use strict";
    	let V;
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    	function rulesAssistantOptions(element) {
    
    vas's avatar
    vas committed
    		V = State.variables;
    		V.nextButton = "Back to Main";
    		V.nextLink = "Main";
    		V.returnTo = "Main";
    		V.showEncyclopedia = 1;
    		V.encyclopedia = "Personal Assistant";
    		const root = new Root(element);
    
    vas's avatar
    vas committed
    	}
    
    
    vas's avatar
    vas committed
    	function onreturn(e, cb) {
    
    vas's avatar
    vas committed
    		if (e.keyCode === 13) cb();
    
    vas's avatar
    vas committed
    	}
    
    	// create a new rule and reload
    
    vas's avatar
    vas committed
    	function newRule(root) {
    
    vas's avatar
    vas committed
    		const id = generateNewID();
    
    vas's avatar
    vas committed
    		V.defaultRules.push({
    
    vas's avatar
    vas committed
    			ID: id,
    			name: `Rule ${id}`,
    			condition: {
    				function: false,
    				data: {},
    				excludeSpecialSlaves: false,
    				assignment: [],
    				excludeAssignment: [],
    				selectedSlaves: [],
    				excludedSlaves: [],
    				facility: [],
    				excludeFacility: [],
    			},
    			set: {
    				releaseRules: "no default setting",
    				clitSetting: "no default setting",
    				clitSettingXY: "no default setting",
    				clitSettingXX: "no default setting",
    				clitSettingEnergy: "no default setting",
    				speechRules: "no default setting",
    				clothes: "no default setting",
    				collar: "no default setting",
    				shoes: "no default setting",
    				virginAccessory: "no default setting",
    				aVirginAccessory: "no default setting",
    				vaginalAccessory: "no default setting",
    				aVirginDickAccessory: "no default setting",
    				dickAccessory: "no default setting",
    				bellyAccessory: "no default setting",
    				aVirginButtplug: "no default setting",
    				buttplug: "no default setting",
    				eyeColor: "no default setting",
    				makeup: "no default setting",
    				nails: "no default setting",
    				hColor: "no default setting",
    				hLength: "no default setting",
    				hStyle: "no default setting",
    				pubicHColor: "no default setting",
    				pubicHStyle: "no default setting",
    				nipplesPiercing: "no default setting",
    				areolaePiercing: "no default setting",
    				clitPiercing: "no default setting",
    				vaginaLube: "no default setting",
    				vaginaPiercing: "no default setting",
    				dickPiercing: "no default setting",
    				anusPiercing: "no default setting",
    				lipsPiercing: "no default setting",
    				tonguePiercing: "no default setting",
    				earPiercing: "no default setting",
    				nosePiercing: "no default setting",
    				eyebrowPiercing: "no default setting",
    				navelPiercing: "no default setting",
    				corsetPiercing: "no default setting",
    				boobsTat: "no default setting",
    				buttTat: "no default setting",
    				vaginaTat: "no default setting",
    				dickTat: "no default setting",
    				lipsTat: "no default setting",
    				anusTat: "no default setting",
    				shouldersTat: "no default setting",
    				armsTat: "no default setting",
    				legsTat: "no default setting",
    				backTat: "no default setting",
    				stampTat: "no default setting",
    				curatives: "no default setting",
    				livingRules: "no default setting",
    				relationshipRules: "no default setting",
    				standardPunishment: "no default setting",
    				standardReward: "no default setting",
    				diet: "no default setting",
    				dietCum: "no default setting",
    				dietMilk: "no default setting",
    				muscles: "no default setting",
    				XY: "no default setting",
    				XX: "no default setting",
    				gelding: "no default setting",
    				preg: "no default setting",
    				growth_boobs: "no default setting",
    				growth_butt: "no default setting",
    				growth_lips: "no default setting",
    				growth_dick: "no default setting",
    				growth_balls: "no default setting",
    				aphrodisiacs: "no default setting",
    				autoSurgery: 0,
    				autoBrand: 0,
    				pornFameSpending: "no default setting",
    				dietGrowthSupport: 0,
    				eyewear: "no default setting",
    				setAssignment: "no default setting",
    				facilityRemove: false,
    				removalAssignment: "rest",
    				surgery_eyes: "no default setting",
    				surgery_lactation: "no default setting",
    				surgery_prostate: "no default setting",
    				surgery_cosmetic: "no default setting",
    				surgery_accent: "no default setting",
    				surgery_shoulders: "no default setting",
    				surgery_shouldersImplant: "no default setting",
    				surgery_boobs: "no default setting",
    				surgery_hips: "no default setting",
    				surgery_hipsImplant: "no default setting",
    				surgery_butt: "no default setting",
    				surgery_faceShape: "no default setting",
    				surgery_lips: "no default setting",
    
    vas's avatar
    vas committed
    				surgery_holes: "no default setting",
    				surgery_hair: "no default setting",
    				surgery_bodyhair: "no default setting",
    
    vas's avatar
    vas committed
    				underArmHColor: "no default setting",
    				underArmHStyle: "no default setting",
    				drug: "no default setting",
    				eyes: "no default setting",
    				pregSpeed: "no default setting",
    				bellyImplantVol: -1,
    			}
    
    vas's avatar
    vas committed
    		});
    		V.currentRule = V.defaultRules[V.defaultRules.length-1];
    		reload(root);
    
    vas's avatar
    vas committed
    	}
    
    	function removeRule(root) {
    
    vas's avatar
    vas committed
    		const idx = V.defaultRules.findIndex(rule => rule.ID === V.currentRule.ID);
    		V.defaultRules.splice(idx, 1);
    		V.currentRule = idx < V.defaultRules.length ? idx : V.defaultRules.length - 1;
    		reload(root);
    
    vas's avatar
    vas committed
    	}
    
    
    vas's avatar
    vas committed
    	function lowerPriority(root) {
    
    vas's avatar
    vas committed
    		if (V.defaultRules.length === 1) return; // nothing to swap with
    		const idx = V.defaultRules.findIndex(rule => rule.ID === V.currentRule.ID);
    		if (idx === 0) return; // no lower rule
    		arraySwap(V.defaultRules, idx, idx-1);
    		reload(root);
    
    vas's avatar
    vas committed
    	}
    
    	function higherPriority(root) {
    
    vas's avatar
    vas committed
    		if (V.defaultRules.length === 1) return; // nothing to swap with
    		const idx = V.defaultRules.findIndex(rule => rule.ID === V.currentRule.ID);
    		if (idx === V.defaultRules.length - 1) return; // no higher rule
    		arraySwap(V.defaultRules, idx, idx+1);
    		reload(root);
    
    vas's avatar
    vas committed
    	}
    
    	function changeName(name, root) {
    
    vas's avatar
    vas committed
    		if (name === V.currentRule.name) return;
    		V.currentRule = name;
    		reload(root);
    
    vas's avatar
    vas committed
    	}
    
    	// reload the passage
    
    vas's avatar
    vas committed
    	function reload(root) {
    
    vas's avatar
    vas committed
    		root.element.remove();
    		rulesAssistantOptions();
    
    vas's avatar
    vas committed
    	}
    
    	// the Element class wraps around a DOM element and adds extra functionality
    	// this is safer than extending DOM objects directly
    	// it also turns DOM manipulation into an implementation detail
    	class Element {
    		constructor(...args) {
    
    vas's avatar
    vas committed
    			this.parent = null;
    			this.element = this.render(...args);
    			this.children = [];
    
    vas's avatar
    vas committed
    		}
    
    		appendChild(child) {
    
    vas's avatar
    vas committed
    			child.parent = this;
    			this.children.push(child);
    			this.element.appendChild(child.element);
    
    vas's avatar
    vas committed
    		}
    
    		// return the first argument to simplify creation of basic container items
    		render(...args) {
    
    vas's avatar
    vas committed
    			return args[0];
    
    vas's avatar
    vas committed
    		}
    	}
    
    	// list of clickable elements
    	// has a short explanation (the prefix) and a value display
    	// value display can optionally be an editable text input field
    	// it can be "bound" to a variable by setting its "onchange" method
    	class List extends Element {
    
    vas's avatar
    vas committed
    		constructor(prefix, data=[], textinput=false) {
    
    vas's avatar
    vas committed
    			super(prefix, textinput);
    			this.selectedItem = null;
    
    vas's avatar
    vas committed
    			data.forEach(item => this.appendChild(new ListItem(...item)));
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		render(prefix, textinput) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    			const label = document.createElement("span");
    			label.innerHTML = prefix;
    			let value;
    
    vas's avatar
    vas committed
    			if (textinput) {
    
    vas's avatar
    vas committed
    				value = document.createElement("input");
    				value.classList.add("rajs-value"); // 
    
    vas's avatar
    vas committed
    				// call the variable binding when the input field is no longer being edited, and when the enter key is pressed
    
    vas's avatar
    vas committed
    				value.onfocusout = () => { this.inputEdited(); };
    				value.onkeypress = (e) => { onreturn(e, () => { this.inputEdited(); }); };
    
    vas's avatar
    vas committed
    			} else {
    
    vas's avatar
    vas committed
    				value = document.createElement("strong");
    
    vas's avatar
    vas committed
    			}
    
    vas's avatar
    vas committed
    			value.setAttribute("type", "text");
    
    vas's avatar
    vas committed
    			this.value = value;
    
    vas's avatar
    vas committed
    			elem.appendChild(label);
    			elem.appendChild(value);
    			elem.classList.add("rajs-list");
    			return elem;
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    		inputEdited() {
    
    vas's avatar
    vas committed
    			if (this.selectedItem) this.selectedItem.deselect();
    			this.propagateChange();
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		selectItem(item) {
    
    vas's avatar
    vas committed
    			if (this.selectedItem) this.selectedItem.deselect();
    			this.selectedItem = item;
    
    vas's avatar
    vas committed
    			this.setValue(item.data);
    
    vas's avatar
    vas committed
    			this.propagateChange();
    
    vas's avatar
    vas committed
    		}
    
    
    vas's avatar
    vas committed
    		selectValue(what) {
    			this.children.some(child => {
    				if (child.data === what) {
    
    vas's avatar
    vas committed
    					child.select();
    					return true;
    
    vas's avatar
    vas committed
    				}
    
    vas's avatar
    vas committed
    				else return false;
    			});
    
    vas's avatar
    vas committed
    		}
    
    
    vas's avatar
    vas committed
    		setValue(what) {
    			if (this.value.tagName === "input")
    
    vas's avatar
    vas committed
    				this.value.value = what;
    
    vas's avatar
    vas committed
    			else
    
    vas's avatar
    vas committed
    				this.value.innerHTML = what;
    
    vas's avatar
    vas committed
    		}
    
    
    vas's avatar
    vas committed
    		getData(what) {
    
    vas's avatar
    vas committed
    			return (this.value.tagName === "input" ? this.parse(this.value.value) : this.selectedItem.data);
    
    vas's avatar
    vas committed
    		}
    
    		// customisable input field parser / sanity checker
    
    vas's avatar
    vas committed
    		parse(what) { return what; }
    
    vas's avatar
    vas committed
    
    		propagateChange() {
    			if (this.onchange instanceof Function)
    
    vas's avatar
    vas committed
    				this.onchange(this.getData());
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	const parse = {
    		integer(string) {
    
    vas's avatar
    vas committed
    			let n = parseInt(string, 10);
    			return isNaN(n)? 0: n;
    
    vas's avatar
    vas committed
    		},
    		boobs(string) {
    
    vas's avatar
    vas committed
    			return Math.clamp(parse.integer(string), 0, 48000);
    
    vas's avatar
    vas committed
    		},
    		butt(string) {
    
    vas's avatar
    vas committed
    			return Math.clamp(parse.integer(string), 0, 10);
    
    vas's avatar
    vas committed
    		},
    		lips(string) {
    
    vas's avatar
    vas committed
    			return Math.clamp(parse.integer(string), 0, 100);
    
    vas's avatar
    vas committed
    		},
    		dick(string) {
    
    vas's avatar
    vas committed
    			return Math.clamp(parse.integer(string), 0, 10);
    
    vas's avatar
    vas committed
    		},
    		balls(string) {
    
    vas's avatar
    vas committed
    			return Math.clamp(parse.integer(string), 0, 10);
    
    vas's avatar
    vas committed
    		},
    
    vas's avatar
    vas committed
    	};
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    	// a clickable item of a list
    	class ListItem extends Element {
    
    vas's avatar
    vas committed
    		constructor(displayvalue, data) {
    
    vas's avatar
    vas committed
    			super(displayvalue);
    			this.data = data !== undefined ? data: displayvalue;
    			this.selected = false;
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		render(displayvalue) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("span");
    			elem.classList.add("rajs-listitem");
    			elem.innerHTML = displayvalue;
    			elem.onclick = () => { return this.select(); };
    			return elem;
    
    vas's avatar
    vas committed
    		}
    
    		select() {
    
    vas's avatar
    vas committed
    			if (this.selected) return false;
    			this.parent.selectItem(this);
    
    vas's avatar
    vas committed
    			this.element.classList.add("selected");
    
    vas's avatar
    vas committed
    			this.selected = true;
    			return true;
    
    vas's avatar
    vas committed
    		}
    
    		deselect() {
    
    vas's avatar
    vas committed
    			this.elem.classList.remove("selected");
    			this.selected = false;
    
    vas's avatar
    vas committed
    		}
    	}
    
    	// a way to organise lists with too many elements in subsections
    	// children are bound to the master list
    	class ListSubSection extends Element {
    
    vas's avatar
    vas committed
    		constructor(parent, label, pairs) {
    
    vas's avatar
    vas committed
    			super(label);
    
    vas's avatar
    vas committed
    			this.parent = parent;
    
    vas's avatar
    vas committed
    			pairs.forEach(item => this.appendChild(new ListItem(...item)));
    
    vas's avatar
    vas committed
    		}
    		
    
    vas's avatar
    vas committed
    		render(label) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("em");
    			elem.innerText = label + ":";
    			return elem;
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		appendChild(child) {
    
    vas's avatar
    vas committed
    			super.appendChild(child);
    			child.parent = this.parent;
    			this.parent.children.push(child);
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// similar to list, but is just a collection of buttons
    
    vas's avatar
    vas committed
    	class Options extends Element {
    		constructor(elements=[]) {
    
    vas's avatar
    vas committed
    			super();
    
    vas's avatar
    vas committed
    			elements.forEach(element => { this.appendChild(element); });
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    			elem.classList.add("rajs-list");
    			return elem;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// options equivalent of ListItem
    
    vas's avatar
    vas committed
    	class OptionsItem extends Element {
    		constructor(label, onclick) {
    
    vas's avatar
    vas committed
    			super(label);
    			this.label = label;
    			this.onclick = onclick;
    
    vas's avatar
    vas committed
    		}
    		render(label, onclick) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("span");
    			elem.classList.add("rajs-listitem");
    			elem.innerHTML = label;
    			elem.onclick = () => { return this.onclick(this); };
    			return elem;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ButtonList extends Element {
    		render(label) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    			const labelel = document.createElement("span");
    			labelel.innerhTML = label += ":";
    			elem.appendChild(labelel);
    			return elem;
    
    vas's avatar
    vas committed
    		}
    
    		getSelection() {
    			return (this.children
    				.filter(child => child.selected)
    				.map(child => child.setvalue)
    
    vas's avatar
    vas committed
    			);
    
    vas's avatar
    vas committed
    		}
    
    
    vas's avatar
    vas committed
    		onchange() { return; }
    
    vas's avatar
    vas committed
    	}
    
    	class ButtonItem extends Element {
    		constructor(label, setvalue, selected=false) {
    
    vas's avatar
    vas committed
    			super(label, selected);
    			this.selected = selected;
    			this.setvalue = setvalue ? setvalue : label;
    
    vas's avatar
    vas committed
    		}
    
    		render(label, selected) {
    
    vas's avatar
    vas committed
    			const container = document.createElement("div");
    			container.classList.add("rajs-listitem");
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			const labelel = document.createElement("span");
    			labelel.innerHTML = label;
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			const button = document.createElement("input");
    			button.setAttribute("type", "checkbox");
    			button.checked = selected;
    			button.onchange = () => this.onchange(button.checked);
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			container.appendChild(labelel);
    			container.appendChild(button);
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			return container;
    
    vas's avatar
    vas committed
    		}
    
    		onchange(value) {
    
    vas's avatar
    vas committed
    			this.selected = value;
    			parent.onchange(this);
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// rule import field
    
    vas's avatar
    vas committed
    	class NewRuleField extends Element {
    		constructor(root) {
    
    vas's avatar
    vas committed
    			super();
    			this.root = root;
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			const container = document.createElement("div");
    
    vas's avatar
    vas committed
    			const textarea = document.createElement("textarea");
    
    vas's avatar
    vas committed
    			textarea.placeholder = "Paste your rule here";
    			container.appendChild(textarea);
    			this.textarea = textarea;
    			const button = document.createElement("button");
    			button.name = "Load";
    			button.onclick = () => { this.loadNewRule(); };
    			container.appendChild(button);
    			return container;
    
    vas's avatar
    vas committed
    		}
    
    		loadNewRule() {
    
    vas's avatar
    vas committed
    			const text = this.textarea.value;
    
    vas's avatar
    vas committed
    			try {
    
    vas's avatar
    vas committed
    				const rule = JSON.parse(text);
    				if (!rule.ID) rule.ID = generateNewID();
    				reload(this.root);
    
    vas's avatar
    vas committed
    			} catch (e) {
    
    vas's avatar
    vas committed
    				alert(e);
    
    vas's avatar
    vas committed
    			}
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// the base element, parent of all elements
    
    vas's avatar
    vas committed
    	class Root extends Element {
    
    vas's avatar
    vas committed
    		constructor(element) {
    			super(element);
    
    vas's avatar
    vas committed
    			if(V.defaultRules.length === 0) {
    
    vas's avatar
    vas committed
    				const paragraph = document.createElement("p");
    				paragraph.innerHTML = "<strong>No rules</strong>";
    				this.appendChild(new Element(paragraph));
    				this.appendChild(new NoRules(this));
    				return;
    
    vas's avatar
    vas committed
    			}
    
    vas's avatar
    vas committed
    			this.appendChild(new RuleSelector(this));
    			this.appendChild(new RuleOptions(this));
    
    vas's avatar
    vas committed
    			this.appendChild(new ConditionEditor(this));
    
    vas's avatar
    vas committed
    			this.appendChild(new EffectEditor(this));
    
    vas's avatar
    vas committed
    		}
    
    		render(element) {
    
    vas's avatar
    vas committed
    			const greeting = document.createElement("p");
    			greeting.innerHTML = `<em>${properTitle()}, I will review your slaves and make changes that will have a beneficial effect. Apologies, ${properTitle()}, but this function is... not fully complete. It may have some serious limitations. Please use the 'no default setting' option to identify areas I should not address.</em>`;
    			element.appendChild(greeting);
    			return element;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// optoins displayed when there are no rules
    
    vas's avatar
    vas committed
    	class NoRules extends Options {
    		constructor(root) {
    
    vas's avatar
    vas committed
    			super();
    			this.root = root;
    			const newrule = new OptionsItem("Add a new rule", () => { newRule(this.root); });
    			this.appendChild(newrule);
    			const importrule = new OptionsItem("Import a rule", () => { this.root.appendChild(new NewRuleField()); });
    			this.appendChild(importrule);
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// buttons for selecting the current rule
    	class RuleSelector extends List {
    		constructor(root) {
    
    vas's avatar
    vas committed
    			if (!V.currentRule)
    
    vas's avatar
    vas committed
    				V.currentRule = V.defaultRules[0];
    
    vas's avatar
    vas committed
    			super("Current rule:", V.defaultRules.map(i => [i.name, i]));
    
    vas's avatar
    vas committed
    			this.onchange = function (rule) {
    
    vas's avatar
    vas committed
    				V.currentRule = rule;
    				reload(root);
    			};
    
    vas's avatar
    vas committed
    		}
    	}
    
    	// buttons for doing transformations on rules
    	class RuleOptions extends Options {
    		constructor(root) {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new OptionsItem("New Rule", () => newRule(root)));
    			this.appendChild(new OptionsItem("Remove Rule", () => removeRule(root)));
    			this.appendChild(new OptionsItem("Apply rules", () => this.appendChild(new ApplicationLog())));
    			this.appendChild(new OptionsItem("Lower Priotity", () => lowerPriority(root)));
    			this.appendChild(new OptionsItem("Higher Priority", () => higherPriority(root)));
    			this.appendChild(new OptionsItem("Rename", () => this.appendChild(new RenameField(root))));
    			this.appendChild(new OptionsItem("Export this rule", () => this.appendChild(new ExportField())));
    			this.appendChild(new OptionsItem("Export all rules", () => this.appendChild(new ExportField(true))));
    			this.appendChild(new OptionsItem("Import a rule", () => this.appendChild(new NewRuleField())));
    
    vas's avatar
    vas committed
    		}
    	}
    
    	class ApplicationLog extends Element {
    		render() {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    			elem.innerHTML = DefaultRules();
    			return elem;
    
    vas's avatar
    vas committed
    		}
    	}
    
    	class RenameField extends Element {
    		constructor(root) {
    
    vas's avatar
    vas committed
    			super();
    			this.element.onfocusout = () => changeName(this.element.value, root);
    			this.element.onkeypress = (e) => onreturn(e, () => changeName(this.element.value, root));
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    			elem.setAttribute("type", "text");
    			elem.setAttribute("value", V.currentRule.name);
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ExportField extends Element {
    		render(all=false) {
    
    vas's avatar
    vas committed
    			const element = document.createElement("textarea");
    			element.value = all ? JSON.stringify(V.currentRule) : map(i => JSON.stringify(i), V.defaultRules).join("\n\n");
    			return element;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// parent section for condition editing
    	class ConditionEditor extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new ConditionFunction());
    			this.appendChild(new AssignmentInclusion());
    			this.appendChild(new FacilityInclusion());
    			this.appendChild(new SpecialExclusion());
    			this.appendChild(new SpecificInclusionExclusion());
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			const element = document.createElement("div");
    			return element;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ConditionFunction extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    
    vas's avatar
    vas committed
    			const items = ["Never", "Always", "Custom"];
    			["Devotion", "Trust", "Health", "Sex drive", "Weight", "Age", "Body Age", "Visible Age", "Muscles", "Lactation", "Pregnancy", "Pregnancy Multiples", "Belly Implant", "Belly Size"].forEach(i => items.push([i, this.getAttribute(i)]));
    			this.fnlist = new List("Activation function:", items);
    			this.fnlist.onchange = (value) => this.fnchanged(value);
    
    vas's avatar
    vas committed
    			this.fneditor = null;
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			switch(V.currentRule.condition.function) {
    				case "Never":
    				case "Always":
    
    vas's avatar
    vas committed
    					break;
    
    vas's avatar
    vas committed
    				case "Custom":
    
    vas's avatar
    vas committed
    					this.appendChild(new CustomEditor(V.currentRule.condition.data));
    					break;
    
    vas's avatar
    vas committed
    				default:
    
    vas's avatar
    vas committed
    					this.appendChild(new RangeEditor(V.currentRule.condition.function, V.currentRule.condition.data));
    					break;
    
    vas's avatar
    vas committed
    			}
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			return document.createElement("div");
    
    vas's avatar
    vas committed
    		}
    
    		getAttribute(what) {
    			return {
    				"Devotion": "devotion",
    				"Trust": "trust",
    				"Health": "health",
    				"Sex drive": "energy",
    				"weight": "weight",
    				"Age": "actualAge",
    				"Body Age": "physicalAge",
    				"Visible Age": "visualAge",
    				"Muscles": "muscles",
    				"Lactation": "lactation",
    				"Pregnancy": "preg",
    				"Pregnancy Multiples": "pregType",
    
    vas's avatar
    vas committed
    				"Belly Implant": "bellyImplant",
    
    vas's avatar
    vas committed
    				"Belly Size": "belly",
    
    vas's avatar
    vas committed
    			}[what];
    
    vas's avatar
    vas committed
    		}
    
    		fnchanged(value) {
    			if (this.fneditor !== null) {
    
    vas's avatar
    vas committed
    				this.fneditor.element.remove();
    				this.fneditor = null;
    
    vas's avatar
    vas committed
    			}
    			switch(value) {
    				case "Never":
    
    vas's avatar
    vas committed
    					V.currentRule.condition.function = false;
    					V.currentRule.condition.data = {};
    					break;
    
    vas's avatar
    vas committed
    				case "Always":
    
    vas's avatar
    vas committed
    					V.currentRule.condition.function = true;
    					V.currentRule.condition.data = {};
    					break;
    
    vas's avatar
    vas committed
    				case "Custom":
    
    vas's avatar
    vas committed
    					V.currentRule.condition.function = "custom";
    					V.currentRule.condition.data = {};
    					this.appendChild(new CustomEditor(V.currentRule.condition.data));
    					break;
    
    vas's avatar
    vas committed
    				default:
    
    vas's avatar
    vas committed
    					V.currentRule.condition.function = "between";
    					V.currentRule.condition.data = { attribute: value, value: [null, null] };
    					this.appendChild(new RangeEditor(V.currentRule.condition.data));
    					break;
    
    vas's avatar
    vas committed
    			}
    		}
    	}
    
    	class CustomEditor extends Element {
    		constructor(data) {
    
    vas's avatar
    vas committed
    			if (data.length === 0) data = "function(slave) { return slave.slaveName === 'Fancy Name'; }";
    			super(data);
    			this.elem.onfocusout = () => V.currentRule.data = this.elem.value;
    
    vas's avatar
    vas committed
    		}
    
    		render(data) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("textarea");
    			elem.setAttribute(value, data);
    			return elem;
    
    vas's avatar
    vas committed
    		}
    	}
    
    	class RangeEditor extends Element {
    
    vas's avatar
    vas committed
    		render(fn, data) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("div");
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			const min = document.createElement("input");
    			min.setAttribute("type", "text");
    
    vas's avatar
    vas committed
    			min.value = data.value[0];
    
    vas's avatar
    vas committed
    			min.onkeypress = e => onreturn(e, () => this.setmin(min.value));
    			min.onfocusout = e => this.setmin(min.value);
    			elem.appendChild(min);
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			const max = document.createElement("input");
    			max.setAttribute("type", "text");
    
    vas's avatar
    vas committed
    			max.value = data.value[0];
    
    vas's avatar
    vas committed
    			max.onkeypress = e => onreturn(e, () => this.setmax(max.value));
    			max.onfocusout = e => this.setmax(max.value);
    			elem.appendChild(max);
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			const infobar = document.createElement("div");
    			infobar.innerHTML = this.info(data.attribute);
    			elem.appendChild(infobar);
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			return elem;
    
    vas's avatar
    vas committed
    		}
    
    		parse(value) {
    
    vas's avatar
    vas committed
    			value = value.strip();
    			if (value === "null") value = null;
    
    vas's avatar
    vas committed
    			else {
    
    vas's avatar
    vas committed
    				value = parseInt(value);
    				if (isNan(value)) value = null;
    
    vas's avatar
    vas committed
    			}
    
    vas's avatar
    vas committed
    			return value;
    
    vas's avatar
    vas committed
    		}
    
    		setmin(value) {
    
    vas's avatar
    vas committed
    			V.currentRule.data.value[0] = this.parse(value);
    
    vas's avatar
    vas committed
    		}
    
    		setmax(value) {
    
    vas's avatar
    vas committed
    			V.currentRule.data.value[1] = this.parse(value);
    
    vas's avatar
    vas committed
    		}
    
    		info(attribute) {
    
    vas's avatar
    vas committed
    			return "TODO";
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class AssignmentInclusion extends ButtonList {
    
    vas's avatar
    vas committed
    		constructor() {
    
    vas's avatar
    vas committed
    			super("Apply to assignments");
    
    vas's avatar
    vas committed
    			["Rest", "Fucktoy", "Subordinate Slave", "House Servant", "Confined", "Whore", "Public Servant", "Classes", "Milked", "Gloryhole"].forEach(
    
    vas's avatar
    vas committed
    				i => this.appendChild(new ButtonItem(i, this.getAttribute(i), V.currentRule.condition.assignment.includes(i))));
    
    vas's avatar
    vas committed
    		}
    
    		onchange() {
    
    vas's avatar
    vas committed
    			V.currentRule.condition.assignment = this.getSelection();
    
    vas's avatar
    vas committed
    		}
    
    		getAttribute(what) {
    			return {
    				"Rest": "rest",
    				"Fucktoy": "please you",
    				"Subordinate Slave": "be a subordinate slave",
    				"House Servant": "be a servant",
    				"Confined": "stay confined",
    				"Whore": "whore",
    				"Public Servant": "serve the public",
    				"Classes": "take classes",
    				"Milked": "get milked",
    				"Gloryhole": "work a glory hole",
    
    vas's avatar
    vas committed
    			}[what];
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class FacilityInclusion extends ButtonList {
    
    vas's avatar
    vas committed
    		constructor() {
    
    vas's avatar
    vas committed
    			super("Apply to assignments");
    			const facilities = [];
    			if (V.HGSuite > 0) facilities.push("Head Girl Suite");
    			if (V.brothel > 0) facilities.push("Brothel");
    			if (V.club > 0) facilities.push("Club");
    			if (V.arcade > 0) facilities.push("Arcade");
    			if (V.dairy > 0) facilities.push("Dairy");
    			if (V.servantQuarters > 0) facilities.push("Servant Quarters");
    			if (V.masterSuite > 0) facilities.push("Master Suite");
    			if (V.schoolroom > 0) facilities.push("Schoolroom");
    			if (V.spa > 0) facilities.push("Spa");
    			if (V.clinic > 0) facilities.push("Clinic");
    			if (V.cellblock > 0) facilities.push("Cellblock");
    
    vas's avatar
    vas committed
    			facilities.forEach(
    
    vas's avatar
    vas committed
    				i => this.appendChild(new ButtonItem(i, this.getAttribute(i), V.currentRule.condition.facility.includes(i))));
    
    vas's avatar
    vas committed
    		}
    
    		onchange(value) {
    
    vas's avatar
    vas committed
    			V.currentRule.condition.facility = this.getSelection();
    
    vas's avatar
    vas committed
    		}
    
    		getAttribute(what) {
    			return {
    				"Head Girl Suite": "live with your Head Girl",
    				"Brothel": "work in the brothel",
    				"Club": "serve in the club",
    				"Arcade": "be confined in the arcade",
    				"Dairy": "work in the dairy",
    				"Servant Quarters": "work as a servant",
    				"Master Suite": "serve in the master suite",
    				"Schoolroom": "learn in the schoolroom",
    				"Spa": "rest in the spa",
    				"Clinic": "get treatment in the clinic",
    				"Cellblock": "be confined in the cellblock",
    
    vas's avatar
    vas committed
    			}[what];
    
    vas's avatar
    vas committed
    		}
    	}
    
    	class SpecialExclusion extends List {
    		constructor() {
    
    vas's avatar
    vas committed
    			const items = [
    				["Yes", true],
    				["No", false]
    
    vas's avatar
    vas committed
    			];
    			super("Exclude special slaves:", items);
    
    vas's avatar
    vas committed
    			this.setValue(V.currentRule.condition.excludeSpecialSlaves);
    			this.onchange = (value) => V.currentRule.condition.excludeSpecialSlaves = value;
    
    vas's avatar
    vas committed
    		}
    	}
    
    	class SpecificInclusionExclusion extends Options {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new OptionsItem("Limit to specific slaves", () => Engine.display("Rules Slave Select")));
    
    vas's avatar
    vas committed
    			this.appendChild(new OptionsItem("Exclude specific slaves", () => Engine.display("Rules Slave Exclude")));
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// parent section for effect editing
    	class EffectEditor extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new AppearanceSection());
    			this.appendChild(new CosmeticSection());
    			this.appendChild(new BodyModSection());
    			this.appendChild(new AutosurgerySection());
    			this.appendChild(new RegimenSection());
    			this.appendChild(new BehaviourSection());
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			const element = document.createElement("div");
    			return element;
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    	class AppearanceSection extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new ClothesList());
    			this.appendChild(new CollarList());
    			this.appendChild(new ShoeList());
    			this.appendChild(new CorsetList());
    			this.appendChild(new VagAccVirginsList());
    			this.appendChild(new VagAccAVirginsList());
    			this.appendChild(new VagAccOtherList());
    
    vas's avatar
    vas committed
    			if (V.seeDicks !== 0 || V.makeDicks !== 0) {
    
    vas's avatar
    vas committed
    				this.appendChild(new DickAccVirginsList());
    				this.appendChild(new DickAccOtherList());
    
    vas's avatar
    vas committed
    			}
    
    vas's avatar
    vas committed
    			this.appendChild(new ButtplugsVirginsList());
    			this.appendChild(new ButtplugsOtherList());
    			this.appendChild(new ImplantVolumeList());
    			this.appendChild(new AutosurgerySwitch());
    
    vas's avatar
    vas committed
    			return document.createElement("div");
    
    vas's avatar
    vas committed
    	class RegimenSection extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new GrowthList());
    			this.appendChild(new CurrativesList());
    			this.appendChild(new AphrodisiacList());
    			this.appendChild(new ContraceptiveList());
    
    vas's avatar
    vas committed
    			if (V.pregSpeedControl)
    
    vas's avatar
    vas committed
    				this.appendChild(new PregDrugsList());
    			this.appendChild(new FemaleHormonesList());
    			this.appendChild(new ShemaleHormonesList());
    			this.appendChild(new GeldingHormonesList());
    			this.appendChild(new OtherDrugsList());
    			this.appendChild(new DietList());
    			this.appendChild(new DietGrowthList());
    			this.appendChild(new DietBaseList());
    			this.appendChild(new MuscleList());
    			this.appendChild(new BraceList());
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			return document.createElement("div");
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class BehaviourSection extends Element {
    
    vas's avatar
    vas committed
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new LivingStandardList());
    			this.appendChild(new PunishmentList());
    			this.appendChild(new RewardList());
    			this.appendChild(new ReleaseList());
    			this.appendChild(new SmartFetishList());
    			this.appendChild(new SmartXYAttractionList());
    			this.appendChild(new SmartXXAttractionList());
    			this.appendChild(new SmartEnergyList());
    			this.appendChild(new SpeechList());
    			this.appendChild(new RelationshipList());
    
    vas's avatar
    vas committed
    			if (V.studio === 1)
    
    vas's avatar
    vas committed
    				this.appendChild(new PornList());
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    
    vas's avatar
    vas committed
    			return document.createElement("div");
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class CosmeticSection extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new EyewearList());
    			this.appendChild(new LensesList());
    			this.appendChild(new MakeupList());
    			this.appendChild(new NailsList());
    			this.appendChild(new HairLengthList());
    			this.appendChild(new HairColourList());
    			this.appendChild(new HairStyleList());
    			this.appendChild(new PubicHairColourList());
    			this.appendChild(new PubicHairStyleList());
    			this.appendChild(new ArmpitHairColourList());
    			this.appendChild(new ArmpitHairStyleList());
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    		render() { return document.createElement("div"); }
    
    vas's avatar
    vas committed
    	}
    
    	class BodyModSection extends Element {
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new EarPiercingList());
    			this.appendChild(new NosePiercingList());
    			this.appendChild(new EyebrowPiercingList());
    			this.appendChild(new NavelPiercingList());
    			this.appendChild(new NipplePiercingList());
    			this.appendChild(new AreolaPiercingList());
    			this.appendChild(new LipPiercingList());
    			this.appendChild(new TonguePiercingList());
    			this.appendChild(new ClitPiercingList());
    			this.appendChild(new LabiaPiercingList());
    			this.appendChild(new ShaftPiercingList());
    			this.appendChild(new PerineumPiercingList());
    			this.appendChild(new CorsetPiercingList());
    
    			this.appendChild(new AutoBrandingList());
    			this.appendChild(new BrandingLocationList());
    			this.appendChild(new BrandDesignList());
    
    			this.appendChild(new FaceTattooList());
    			this.appendChild(new ShoulderTattooList());
    			this.appendChild(new ChestTattooList());
    			this.appendChild(new ArmTattooList());
    			this.appendChild(new UpperBackTattooList());
    			this.appendChild(new LowerBackTattooList());
    			this.appendChild(new AbdomenTattooList());
    
    vas's avatar
    vas committed
    			if (V.seeDicks || V.makeDicks)
    
    vas's avatar
    vas committed
    				this.appendChild(new DickTattooList());
    			this.appendChild(new ButtockTattooList());
    			this.appendChild(new AnalTattooList());
    			this.appendChild(new LegTattooList());
    
    vas's avatar
    vas committed
    		}
    
    vas's avatar
    vas committed
    
    		render() { return document.createElement("div"); }
    
    vas's avatar
    vas committed
    	}
    
    
    vas's avatar
    vas committed
    	class AutosurgerySection extends Element {
    
    vas's avatar
    vas committed
    		constructor() {
    
    vas's avatar
    vas committed
    			super();
    			this.appendChild(new VisionSurgeryList());
    			this.appendChild(new LactationSurgeryList());
    
    vas's avatar
    vas committed
    			if (V.seeDicks || V.makeDicks)
    
    vas's avatar
    vas committed
    				this.appendChild(new SemenSurgeryList());
    			this.appendChild(new CosmeticSurgeryList());
    			this.appendChild(new LipSurgeryList());
    			this.appendChild(new ButtSurgeryList());
    			this.appendChild(new BreastSurgeryList());
    			this.appendChild(new TighteningSurgeryList());
    			this.appendChild(new BodyHairSurgeryList());
    			this.appendChild(new HairSurgeryList());
    
    vas's avatar
    vas committed
    		}
    
    		render() { return document.createElement("div"); }
    
    vas's avatar
    vas committed
    	}
    
    
    vas's avatar
    vas committed
    	class ClothesList extends List {
    		constructor() {
    			const items = [
    				["Select her own outfit", "choosing her own clothes"]
    			];
    			super("Clothes", items);
    
    			const nclothes = [
    				["No default clothes setting", "no default setting"],
    				["Bangles", "slutty jewelry"],
    				["Bodysuit", "a comfortable bodysuit"],
    				["Cheerleader outfit", "a cheerleader outfit"],
    				["Clubslut netting", "clubslut netting"],
    				["Cutoffs and a t-shirt", "cutoffs and a t-shirt"],
    				["Fallen nun", "a fallen nuns habit"],
    				["Halter top", "a halter top dress"],
    				["Hijab and abaya", "a hijab and abaya"],
    				["Latex catsuit", "a latex catsuit"],
    				["Leotard", "a leotard"],
    				["Maid (nice)", "a nice maid outfit"],
    				["Maid (slutty)", "a slutty maid outfit"],
    				["Military uniform", "a military uniform"],
    				["Mini dress", "a mini dress"],
    				["Nice lingerie", "attractive lingerie"],
    				["Nurse (nice)", "a nice nurse outfit"],
    				["Schoolgirl", "a schoolgirl outfit"],
    				["Silken ballgown", "a ball gown"],
    				["Skimpy battledress", "battledress"],
    				["Slave gown", "a slave gown"],
    				["Slutty outfit", "a slutty outfit"],
    				["String bikini", "a stirng bikini"],