Skip to content
Snippets Groups Projects
rulesAssistantOptions.tw 60.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • vas's avatar
    vas committed
    :: Rules Assistant Options [script]
    
    // 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() {
    	"use strict"
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    	let V
    	let r = ""
    
    
    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"
    
    vas's avatar
    vas committed
    		const root = new Root(element)
    
    vas's avatar
    vas committed
    	}
    
    
    vas's avatar
    vas committed
    	function onreturn(e, cb) {
    		if (e.keyCode === 13) cb()
    	}
    
    	// 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",
    				surgery_holes: "not default setting",
    				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)
    	}
    
    	function removeRule(root) {
    		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
    
    vas's avatar
    vas committed
    		reload(root)
    	}
    
    
    vas's avatar
    vas committed
    	function lowerPriority(root) {
    		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)
    	}
    
    	function higherPriority(root) {
    		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)
    	}
    
    	function changeName(name, root) {
    		if (name === V.currentRule.name) return
    		V.currentRule = name
    		reload(root)
    	}
    
    	// reload the passage
    
    vas's avatar
    vas committed
    	function reload(root) {
    		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) {
    			this.parent = null
    			this.element = this.render(...args)
    			this.children = []
    		}
    
    		appendChild(child) {
    			child.parent = this
    			this.children.push(child)
    			this.element.appendChild(child.element)
    		}
    
    		// return the first argument to simplify creation of basic container items
    		render(...args) {
    			return args[0]
    		}
    	}
    
    	// 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 {
    		constructor(prefix, textinput=false) {
    
    vas's avatar
    vas committed
    			super(prefix, textinput)
    
    vas's avatar
    vas committed
    			this.selectedItem = null
    			this.value = this.element.querySelector(".rajs-value")
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		render(prefix, textinput) {
    			const elem = document.createElement("div")
    
    vas's avatar
    vas committed
    			const label = document.createElement("span")
    			label.innerHTML = prefix
    
    vas's avatar
    vas committed
    			let value
    			if (textinput) {
    				value = document.createElement("input")
    				value.classList.add("rajs-value")
    				// 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 {
    				value = document.createElement("strong")
    			}
    			value.setAttribute("type", "text")
    
    vas's avatar
    vas committed
    			elem.appendChild(label)
    
    vas's avatar
    vas committed
    			elem.appendChild(value)
    			elem.classList.add("rajs-list")
    			return elem
    		}
    
    vas's avatar
    vas committed
    
    		inputEdited() {
    			if (this.selectedItem) this.selectedItem.deselect()
    			this.propagateChange()
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		selectItem(item) {
    			if (this.selectedItem) this.selectedItem.deselect()
    			this.selectedItem = item
    			setValue(item.setValue)
    			this.propagateChange()
    		}
    
    
    vas's avatar
    vas committed
    		selectValue(what) {
    			this.children.some(child => {
    				if (child.data === what) {
    					child.select()
    					return true
    				}
    				else return false
    			})
    		}
    
    
    vas's avatar
    vas committed
    		setValue(what) {
    			if (this.value.tagName === "input")
    				this.value.value = what
    			else
    				this.value.innerHTML = what
    		}
    
    		getValue(what) {
    			return (this.value.tagName === "input" ? this.parse(this.value.value) : this.value.innerHTML)
    		}
    
    		// customisable input field parser / sanity checker
    		parse(what) { return what }
    
    		propagateChange() {
    			if (this.onchange instanceof Function)
    
    vas's avatar
    vas committed
    				this.onchange(this.getValue(), this.selectedItem.data)
    
    vas's avatar
    vas committed
    		}
    	}
    
    
    vas's avatar
    vas committed
    	const parse = {
    		integer(string) {
    			let n = parseInt(string, 10)
    			return n === NaN? 0: n
    		},
    		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
    	// a clickable item of a list
    	class ListItem extends Element {
    
    vas's avatar
    vas committed
    		constructor(displayvalue, setvalue, data) {
    
    vas's avatar
    vas committed
    			super(displayvalue)
    
    vas's avatar
    vas committed
    			this.setvalue = setvalue !== undefined ? setvalue : displayvalue
    			this.data = data !== undefined ? data : this.setvalue !== undefined ? this.setvalue : displayvalue
    
    vas's avatar
    vas committed
    			this.selected = false
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		render(displayvalue) {
    			const elem = document.createElement("span")
    			elem.classList.add("rajs-listitem")
    			elem.innerHTML = displayvalue
    			elem.onclick = () => { return this.select() }
    			return elem
    		}
    
    		select() {
    			if (this.selected) return false
    			this.parent.selectItem(this)
    			this.elem.classList.add("selected")
    			this.selected = true
    
    vas's avatar
    vas committed
    			return true
    
    vas's avatar
    vas committed
    		}
    
    		deselect() {
    			this.elem.classList.remove("selected")
    			this.selected = false
    		}
    	}
    
    	// a way to organise lists with too many elements in subsections
    	// children are bound to the master list
    	class ListSubSection extends Element {
    		render(label) {
    
    vas's avatar
    vas committed
    			const elem = document.createElement("em")
    
    vas's avatar
    vas committed
    			elem.innerText = label + ":"
    			return elem
    		}
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    		appendChild(child) {
    			super.appendChild(child)
    			child.parent = this.parent
    
    vas's avatar
    vas committed
    			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=[]) {
    			elements.forEach(element => { this.appendChild(element) })
    		}
    
    		render() {
    			const elem = document.createElement("div")
    			elem.classList.add("rajs-list")
    			return elem
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// options equivalent of ListItem
    
    vas's avatar
    vas committed
    	class OptionsItem extends Element {
    		constructor(label, onclick) {
    			super(label)
    
    vas's avatar
    vas committed
    			this.label = label
    
    vas's avatar
    vas committed
    			this.onclick = onclick
    		}
    		render(label, onclick) {
    			const elem = document.createElement("span")
    			elem.classList.add("rajs-listitem")
    			elem.innerHTML = label
    
    vas's avatar
    vas committed
    			elem.onclick = () => { return this.onclick(this) }
    
    vas's avatar
    vas committed
    			return elem
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ButtonList extends Element {
    		render(label) {
    			const elem = document.createElement("div")
    			const labelel = document.createElement("span")
    			labelel.innerhTML = label += ":"
    			elem.appendChild(labelel)
    			return elem
    		}
    
    		getSelection() {
    			return (this.children
    				.filter(child => child.selected)
    				.map(child => child.setvalue)
    			)
    		}
    
    		onchange() { return }
    	}
    
    	class ButtonItem extends Element {
    		constructor(label, setvalue, selected=false) {
    			super(label, selected)
    			this.selected = selected
    			this.setvalue = setvalue ? setvalue : label
    		}
    
    		render(label, selected) {
    			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
    
    			const button = document.createElement("input")
    			button.setAttribute("type", "checkbox")
    			button.checked = selected
    			button.onchange = () => this.onchange(button.checked)
    
    			container.appendChild(labelel)
    			container.appendChild(button)
    
    			return container
    		}
    
    		onchange(value) {
    			this.selected = value
    			parent.onchange(this)
    		}
    	}
    
    
    vas's avatar
    vas committed
    	// rule import field
    
    vas's avatar
    vas committed
    	class NewRuleField extends Element {
    		constructor(root) {
    			super()
    			this.root = root
    		}
    
    		render() {
    			const container = document.createElement("div")
    			const textarea = document.createElmenet("textarea")
    			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
    		}
    
    		loadNewRule() {
    			const text = this.textarea.value
    			try {
    				const rule = JSON.parse(text)
    
    vas's avatar
    vas committed
    				if (!rule.ID) rule.ID = generateNewID()
    
    vas's avatar
    vas committed
    				reload(this.root)
    			} catch (e) {
    				alert(e)
    			}
    		}
    	}
    
    
    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() {
    			super()
    
    vas's avatar
    vas committed
    			if(V.defaultRules.length === 0) {
    				const paragraph = document.createElement("p")
    				paragraph.innerHTML = "<strong>No rules</strong>"
    
    vas's avatar
    vas committed
    				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))
    		}
    
    		render(element) {
    			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) {
    			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
    	// buttons for selecting the current rule
    	class RuleSelector extends List {
    		constructor(root) {
    			super("Current rule:")
    			if (!V.currentRule) V.currentRule = V.defaultRules[0]
    			V.defaultRules.forEach(rule => {
    				const item = new ListItem(rule.name, rule.name, rule)
    				this.appendChild(item)
    				if (rule.ID === V.currentRule.ID) item.select()
    			})
    			this.onchange = function (rulename, rule) {
    				V.currentRule = rule
    				reload(root)
    			}
    		}
    	}
    
    	// buttons for doing transformations on rules
    	class RuleOptions extends Options {
    		constructor(root) {
    			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())))
    		}
    	}
    
    	class ApplicationLog extends Element {
    		render() {
    			const elem = document.createElement("div")
    			elem.innerHTML = DefaultRules()
    			return elem
    		}
    	}
    
    	class RenameField extends Element {
    		constructor(root) {
    			super()
    			this.element.onfocusout = () => changeName(this.element.value, root)
    			this.element.onkeypress = (e) => onreturn(e, () => changeName(this.element.value, root))
    		}
    
    		render() {
    			const elem = document.createElement("div")
    			elem.setAttribute("type", "text")
    			elem.setAttribute("value", V.currentRule.name)
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ExportField extends Element {
    		render(all=false) {
    			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
    	// parent section for condition editing
    	class ConditionEditor extends Element {
    		constructor() {
    			super()
    			this.appendChild(new ConditionFunction())
    			this.appendChild(new AssignmentInclusion())
    			this.appendChild(new FacilityInclusion())
    			this.appendChild(new SpecialExclusion())
    			this.appendChild(new SpecificInclusionExclusion())
    		}
    
    		render() {
    			const element = document.createElement("div")
    			return element
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ConditionFunction extends Element {
    		constructor() {
    			super()
    			this.fnlist = new List("Activation function:")
    			this.fneditor = null
    			["Never", "Always", "Custom"].forEach(i => this.fnlist.appendChild(i))
    			["Devotion", "Trust", "Health", "Sex drive", "Weight", "Age", "Body Age", "Visible Age", "Muscles", "Lactation", "Pregnancy", "Pregnancy Multiples", "Belly Implant", "Belly Size"].forEach(i => this.fnlist.appendChild(i, this.getAttribute(i)))
    			this.fnlist.onchange = () => this.fnchanged
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			switch(V.currentRule.condition.function) {
    				case "Never":
    				case "Always":
    					break
    				case "Custom":
    					this.appendChild(new CustomEditor(V.currentRule.condition.data))
    					break
    				default:
    					this.appendChild(new RangeEditor(V.currentRule.condition.function, V.currentRule.condition.data))
    					break
    			}
    		}
    
    		render() {
    			return document.createElement("div")
    		}
    
    		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",
    				"Belly implant": "bellyImplant",
    				"Belly Size": "belly",
    			}[what]
    		}
    
    		fnchanged(value) {
    			if (this.fneditor !== null) {
    				this.fneditor.element.remove()
    				this.fneditor = null
    			}
    			switch(value) {
    				case "Never":
    					V.currentRule.condition.function = false
    					V.currentRule.condition.data = {}
    					break
    				case "Always":
    					V.currentRule.condition.function = true
    					V.currentRule.condition.data = {}
    					break
    				case "Custom":
    					V.currentRule.condition.function = "custom"
    					V.currentRule.condition.data = {}
    					this.appendChild(new CustomEditor(V.currentRule.condition.data))
    					break
    				default:
    					V.currentRule.condition.function = "between"
    					V.currentRule.condition.data = { attribute: value, value: [null, null] }
    					this.appendChild(new RangeEditor(V.currentRule.condition.data))
    					break
    			}
    		}
    	}
    
    	class CustomEditor extends Element {
    		constructor(data) {
    			if (data.length === 0) data = "function(slave) { return slave.slaveName === 'Fancy Name'; }"
    			super(data)
    			this.elem.onfocusout = () => V.currentRule.data = this.elem.value
    		}
    
    		render(data) {
    			const elem = document.createElement("textarea")
    			elem.setAttribute(value, data)
    			return elem
    		}
    	}
    
    	class RangeEditor extends Element {
    		render(data) {
    			const elem = document.createElement("div")
    
    			const min = document.createElement("input")
    			min.setAttribute("type", "text")
    			min.value = data.between[0]
    			min.onkeypress = e => onreturn(e, () => this.setmin(min.value))
    			min.onfocusout = e => this.setmin(min.value)
    			elem.appendChild(min)
    
    			const max = document.createElement("input")
    			max.setAttribute("type", "text")
    			max.value = data.between[0]
    			max.onkeypress = e => onreturn(e, () => this.setmax(max.value))
    			max.onfocusout = e => this.setmax(max.value)
    			elem.appendChild(max)
    
    			const infobar = document.createElement("div")
    			infobar.innerHTML = this.info(data.attribute)
    			elem.appendChild(infobar)
    
    			return elem
    		}
    
    		parse(value) {
    			value = value.strip()
    			if (value === "null") value = null
    			else {
    				value = parseInt(value)
    				if (value === NaN) value = null
    			}
    			return value
    		}
    
    		setmin(value) {
    			V.currentRule.data.between[0] = this.parse(value)
    		}
    
    		setmax(value) {
    			V.currentRule.data.between[1] = this.parse(value)
    		}
    
    		info(attribute) {
    			return "TODO"
    		}
    	}
    
    	class AssignmentInclusion extends ButtonList() {
    		constructor() {
    			super("Apply to assignments")
    			["Rest", "Fucktoy", "Subordinate Slave", "House Servant", "Confined", "Whore", "Public Servant", "Classes", "Milked", "Gloryhole"].forEach(
    				i => this.appendChild(new ButtonItem(i, this.getAttribute(i), V.currentRule.condition.assignment.includes(i))))
    		}
    
    		onchange() {
    			V.currentRule.condition.assignment = this.getSelection()
    		}
    
    		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",
    			}[what]
    		}
    	}
    
    	class FacilityInclusion extends ButtonList() {
    		constructor() {
    			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")
    			facilities.forEach(
    				i => this.appendChild(new ButtonItem(i, this.getAttribute(i), V.currentRule.condition.facility.includes(i))))
    		}
    
    		onchange(value) {
    			V.currentRule.condition.facility = this.getSelection()
    		}
    
    		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",
    			}[what]
    		}
    	}
    
    	class SpecialExclusion extends List {
    		constructor() {
    			super("Exclude special slaves:")
    			const yes = new ListItem("Yes", "Yes", true)
    			const no = new ListItem("No", "No", false)
    			this.appendChild(yes)
    			this.appendChild(no)
    			if (V.currentRule.excludeSpecialSlaves) yes.select()
    			this.onchange = (label, value) => V.currentRule.excludeSpecialSlaves = value
    		}
    	}
    
    	class SpecificInclusionExclusion extends Options {
    		constructor() {
    			super()
    			this.appendChild(new OptionsItem("Limit to specific slaves", () => Engine.display("Rules Slave Select")))
    			this.appendChild(new OptionsItem("Exclude specific slaveS", () => Engine.display("Rules Slave Exclude")))
    		}
    	}
    
    
    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() {
    			const element = document.createElement("div")
    			return element
    		}
    	}
    
    
    	class AppearanceSection extends Element {
    		constructor() {
    			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) {
    				this.appendChild(new DickAccVirginsList())
    				this.appendChild(new DickAccOtherList())
    			}
    
    			this.appendChild(new ButtplugsVirginsList())
    			this.appendChild(new ButtplugsOtherList())
    			this.appendChild(new ImplantVolumeList())
    			this.appendChild(new AutosurgerySwitch())
    
    		}
    
    		render() {
    			return document.createElement("div")
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class RegimenSection extends Element {
    		constructor() {
    			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)
    				this.appendChild(new PregDrugsList())
    
    vas's avatar
    vas committed
    			this.appendChild(new FemaleHormonesList())
    			this.appendChild(new ShemaleHormonesList())
    			this.appendChild(new GeldingHormonesList())
    			this.appendChild(new OtherDrugsList())
    			this.appendChild(new DietList())
    
    vas's avatar
    vas committed
    			this.appendChild(new DietGrowthList())
    
    vas's avatar
    vas committed
    			this.appendChild(new DietBaseList())
    			this.appendChild(new MuscleList())
    			this.appendChild(new BraceList())
    		}
    
    		render() {
    			return document.createElement("div")
    		}
    	}
    
    	class BehaviourSection extends List {
    		constructor() {
    			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)
    				this.appendChild(new PornList())
    
    vas's avatar
    vas committed
    		}
    
    		render() {
    			return document.createElement("div")
    		}
    	}
    
    
    vas's avatar
    vas committed
    	class ClothesList extends List {
    		constructor() {
    			super("Clothes")
    			this.appendChild(new ListItem("Select her own outfit", "choosing her own clothes"))
    
    			const nice = new ListSubSection()
    			this.appendChild(nice)
    
    			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"],
    				["Scalemail bikini", "a scalemail bikini"],
    				["Succubus costume", "a succubus outfit"],
    				["Suit (nice)", "nice business sattire"],
    				["Suit (slutty", "slutty business attire"],
    				["Spats and tank top", "spats and a tank top"]
    			]
    			const fsnclothes = [
    				["Body oil (FS)", "body oil"],
    				["Bunny outfit (FS)", "a bunny outfit"],
    				["Chattel habit (FS)", "a chattel habit"],
    				["Conservative clothing (FS)", "conservative clothing"],
    				["Harem gauze (FS)", "harem gauze"],
    				["Huipil (FS)", "a huipil"],
    				["Kimono (FS)", "a kimono"],
    				["Maternity dress (FS)", "a maternity dress"],
    				["Maternity lingerie (FS)", "attractive lingerie for a pregnant woman"],
    				["Slutty qipao (FS)", "a slutty qipao"],
    				["Stretch pants and a crop-top (FS)", "stretch pants and a crop-top"],
    				["Toga (FS)", "a toga"],
    				["Western clothing (FS)", "Western clothing"],
    			]
    			nclothes.forEach(pair => nice.appendChild(new ListItem(...pair)))
    			fsnclothes.forEach(pair => { if (isItemAccessible(pair[1])) nice.appendChild(new ListItem(...pair))})
    
    vas's avatar
    vas committed
    
    
    vas's avatar
    vas committed
    			harsh = new ListSubSection()
    			this.appendChild(harsh)
    
    			const hclothes = [
    				["Nude", "no clothing"]
    				["Penitent nun", "a penitent nuns habit"],
    				["Restrictive latex", "restrictive latex"],
    				["Shibari ropes", "shibari ropes"],
    				["Uncomfortable straps", "uncomfortable straps"]
    			]
    			const fshclothes = [
    				["Chains (FS)", "chains"],
    			]
    
    			hclothes.forEach(pair => nice.appendChild(new ListItem(...pair)))
    			fshclothes.forEach(pair => { if (isItemAccessible(pair[1])) nice.appendChild(new ListItem(...pair))})
    
    			this.selectValue(V.currentRule.set.clothes)
    			this.onchange = (label, value) => V.currentRule.set.clothes = value
    		}
    	}
    
    	class CollarList extends List {
    		constructor() {
    			super("Collar")
    			this.appendChild("No default collar setting", "no default setting")
    			this.appendChild("No collar", "none")
    
    			const nice = new ListSubSection("Nice")
    			this.appendChild(nice)
    
    			const ncollars = [
    				["Stylish leather", "stylish leather"],
    				["Satin choker", "satin choker"],
    				["Silken Ribbon", "silk ribbon"],
    				["Heavy Gold", "heavy gold"],
    				["Pretty jewelry", "pretty jewelry"],
    				["Cowbell", "leather with cowbell"]
    			]
    			if (V.seeAge !== 0)
    				ncollars.push(["Nice retirement counter", "nice retirement counter"])
    			const fsncollars = [
    				["Bowtie collar", "bowtie"],
    				["ancient Egyptian", "ancient Egyptian"],
    			]
    			ncollars.forEach(pair => nice.appendChild(new ListItem(...pair)))
    			fsncollars.forEach(pair => { if (isItemAccessible(pair[1])) nice.appendChild(new ListItem(...pair))})
    
    			const harsh = new ListSubSection("Harsh")
    			this.appendChild(harsh)
    
    			const hcollars = [
    				["Tight steel", "tight steel"],
    				["Uncomfortable leather", "uncomfortable leather"],
    				["Pregnancy biometrics", "preg biometrics"],
    				["Shock punishment", "shock punishment"],
    				["Dildo gag", "dildo gag"],