Newer
Older
:: Rules Assistant Options [script]
// rewrite of the rules assistant options page in javascript
// uses an object-oriented widget pattern
// 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
V = State.variables;
V.nextButton = "Back to Main";
V.nextLink = "Main";
V.returnTo = "Main";
V.showEncyclopedia = 1;
V.encyclopedia = "Personal Assistant";
if (V.currentRule !== null) {
const idx = V.defaultRules.findIndex(rule => rule.ID === V.currentRule);
if (idx === -1)
current_rule = V.defaultRules[0];
else
current_rule = V.defaultRules[idx];
}
const rule = emptyDefaultRule();
V.defaultRules.push(rule);
const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
if (V.defaultRules.length > 0) {
const new_idx = idx < V.defaultRules.length ? idx : V.defaultRules.length - 1;
V.currentRule = V.defaultRules[new_idx].ID;
} else V.currentRule = null;
const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
if (idx === 0) return; // no lower rule
arraySwap(V.defaultRules, idx, idx-1);
reload(root);
const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
if (idx === V.defaultRules.length - 1) return; // no higher rule
arraySwap(V.defaultRules, idx, idx+1);
reload(root);
if (name === current_rule.name) return;
current_rule.name = name;
const parse = {
integer(string) {
let n = parseInt(string, 10);
return isNaN(n)? 0: n;
},
boobs(string) {
return Math.clamp(parse.integer(string), 0, 48000);
},
butt(string) {
},
lips(string) {
return Math.clamp(parse.integer(string), 0, 100);
},
dick(string) {
// 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 = [];
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) {
remove() {
const idx = this.parent.children.findIndex(child => child === this);
this.parent.children.slice(idx, 1);
this.element.remove();
}
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class Section extends Element {
constructor(header, hidden=false) {
super(header);
this.hidey = this.element.querySelector("div");
if (hidden) this.toggle_hidey();
}
render(header) {
const section = document.createElement("section");
section.classList.add("rajs-section");
const h1 = document.createElement("h1");
h1.onclick = () => { this.toggle_hidey(); };
h1.innerHTML = header;
const hidey = document.createElement("div");
section.appendChild(h1);
section.appendChild(hidey);
return section;
}
appendChild(child) {
child.parent = this;
this.children.push(child);
this.hidey.appendChild(child.element);
}
toggle_hidey() {
switch(this.hidey.style.display) {
case "none":
this.hidey.style.display = "initial";
break;
default:
this.hidey.style.display = "none";
break;
}
}
}
// 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 {
const elem = document.createElement("div");
const label = document.createElement("span");
label.innerHTML = prefix;
let value;
// call the variable binding when the input field is no longer being edited, and when the enter key is pressed
value.onblur = () => {this.inputEdited(); };
value.onkeypress = (e) => { if (returnP(e)) this.inputEdited(); };
elem.appendChild(label);
elem.appendChild(value);
elem.classList.add("rajs-list");
return elem;
if (this.selectedItem) this.selectedItem.deselect();
this.propagateChange();
if (this.selectedItem) this.selectedItem.deselect();
this.selectedItem = item;
return (this.value.tagName === "INPUT" ? this.parse(this.value.value): this.selectedItem.data);
propagateChange() {
if (this.onchange instanceof Function)
}
}
// a clickable item of a list
class ListItem extends Element {
super(displayvalue);
this.data = data !== undefined ? data: displayvalue;
this.selected = false;
const elem = document.createElement("span");
elem.classList.add("rajs-listitem");
elem.innerHTML = displayvalue;
elem.onclick = () => { return this.select(); };
return elem;
}
}
// a way to organise lists with too many elements in subsections
// children are bound to the master list
class ListSubSection extends Element {
const elem = document.createElement("div");
const lelem = document.createElement("em");
lelem.innerText = label + ": ";
elem.appendChild(lelem);
super.appendChild(child);
child.parent = this.parent;
this.parent.children.push(child);
const elem = document.createElement("div");
elem.classList.add("rajs-list");
return elem;
class OptionsItem extends Element {
constructor(label, onclick) {
const elem = document.createElement("span");
elem.classList.add("rajs-listitem");
elem.innerHTML = label;
elem.onclick = () => { return this.onclick(this); };
return elem;
const elem = document.createElement("div");
const labelel = document.createElement("span");
}
getSelection() {
return (this.children
.filter(child => child.selected)
.map(child => child.setvalue)
}
class ButtonItem extends Element {
constructor(label, setvalue, selected=false) {
super(label, selected);
this.selected = selected;
this.setvalue = setvalue ? setvalue : label;
const container = document.createElement("div");
container.classList.add("rajs-listitem");
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);
let element = document.getElementById("importfield");
if (element !== null) { return element; }
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;
const paragraph = document.createElement("p");
paragraph.innerHTML = "<strong>No rules</strong>";
this.appendChild(new Element(paragraph));
this.appendChild(new NoRules(this));
return;
this.appendChild(new RuleSelector(this));
this.appendChild(new RuleOptions(this));
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>`;
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.root)); });
// buttons for selecting the current rule
class RuleSelector extends List {
constructor(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 Priority", () => 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(current_rule))));
this.appendChild(new OptionsItem("Export all rules", () => this.appendChild(new ExportField(...V.defaultRules))));
this.appendChild(new OptionsItem("Import rule(s)", () => this.appendChild(new NewRuleField(root))));
const elem = document.querySelector("#application-log") || document.createElement("div");
elem.id = "application-log";
}
}
class RenameField extends Element {
constructor(root) {
this.element.onblur = () => changeName(this.element.value, root);
this.element.onkeypress = (e) => { if (returnP(e)) changeName(this.element.value, root); };
elem.setAttribute("value", current_rule.name);
return elem;
render(...args) {
let element = document.getElementById("exportfield");
if (element === null) {
this.appendChild(new ConditionFunction());
this.appendChild(new AssignmentInclusion());
this.appendChild(new SpecialExclusion());
const items = [
["Never", false],
["Always", true],
["Custom", "custom"],
["Devotion", "devotion"],
["Trust", "trust"],
["Health", "health"],
["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"],
["Education", "intelligenceImplant"],
["Intelligence", "intelligence"],
["Fetish", "fetish"],
];
this.fnlist = new List("Activation function", items);
this.fnlist.setValue(current_rule.condition.function === "between" ? current_rule.condition.data.attribute : current_rule.condition.function);
switch(current_rule.condition.function) {
case false:
case true:
this.show_custom_editor(CustomEditor, current_rule.condition.data);
this.show_custom_editor(RangeEditor, current_rule.condition.function, current_rule.condition.data);
case "belongs":
this.show_custom_editor(ItemEditor, current_rule.condition.function, current_rule.condition.data);
break;
betweenP(attribute) {
return [
"devotion",
"trust",
"health",
"energy",
"weight",
"actualAge",
"physicalAge",
"visualAge",
"muscles",
"lactation",
"preg",
"pregType",
"bellyImplant",
"belly",
"intelligenceImplant",
"intelligence",
].includes(attribute);
}
belongsP(attribute) {
return [
"fetish",
show_custom_editor(what, ...args) {
if (this.custom_editor !== null) this.hide_custom_editor();
this.custom_editor = new what(...args);
this.appendChild(this.custom_editor);
}
hide_custom_editor() {
if (this.custom_editor) {
this.custom_editor.remove();
this.custom_editor = null;
}
}
const elem = document.createElement("div");
return elem;
if (value === true || value === false) {
current_rule.condition.function = value;
current_rule.condition.data = {};
this.hide_custom_editor();
} else if (value === "custom") {
current_rule.condition.function = "custom";
current_rule.condition.data = "";
this.show_custom_editor(CustomEditor, current_rule.condition.data);
} else if (this.betweenP(value)) {
current_rule.condition.function = "between";
current_rule.condition.data = { attribute: value, value: [null, null] };
this.show_custom_editor(RangeEditor, current_rule.condition.function, current_rule.condition.data);
} else if (this.belongsP(value)) {
current_rule.condition.function = "belongs";
current_rule.condition.data = { attribute: value, value: [] };
this.show_custom_editor(ItemEditor, current_rule.condition.function, current_rule.condition.data);
}
}
}
class CustomEditor extends Element {
constructor(data) {
if (data.length === 0) data = "(slave) => slave.slaveName === 'Fancy Name'";
const elem = document.createElement("div");
const textarea = document.createElement("textarea");
textarea.innerHTML = data;
elem.appendChild(textarea);
const explanation = document.createElement("div");
explanation.innerHTML = "Insert a valid <a target='_blank' class='link-external' href='https://www.w3schools.com/js/js_comparisons.asp'>JavaScript comparison and/or logical operation</a>.";
elem.appendChild(explanation);
const minlabel = document.createElement("label");
minlabel.innerHTML = "Lower bound: ";
elem.appendChild(minlabel);
const min = document.createElement("input");
min.setAttribute("type", "text");
min.onkeypress = e => { if (returnP(e)) this.setmin(min.value); };
min.onblur = e => this.setmin(min.value);
elem.appendChild(document.createElement("br"));
const maxlabel = document.createElement("label");
maxlabel.innerHTML = "Upper bound: ";
elem.appendChild(maxlabel);
const max = document.createElement("input");
max.setAttribute("type", "text");
max.onkeypress = e => { if (returnP(e)) this.setmax(max.value); };
max.onblur = e => this.setmax(max.value);
const infobar = document.createElement("div");
infobar.innerHTML = this.info(data.attribute);
elem.appendChild(infobar);
current_rule.condition.data.value[0] = this.parse(value);
this.min.value = ""+current_rule.condition.data.value[0];
current_rule.condition.data.value[1] = this.parse(value);
this.max.value = ""+current_rule.condition.data.value[1];
return ({
"devotion": "Very Hateful: (-∞, -95), Hateful: [-95, -50), Resistant: [-50, -20), Ambivalent: [-20, 20], Accepting: (20, 50], Devoted: (50, 95], Worshipful: (95, ∞)",
"trust": "Extremely terrified: (-∞, -95), Terrified: [-95, -50), Frightened: [-50, -20), Fearful: [-20, 20], Careful: (20, 50], Trusting: (50, 95], Total trust: (95, ∞)",
"health": "Death: (-∞, -100), Near Death: [-100, -90), Extremely Unhealthy: [-90, -50), Unhealthy: [-50, -20), Healthy: [-20, 20], Very Healthy: (20, 50], Extremely Healthy: (50, 90], Unnaturally Healthy: (90, ∞)",
"energy": "Frigid: (-∞, 20], Poor: (20, 40], Average: (40, 60], Powerful: (60, 80], Sex Addict: (80, 100), Nympho: 100",
"weight": "Emaciated: (-∞, -95), Skinny: [-95, -30), Thin: [-30, -10), Average: [-10, 10], Plush: (10, 30], Fat: (30, 95], Overweight: (95, ∞)",
"lactation": "None: 0, 1: Natural, 2: Lactation implant",
"preg": "Barren: -2, On contraceptives: -1, Not pregnant: 0, Pregnancy weeks: [1, ∞)",
"pregType": "Fetus count, known only after the 10th week of pregnancy",
"bellyImplant": "Volume in CCs. None: -1",
"belly": "Volume in CCs, any source",
"intelligenceImplant": "Education level. 0: uneducated, 15: educated, 30: advanced education, (0, 15): incomplete education.",
"intelligence": "From moronic to brilliant: [-100, 100]",
"accent": "No accent: 0, Nice accent: 1, Bad accent: 2, Can't speak language: 3 and above",
"waist": "Masculine waist: (95, ∞), Ugly waist: (40, 95], Unattractive waist: (10, 40], Average waist: [-10, 10], Feminine waist: [-40, -10), Wasp waist: [-95, -40), Absurdly narrow: (-∞, -95)",
class ItemEditor extends Element {
render(fn, data) {
const elem = document.createElement("div");
const input = document.createElement("input");
input.setAttribute("type", "text");
input.value = JSON.stringify(data.value);
input.onkeypress = e => { if (returnP(e)) this.setValue(input); };
input.onblur = e => this.setValue(input);
this.input = input;
elem.appendChild(input);
const infobar = document.createElement("div");
infobar.innerHTML = this.info(data.attribute);
elem.appendChild(infobar);
return elem;
}
info(attribute) {
return "Insert a valid JSON array. Known values: " + {
"fetish": "buttslut, cumslut, masochist, sadist, dom, submissive, boobs, pregnancy, none (AKA vanilla)",
}[attribute];
}
setValue(input) {
try {
const arr = JSON.parse(input.value);
current_rule.condition.data.value = arr;
input.value = JSON.stringify(arr);
} catch (e) {
alert(e);
}
}
}
super("Apply to assignments and facilities");
const items = ["Rest", "Fucktoy", "Subordinate Slave", "House Servant", "Confined", "Whore", "Public Servant", "Classes", "Milked", "Gloryhole"];
if (V.HGSuite > 0) items.push("Head Girl Suite");
if (V.brothel > 0) items.push("Brothel");
if (V.club > 0) items.push("Club");
if (V.arcade > 0) items.push("Arcade");
if (V.dairy > 0) items.push("Dairy");
if (V.servantsQuarters > 0) items.push("Servant Quarters");
if (V.masterSuite > 0) items.push("Master Suite");
if (V.schoolroom > 0) items.push("Schoolroom");
if (V.spa > 0) items.push("Spa");
if (V.clinic > 0) items.push("Clinic");
if (V.cellblock > 0) items.push("Cellblock");
items.forEach(
i => this.appendChild(new ButtonItem(i, this.getAttribute(i), current_rule.condition.assignment.includes(this.getAttribute(i)))));
current_rule.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",
"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",
}
}
class SpecialExclusion extends List {
constructor() {
this.setValue(current_rule.condition.excludeSpecialSlaves);
this.onchange = (value) => current_rule.condition.excludeSpecialSlaves = value;
}
}
class SpecificInclusionExclusion extends Options {
constructor() {
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
this.appendChild(new OptionsItem("Limit to specific slaves", () => this.show_slave_selection()));
this.appendChild(new OptionsItem("Exclude specific slaves", () => this.show_slave_exclusion()));
this.subwidget = null;
}
show_slave_selection() {
if (this.subwidget) this.subwidget.remove();
this.subwidget = new SlaveSelection();
this.appendChild(this.subwidget);
}
show_slave_exclusion() {
if (this.subwidget) this.subwidget.remove();
this.subwidget = new SlaveExclusion();
this.appendChild(this.subwidget);
}
}
class SlaveSelection extends ButtonList {
constructor() {
super("Include specific slaves");
V.slaves.forEach(slave => this.appendChild(new ButtonItem(
[slave.slaveName, slave.slaveSurname].join(" "),
slave.ID,
current_rule.condition.selectedSlaves.includes(slave.ID))));
}
onchange() {
current_rule.condition.selectedSlaves = this.getSelection();
}
}
class SlaveExclusion extends ButtonList {
constructor() {
super("Exclude specific slaves");
V.slaves.forEach(slave => this.appendChild(new ButtonItem(
[slave.slaveName, slave.slaveSurname].join(" "),
slave.ID,
current_rule.condition.excludedSlaves.includes(slave.ID))));
}
onchange() {
current_rule.condition.excludedSlaves = this.getSelection();
// parent section for effect editing
class EffectEditor extends Element {
constructor() {
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());
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());
this.appendChild(new DickAccVirginsList());
this.appendChild(new DickAccOtherList());
this.appendChild(new ButtplugsVirginsList());
this.appendChild(new ButtplugsOtherList());
this.appendChild(new ButtplugAttachmentsList());
if (V.arcologies[0].FSAssetExpansionistResearch === 1)
this.appendChild(new HyperGrowthSwitch());
this.appendChild(new GrowthList());
this.appendChild(new CurrativesList());
this.appendChild(new AphrodisiacList());
this.appendChild(new ContraceptiveList());
this.appendChild(new PregDrugsList());
this.appendChild(new FemaleHormonesList());
this.appendChild(new ShemaleHormonesList());
this.appendChild(new GeldingHormonesList());
this.appendChild(new OtherDrugsList());
if (V.enema === 1) {
this.appendChild(new EnemaList());
}
this.appendChild(new DietList());
this.appendChild(new DietGrowthList());
this.appendChild(new DietBaseList());
if (V.arcologies[0].FSHedonisticDecadenceResearch === 1) {
this.appendChild(new DietSolidFoodList());
}
this.appendChild(new MuscleList());
this.appendChild(new BraceList());
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());
if (V.studio === 1) {
this.appendChild(new PornBroadcastStatus());
class OtherSection extends Section {
constructor() {
super("Other Settings");
this.appendChild(new LabelList());
this.appendChild(new LabelRemoveList());
this.appendChild(new EyewearList());
this.appendChild(new LensesList());
this.appendChild(new MakeupList());
this.appendChild(new NailsList());
this.appendChild(new HairLengthList());