Newer
Older
// 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]; }
function returnP(e) { return e.keyCode === 13; }
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;
const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
const parse = {
integer(string) {
let n = parseInt(string, 10);
},
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();
}
super(header);
this.hidey = this.element.querySelector("div");
render(header) {
const section = document.createElement("section");
section.classList.add("rajs-section");
const h1 = document.createElement("h1");
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() {
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
constructor(prefix, data = [], editor = false, ...args) {
const elem = document.createElement("div");
const label = document.createElement("span");
label.innerHTML = prefix;
this.value = editor ? this.createEditor(...args) : document.createElement("strong");
if (this.value.tagName === "INPUT") { this.value.value = `${what}`; } else { this.value.innerHTML = `${what}`; }
getData(what) {
return (this.value.tagName === "INPUT" ? this.parse(this.value.value): this.selectedItem.data);
}
if (this.onchange instanceof Function) { this.onchange(this.getData()); }
}
}
// a clickable item of a list
class ListItem extends Element {
const elem = document.createElement("span");
elem.classList.add("rajs-listitem");
elem.innerHTML = displayvalue;
class List extends EditorWithShortcuts {
constructor(prefix, data = [], textinput = false) {
super(prefix, data, textinput);
}
createEditor() {
let res = document.createElement("input");
res.setAttribute("type", "text");
res.classList.add("rajs-value"); //
// call the variable binding when the input field is no longer being edited, and when the enter key is pressed
res.onblur = () => {
this.inputEdited();
};
res.onkeypress = (e) => {
return res;
}
}
class NumberRange extends EditorWithShortcuts {
constructor(prefix, data = [], min, max, spinbox = false) {
super(prefix, data, spinbox, min, max);
this.nullValue = data.length ? data[0][1] : null;
}
createEditor(min, max) {
let res = document.createElement("input");
res.setAttribute("type", "number");
res.setAttribute("min", min);
res.setAttribute("max", max);
res.onblur = () => {
this.inputEdited();
};
res.onkeypress = (e) => {
parse(what) {
return what === "" ? this.nullValue : parseInt(what);
}
// a way to organize 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");
super.appendChild(child);
child.parent = this.parent;
this.parent.children.push(child);
elements.forEach(element => { this.appendChild(element); });
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;
const elem = document.createElement("div");
const labelel = document.createElement("span");
}
getSelection() {
return (this.children
.filter(child => child.selected)
.map(child => child.setvalue)
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);
textarea.placeholder = "Paste your rule here";
container.appendChild(textarea);
this.textarea = textarea;
const button = document.createElement("button");
button.name = "Load";
if (rule instanceof Array) { rule.forEach(r => V.defaultRules.push(r)); } else { V.defaultRules.push(rule); }
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>`;
const newrule = new OptionsItem("Add a new rule", () => { newRule(this.root); });
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 SpecialInclusion());
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);
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",
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";
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");
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");
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);
this.input = input;
elem.appendChild(input);
const infobar = document.createElement("div");
infobar.innerHTML = this.info(data.attribute);
elem.appendChild(infobar);
return elem;
}
info(attribute) {
"fetish": "buttslut, cumslut, masochist, sadist, dom, submissive, boobs, pregnancy, none (AKA vanilla)",
setValue(input) {
try {
const arr = JSON.parse(input.value);
current_rule.condition.data.value = arr;
input.value = JSON.stringify(arr);
} catch (e) {
alert(e);
}
}
}
const items = ["Classes", "Confined", "Fucktoy", "Gloryhole", "House Servant", "Milked", "Public Servant", "Rest", "Subordinate Slave", "Whore"];
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.nursery > 0) { items.push("Nursery"); }
if (V.clinic > 0) { items.push("Clinic"); }
if (V.cellblock > 0) { items.push("Cellblock"); }
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",
["Include", -1],
["Exclude", 0],
["Only", 1]
super("Special slaves", items);
this.setValue(current_rule.condition.specialSlaves);
this.onchange = (value) => current_rule.condition.specialSlaves = value;
}
}
class SpecificInclusionExclusion extends Options {
constructor() {
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() {
this.subwidget = new SlaveSelection();
this.appendChild(this.subwidget);
}
show_slave_exclusion() {
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 DickChastityList());
this.appendChild(new DickAccVirginsList());
this.appendChild(new DickAccOtherList());
this.appendChild(new AnalChastityList());
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 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());