From 0037148d16fe99741d5ea7385f018a04ad0a7a97 Mon Sep 17 00:00:00 2001 From: lowercasedonkey <lowercasedonkey@gmail.com> Date: Wed, 16 Dec 2020 15:55:54 -0500 Subject: [PATCH] start --- src/facilities/salon/salon.js | 1405 +++++++++++++++++++++++++++++++++ 1 file changed, 1405 insertions(+) create mode 100644 src/facilities/salon/salon.js diff --git a/src/facilities/salon/salon.js b/src/facilities/salon/salon.js new file mode 100644 index 00000000000..9eaa3867379 --- /dev/null +++ b/src/facilities/salon/salon.js @@ -0,0 +1,1405 @@ +/** + * UI for the Salon. Refreshes without refreshing the passage. + * @param {App.Entity.SlaveState} slave + * @param {boolean} cheat if true, will hide scenes and keep the player from being billed for changes. + */ +App.UI.salon = function(slave, cheat = false) { + const container = document.createElement("span"); + container.id = "body-modification"; + const { + He, His, + he, his, him, himself + } = getPronouns(slave); + Enunciate(slave); + let piercingLevel; + let modReaction = ""; + /** @type {string|0} */ + let tattooChoice = ""; + let scarApplied = false; + let brandApplied = false; + let degradation = 0; + + container.append(createPage()); + return container; + + function createPage() { + const el = new DocumentFragment(); + if (!cheat) { + if (V.seeImages > 0) { + App.Events.drawEventArt(el, slave); + } + el.append(intro()); + el.append(reaction()); + } + el.append(piercings()); + el.append(tattoos()); + el.append(branding()); + el.append(scar()); + return el; + } + + function intro() { + const el = new DocumentFragment(); + App.UI.DOM.appendNewElement("h1", el, "Body Modification Studio"); + App.UI.DOM.appendNewElement("div", el, `${SlaveFullName(slave)} is lying strapped down on the table in your body modification studio. ${He} is entirely at your mercy.`); + return el; + } + + function reaction() { + const el = new DocumentFragment(); + let r = []; + if (brandApplied || degradation || scarApplied || modReaction) { + if (slave.fuckdoll === 0) { + if (canSee(slave)) { + r.push(`There's a mirror on the ceiling, so ${he} can see`); + } else { + r.push(`${He} can't see, so `); + if (canHear(slave)) { + r.push(`you're careful to describe`); + } else { + r.push(`${he} must, by ${himself}, get a feel for`); + } + } + r.push(`${his} new appearance.`); + } + if (brandApplied) { + r.push(`The smell of burnt flesh hangs in the air. Being branded <span class="health.dec">has hurt ${his} health a little.</span>`); + healthDamage(slave, 10); + brandApplied = false; + } + if (scarApplied) { + if (V.scarTarget.local === "entire body") { + switch (V.scarDesign.local) { + case "burn": + r.push(`Your goal wasn't to make the distinct shape of a brand, but rather to permanently mar the skin with an open flame.`); + break; + case "surgical": + if (V.PC.skill.medicine >= 100) { + r.push(`Your medical mastery is perfect, so creating Frankenstein's monster was a deliberate work of art.`); + } else if (V.PC.skill.medicine > 0) { + r.push(`Your medical skills are progressing, and the Frankenstein effect reminds you of your earliest attempts.`); + } else { + r.push(`You really slashed away with your knife, but were careful not to allow ${him} to bleed out.`); + } + break; + default: + r.push(`The best way to apply scarring to the entire body is with a good old fashioned whip. ${His} body is a mess of crisscrossed lines`); + if (hasAnyNaturalLimbs(slave)) { + r.push(`, and ${his} `); + if (getLimbCount(slave, piercingLevel) > 1) { + r.push(`limbs twisted so violently in their restraints that they too have`); + } else { + r.push(`only limb twists so violently in its restraints that it too has`); + } + r.push(` become scarred`); + } + r.push(r.pop() + "."); + } + r.push(`No matter how you chose to apply it, scarring so much of ${his} body has <span class="health.dec"> hurt ${his} health.</span>`); + healthDamage(slave, 20); + } else { + if (slave.scar[V.scarTarget.local][V.scarDesign.local] > 0) { + r.push(`This is not the first time ${he} was scarred like this.`); + } + switch (V.scarDesign.local) { + case "whip": + r.push(`Targeting a single area with a whip is not easy. You set the mood by carefully arranging candles dripping on to a whimpering ${slave.slaveName}, then got ${his} attention with a quick`); + if (canSee(slave)) { + r.push(`wave`); + } else if (canHear(slave)) { + r.push(`crack`); + } else { + r.push(`tap`); + } + r.push(`of the whip. One by one, you carefully snuffed out the candles, flicking hot wax as you went. After pausing a moment, you prepared to leave your mark.`); + if (["penis", "vagina"].includes(V.scarTarget.local)) { + if (slave.dick > 4 && V.seeDicks) { + r.push(`${His} dick was large enough that it was not too difficult to hit,`); + } else if (slave.dick > 0 && V.seeDicks) { + r.push(`${His} dick was a challengingly small target,`); + } else { + if (slave.clit > 0) { + r.push(`${His} clit was a difficult target,`); + } else { + r.push(`${His} clit was an impossibly tiny target,`); + } + } + r.push(`but the end was never in doubt. The tip connected with ${his} most intimate place on the first try, and plunged ${him} into absolute agony.`); + } else { + r.push(`The end was never in doubt. A few strokes of the whip plunged ${him} into agony ${his} body will not allow ${him} to forget.`); + } + break; + case "burn": + r.push(`Your goal wasn't to make the distinct shape of a brand, but rather to permanently mar the ${slave.skin} skin of ${his} ${V.scarTarget.local} with an open flame.`); + break; + case "surgical": + if (V.PC.skill.medicine >= 100) { + r.push(`Your medical mastery is perfect, so creating such a scar was a deliberate act of degradation.`); + } else if (V.PC.skill.medicine > 0) { + r.push(`Your medical skills are progressing, and the sloppy scar reminds you of your earliest attempts.`); + } else { + r.push(`You really slashed away at ${V.scarTarget.local} with your knife, but were careful not to allow ${him} to bleed out.`); + } + break; + default: + r.push(`You had no shortage of kinky and medical tools for applying scars. ${His} ${slave.skin} ${V.scarTarget.local} is bleeding profusely.`); + } + + r.push(`No matter how you chose to apply it, being scarred <span class="health.dec"> hurt ${his} health a little.</span>`); + healthDamage(slave, 10); + } + r.push(`Afterwards you seal the wounds with a white medical spray. Infection is no risk to ${slave.slaveName} thanks to your curatives, but in order to form obvious scar tissue you had to keep air out and delay normal healing as long as possible.`); + scarApplied = false; + } + if (slave.fetish !== "mindbroken" && slave.fuckdoll === 0) { + if (degradation > 1) { + if (degradation > 5) { + if (slave.devotion <= 50 && slave.trust < -50) { + r.push(`${He} is appalled by the whorish spectacle you have made of ${him}. ${He} <span class="gold">fears</span> you all the more for this but is so terrified of you it does not affect ${his} submission.`); + slave.trust -= 10; + } else if (slave.devotion <= 50) { + r.push(`${He} is appalled by the whorish spectacle you have made of ${him}. ${He} <span class="mediumorchid">hates</span> and <span class="gold">fears</span> you for this.`); + slave.devotion -= 10; + slave.trust -= 10; + } else { + r.push(`${He} is shocked by the whorish spectacle you have made of ${him}. However, ${he} is so submissive to your will that ${he} <span class="hotpink">accepts</span> that the slave `); + if (canSee(slave)) { + r.push(`in the mirror`); + } else { + r.push(`${he} pictures`); + } + r.push(`is who ${he} is now.`); + slave.devotion += 4; + } + } else { + if (slave.devotion < -20 && slave.trust < 20) { + r.push(`${He} is <span class="gold">afraid</span> that ${he} has been permanently altered against ${his} will, but is also scared of your reaction to any objection and suppresses ${his} disgust.`); + slave.trust -= 5; + } else if (slave.devotion < -20) { + r.push(`${He} is <span class="mediumorchid">angry</span> and <span class="gold">afraid</span> that ${he} has been permanently altered against ${his} will.`); + slave.devotion -= 5; + slave.trust -= 5; + } else { + r.push(`${He} is saddened to have been altered against ${his} will. However, ${he} realizes that ${he} is a slave, so ${he} <span class="hotpink">accepts</span> your work.`); + slave.devotion += 2; + } + } + degradation = 0; + } + if (modReaction) { + r.push(modReaction); + } + } + modReaction = ""; + } + App.Events.addNode(el, r, "p"); + return el; + } + + function piercings() { + const el = new DocumentFragment(); + let r = []; + const piercingLocations = ["ear", "nose", "eyebrow", "lips", "tongue", "nipples", "areolae", "navel", "corset", "clit", "vagina", "dick", "anus"]; + // DESCRIPTIONS + App.UI.DOM.appendNewElement("h2", el, "Piercings"); + + for (const piercing of piercingLocations.concat(["chastity"])) { + if (piercing === "nipples") { + r.push(App.UI.DOM.makeElement("div", App.Desc.piercing(slave, "nipple"))); // This is stupid, but the variable is slave.nipplesPiercing, plural nipples, but in almost all other places we refer to plural body parts as singular (eyebrow, ear, nose, etc). + } else { + r.push(App.UI.DOM.makeElement("div", App.Desc.piercing(slave, piercing))); + } + } + if (r.length === 0) { + r.push(App.UI.DOM.makeElement("div", `${His} smooth ${slave.skin} skin is completely unpierced.`)); + } + App.Events.addNode(el, r); + r = []; + + // Apply piercings + r.push(`Choose piercing style:`); + const piercingLevelNames = new Map([ + ["Remove", 0], + ["Light", 1], + ["Heavy", 2], + ["Smart", 3] + ]); + let linkArray = []; + for (const [title, num] of piercingLevelNames) { + if (piercingLevel === num) { + linkArray.push(App.UI.DOM.disabledLink(title, ["Currently selected"])); + } else { + linkArray.push( + App.UI.DOM.link( + title, + () => { + piercingLevel = num; + refresh(); + } + ) + ); + } + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + r = []; + + // Determine parts that cannot be pierced + let validPiercingLocations = Array.from(piercingLocations); + + if (piercingLevel !== 0) { // Sometimes a piercing winds up in a place that is no longer valid. Make sure players can always remove an existing piercing. + if (slave.nipples === "fuckable") { + removePiercingLocation("nipples"); + } + + if (slave.vagina === -1) { + removePiercingLocation("vagina"); + } + + if (slave.dick === 0) { + removePiercingLocation("dick"); + if (slave.vagina === -1) { + removePiercingLocation("clit"); + } + } + } + + function removePiercingLocation(location) { + const index = validPiercingLocations.indexOf(location); + validPiercingLocations.splice(index, 1); + } + + if (piercingLevel < 3) { + if (piercingLevel === 0) { + r.push(`Remove piercings from:`); + } else if (piercingLevel === 1) { + r.push(`Lightly pierce ${his}:`); + } else if (piercingLevel === 2) { + r.push(`Heavily pierce ${his}:`); + } + // Entire body + linkArray = []; + linkArray.push( + App.UI.DOM.link( + "Entire body", + () => { + for (const location of validPiercingLocations) { + if (slave[`${location}Piercing`] !== piercingLevel) { + modReaction += App.Medicine.Modification.setPiercing(slave, location, piercingLevel); + if (piercingLevel > 1) { + degradation += 1; + } + } + } + refresh(); + } + ) + ); + + // Each individual piercing + for (const location of validPiercingLocations) { + if (slave[`${location}Piercing`] !== piercingLevel) { + linkArray.push( + App.UI.DOM.link( + capFirstChar(location), + () => { + modReaction = App.Medicine.Modification.setPiercing(slave, location, piercingLevel); + if (piercingLevel > 1) { + degradation += 1; + } + refresh(); + } + ) + ); + } + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + } else if (piercingLevel === 3) { + // Smart piercings + if (slave.vagina !== -1 || slave.dick !== 0) { + if (slave.clitPiercing !== 3) { + r.push(`Give ${him} a`); + r.push( + App.UI.DOM.link( + "smart piercing", + () => { + modReaction = App.Medicine.Modification.setPiercing(slave, "clit", 3); + slave.clitSetting = "all"; + degradation += 1; + refresh(); + }, + [], + "", + `Costs ${cashFormat(V.SPcost)}, unlocks options to mold sexuality` + ) + ); + } else { + r.push(`${He} already has a smart piercing!`); + } + } + } + App.Events.addNode(el, r, "div"); + return el; + } + + function tattoos() { + const el = new DocumentFragment(); + let r = []; + let noTats; + const tattooLocations = new Map([ + ["shoulder", "shoulders"], + ["lips", "lips"], + ["breast", "boobs"], + ["upper arm", "arms"], + ["back", "back"], + ["lower back", "stamp"], + ["buttock", "butt"], + ["vagina", "vagina"], + ["dick", "dick"], + ["anus", "anus"], + ["leg", "legs"] + ]); + // DESCRIPTIONS + App.UI.DOM.appendNewElement("h2", el, "Tattoos"); + + for (const name of tattooLocations.keys()) { + if (name === "leg") { + r.push(App.UI.DOM.makeElement("div", App.Desc.tattoo(slave, "thigh"))); + } else { + r.push(App.UI.DOM.makeElement("div", App.Desc.tattoo(slave, name))); + } + } + if (r.length === 0) { + r.push(App.UI.DOM.makeElement("div", `${His} smooth ${slave.skin} skin is completely unmarked.`)); + noTats = true; + } + App.Events.addNode(el, r); + r = []; + + // Apply tattoos + r.push(`Choose a tattoo style:`); + const tattooChoiceNames = new Set([ + "tribal patterns", + "flowers", + "counting", + "advertisements", + "rude words", + "degradation", + "Asian art", + "scenes", + "bovine patterns", + "permanent makeup", + "sacrilege", + "sacrament", + "possessive", + "paternalist", + ]); + if (slave.anusTat === 0) { + tattooChoiceNames.add("bleached"); + } + let linkArray = []; + for (const style of tattooChoiceNames) { + if (tattooChoice === style) { + linkArray.push(App.UI.DOM.disabledLink(capFirstChar(style), ["Currently selected"])); + } else { + linkArray.push( + App.UI.DOM.link( + capFirstChar(style), + () => { + tattooChoice = style; + refresh(); + } + ) + ); + } + } + if (!noTats) { + linkArray.push( + App.UI.DOM.link( + "Remove a tattoo", + () => { + tattooChoice = 0; + refresh(); + } + ) + ); + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + r = []; + + // Determine parts that cannot be pierced + let validTattooLocations; + if (tattooChoice === "bleached") { + validTattooLocations = ["anus"]; + } else { + validTattooLocations = Array.from(tattooLocations.keys()); + if (!hasAnyNaturalArms(slave)) { + removeTattooLocation("upper arm"); + } + + if (!hasAnyNaturalArms(slave)) { + removeTattooLocation("thigh"); + } + + if (slave.dick === 0 || tattooChoice === "scenes") { + removeTattooLocation("dick"); + } + if ((tattooChoice === "Asian art" || tattooChoice === "scenes") && slave.anusTat === "bleached") { // leave existing bleached anus alone + removeTattooLocation("anus"); + } + } + + function removeTattooLocation(location) { + const index = validTattooLocations.indexOf(location); + validTattooLocations.splice(index, 1); + } + + if (tattooChoice === 0) { + r.push(`Clean the ink off of ${him}:`); + } else if (tattooChoice === "counting") { + r.push(`Add tallies of ${his} sexual exploits to ${him}:`); + } else if (tattooChoice === "bleached") { + r.push(`Bleach ${his}:`); + } else if (tattooChoice) { + r.push(`Add ${tattooChoice} to ${his}:`); + } + // Entire body + linkArray = []; + if (tattooChoice !== undefined && tattooChoice !== "bleached") { + linkArray.push( + App.UI.DOM.link( + "Entire body", + () => { + for (const location of validTattooLocations) { + if (slave[`${location}tattoo`] !== tattooChoice) { + applyTat(location); + } + } + refresh(); + } + ) + ); + } + // Each individual tattoo + for (const location of validTattooLocations) { + if (slave[`${tattooLocations.get(location)}Tat`] !== tattooChoice) { + linkArray.push( + App.UI.DOM.link( + capFirstChar(location), + () => { + applyTat(location); + refresh(); + } + ) + ); + } + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + + el.append(oddTattoos()); + + const customEl = document.createElement("div"); + customEl.id = "custom-el"; + customEl.append( + App.UI.DOM.link( + "Show custom tattoo locations", + () => { + jQuery("#custom-el").empty().append(customTats()); + } + ) + ); + el.append(customEl); + + return el; + + function applyTat(location) { + tattooChoice = (location === "lips" && tattooChoice === "scenes") ? "permanent makeup" : tattooChoice; + modReaction += App.Medicine.Modification.setTattoo(slave, tattooLocations.get(location), tattooChoice); + if (!["flowers", "paternalist", "tribal patterns", 0].includes(tattooChoice)) { + degradation += 1; + } + } + + function oddTattoos() { + const el = new DocumentFragment(); + let linkArray = []; + let r = []; + + // Has tat, display option to remove + if (slave.bellyTat !== 0) { + r.push(`${His} navel is tattooed with ${slave.bellyTat}.`); + linkArray.push( + App.UI.DOM.link( + "Remove tattoos", + () => { + tattooChoice = 0; + modReaction += App.Medicine.Modification.setTattoo(slave, "belly", tattooChoice); + refresh(); + } + ) + ); + } + + if (slave.belly >= 10000 && slave.bellyPreg < 450000 && slave.bellyFluid < 5000) { + if (slave.bellyTat === 0) { + const bellyTats = new Map([ + ["Heart", "a heart"], + ["Star", "a star"], + ["Butterfly", "a butterfly"], + ]); + r.push(`${He} has no navel tattoos.`); + for (const [title, value] of bellyTats) { + linkArray.push( + App.UI.DOM.link( + title, + () => { + tattooChoice = value; + modReaction += App.Medicine.Modification.setTattoo(slave, "belly", tattooChoice); + refresh(); + } + ) + ); + } + } + } else if (slave.bellyPreg >= 450000) { + r.push(`${His} middle is large and taut enough to be a suitable canvas for a navel focused tattoo, but ${his} brood is too active to permit the needle to do its work.`); + } else if (slave.bellyFluid >= 10000) { + r.push(`${His} middle is large and taut enough to be a suitable canvas for a navel focused tattoo, but the pressure applied to ${his} stomach will likely force ${him} to release its contents.`); + } else { + r.push(`${His} middle isn't large enough to be a suitable canvas for a navel focused tattoo.`); + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + + r = []; + linkArray = []; + if (slave.birthsTat > 0) { + if (slave.birthsTat > 1) { + r.push(`${He} has a series of ${num(slave.birthsTat)} baby-shaped tattoos adorning ${his} stomach; one for each successful`); + if (slave.pregKnown === 1) { + r.push(`pregnancy and a temporary one for ${his} current pregnancy.`); + } else { + r.push(`pregnancy.`); + } + } else { + r.push(`${He} has a single baby-shaped tattoo${(slave.pregKnown === 1) ? `, and one temporary one,` : ``} adorning ${his} stomach.`); + } + if (slave.bellyTat !== 0) { + linkArray.push( + App.UI.DOM.link( + "Remove tattoos", + () => { + slave.birthsTat = -1; + billMod(); + refresh(); + } + ) + ); + } + } else if (slave.birthsTat === 0) { + if (slave.pregKnown === 1) { + r.push(`${He} has a single baby-shaped temporary tattoo adorning ${his} stomach.`); + r.push( + App.UI.DOM.link( + "Remove it", + () => { + slave.birthsTat = -1; + refresh(); + } + ) + ); + if (slave.abortionTat > -1) { + r.push(App.UI.DOM.makeElement("span", `This will only remove birth tracking`, "note")); + } + } else { + r.push(`${He} is scheduled to receive a tattoo each time ${he} gives birth.`); + r.push( + App.UI.DOM.link( + "Cancel", + () => { + slave.birthsTat = -1; + refresh(); + } + ) + ); + } + } else { + r.push(`Have ${him} receive a tattoo each time ${he} gives birth.`); + r.push( + App.UI.DOM.link( + "Begin keeping track", + () => { + slave.birthsTat = 0; + refresh(); + } + ) + ); + } + App.Events.addNode(el, r, "div"); + + r = []; + + if (slave.abortionTat > 0) { + if (slave.abortionTat > 1) { + r.push(`${He} has a series of ${num(slave.abortionTat)} crossed out baby-shaped tattoos${(slave.pregKnown === 1) ? `, and one uncrossed one,` : ``} adorning ${his} stomach.`); + } else { + r.push(`${He} has a single crossed out baby-shaped tattoo${(slave.pregKnown === 1) ? `, and one uncrossed one,` : ``} adorning ${his} stomach.`); + } + r.push( + App.UI.DOM.link( + "Remove tattoos", + () => { + slave.abortionTat = -1; + billMod(); + refresh(); + } + ) + ); + } else if (slave.abortionTat === 0) { + if (slave.pregKnown === 1) { + r.push(`${He} has a single baby-shaped temporary tattoo adorning ${his} stomach.`); + r.push( + App.UI.DOM.link( + "Remove it", + () => { + slave.abortionTat = -1; + refresh(); + } + ) + ); + if (slave.birthsTat > -1) { + r.push(App.UI.DOM.makeElement("span", `This will only remove abortion tracking`, "note")); + } + } else { + r.push(`${He} is scheduled to receive a tattoo each time ${he} gets an abortion or miscarries.`); + r.push( + App.UI.DOM.link( + "Cancel", + () => { + slave.abortionTat = -1; + refresh(); + } + ) + ); + } + } else { + r.push(`Have ${him} receive a tattoo for each abortion or miscarriage ${he} has.`); + r.push( + App.UI.DOM.link( + "Begin keeping track", + () => { + slave.abortionTat = 0; + refresh(); + } + ) + ); + } + App.Events.addNode(el, r, "div"); + return el; + } + + function customTats() { + const el = new DocumentFragment(); + App.UI.DOM.appendNewElement("h3", el, "Custom Tattoos"); + const r = []; + for (const location of validTattooLocations) { + const varName = tattooLocations.get(location); + if (varName) { + r.push(App.UI.DOM.makeElement("div", `${capFirstChar(location)}: `)); + r.push( + App.UI.DOM.makeElement( + "div", + App.UI.DOM.makeTextBox( + slave[`${varName}Tat`], + (v) => { + modReaction += App.Medicine.Modification.setTattoo(slave, varName, v); + refresh(); + } + ) + ) + ); + } + } + + r.push(App.UI.DOM.makeElement("div", `Custom: `)); + r.push( + App.UI.DOM.makeElement( + "div", + App.UI.DOM.makeTextBox( + slave.custom.tattoo, + (v) => { + slave.custom.tattoo = v; + billMod(); + refresh(); + } + ) + ) + ); + App.Events.addNode(el, r, "div", "grid-2columns-auto"); + if (slave.custom.tattoo !== "") { + el.append( + App.UI.DOM.link( + "Remove custom Tattoo", + () => { + slave.custom.tattoo = ""; + billMod(); + refresh(); + } + ) + ); + } + return el; + } + } + + function branding() { + const el = new DocumentFragment(); + const selection = document.createElement("span"); + selection.id = "brand-selection"; + selection.append(brand(slave, cheat)); + el.append(selection); + + if (slave.breedingMark === 1 && (V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) { + const r = []; + r.push(`${He} has an intricate tattoo on ${his} lower belly that suggests ${he} was made to be bred.`); + r.push( + App.UI.DOM.link( + "Remove it", + () => { + slave.breedingMark = 0; + refresh(); + } + ) + ); + App.Events.addNode(el, r, "div"); + } + return el; + } + + function scar() { + const el = new DocumentFragment(); + App.UI.DOM.appendNewElement("h2", el, "Scars"); + let r = []; + + for (const _scarName in slave.scar) { + const scarDiv = document.createElement("div"); + scarDiv.append(`${His} ${_scarName} is marked with ${App.Desc.expandScarString(slave, _scarName)}: `); + scarDiv.append( + App.UI.DOM.link( + "Remove Scar", + () => { + scarApplied = false; + delete slave.scar[_scarName]; + billSurgery(); + degradation -= 10; + refresh(); + } + ) + ); + r.push(scarDiv); + } + if (r.length > 0) { + App.Events.addNode(el, r, "div"); + } else { + App.UI.DOM.appendNewElement("div", el, `${His} skin is not scarred.`); + } + + r = []; + r.push(`Use <strong>${V.scarDesign.local}</strong> or choose another scar:`); + const scarTypes = new Set([ + "whip", + "burn", + "surgical", + "menacing", + "exotic" + ]); // Other common scars might be battle scars or c-Section but it makes little sense to include them here + let linkArray = []; + for (const scarType of scarTypes) { + linkArray.push( + App.UI.DOM.link( + capFirstChar(scarType), + () => { + V.scarDesign.local = scarType; + refresh(); + } + ) + ); + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + + r = []; + r.push(`Or design your own:`); + r.push( + App.UI.DOM.makeTextBox( + V.scarDesign.local, + (v) => { + V.scarDesign.local = v; + refresh(); + } + ) + ); + App.Events.addNode(el, r, "div"); + + r = []; + r.push(`Choose a site for scaring:`); + + let scarLocations = new Map(); + + if (["exotic", "menacing"].includes(V.scarDesign.local)) { + scarLocations.set("Cheeks", "cheek"); + } else { + // Sorted head to toe + scarLocations.set("Entire body", "entire body"); + + // Head + if (slave.earShape !== "none") { + scarLocations.set("Ears", "ear"); + } + scarLocations.set("Cheeks", "cheek"); + scarLocations.set("Neck", "neck"); + + // Torso + scarLocations.set("Chest", "chest"); + scarLocations.set("Breasts", "breast"); + scarLocations.set("Back", "back"); + scarLocations.set("Lower Back", "lower back"); + scarLocations.set("Belly", "belly"); + scarLocations.set("Pubic Mound", "pubic mound"); + + if (slave.dick > 0) { + scarLocations.set("Penis", "penis"); + } + if (slave.balls > 0 && slave.scrotum > 0) { + scarLocations.set("Testicles", "testicle"); + } + + // Arms + scarLocations.set("Shoulders", "shoulder"); + if (hasAnyNaturalArms(slave)) { + scarLocations.set("Arm, upper", "upper arm"); + scarLocations.set("Arm, lower", "lower arm"); + scarLocations.set("Wrists", "wrist"); + scarLocations.set("Hands", "hand"); + } + + // Legs + scarLocations.set("Buttocks", "buttock"); + if (hasAnyNaturalLegs(slave)) { + scarLocations.set("Thighs", "thigh"); + scarLocations.set("Calves", "calf"); + scarLocations.set("Ankles", "ankle"); + scarLocations.set("Feet", "foot"); + } + } + + linkArray = []; + for (const [text, value] of scarLocations) { + linkArray.push( + App.UI.DOM.link( + text, + () => { + V.scarTarget.local = value; + refresh(); + } + ) + ); + } + r.push(App.UI.DOM.generateLinksStrip(linkArray)); + App.Events.addNode(el, r, "div"); + + r = []; + r.push(`Or a custom site:`); + r.push( + App.UI.DOM.makeTextBox( + V.scarTarget.local, + (v) => { + V.scarTarget.local = v; + refresh(); + } + ) + ); + App.Events.addNode(el, r, "div"); + + // Correct some "bad" choices" + if (["exotic", "menacing"].includes(V.scarDesign.local)) { + if (V.scarTarget.local !== "cheek") { + V.scarTarget.local = "cheek"; + } + } + + r = []; + + if (["ankle", "breast", "buttock", "calf", "cheek", "ear", "foot", "hand", "lower arm", "shoulder", "testicle", "thigh", "upper arm", "wrist"].includes(V.scarTarget.local)) { + const _leftTarget = ("left " + V.scarTarget.local); + const _rightTarget = ("right " + V.scarTarget.local); + if (slave.scar[_leftTarget]) { + r.push(`${His}${_leftTarget} is already marked with ${App.Desc.expandScarString(slave, _leftTarget)}.`); + } + if (slave.scar[_rightTarget]) { + r.push(`${His}${_rightTarget} is already marked with ${App.Desc.expandScarString(slave, _rightTarget)}.`); + } + r.push(`Scar ${him} now with ''${V.scarDesign.local}'' on the`); + let _left = 0, _right = 0; + // overwrite brand + + if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.scarTarget.local) && getLeftArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.scarTarget.local) && getLeftLegID(slave) !== 1)) { + _left = 1; + // make next checks easier + r.push( + App.UI.DOM.link( + "left", + () => { + V.scarTarget.local = _leftTarget; + scarApplied = true; + App.Medicine.Modification.addScar(slave, _leftTarget, V.scarDesign.local); + billMod(); + degradation += 10; + refresh(); + } + ) + ); + } + if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.scarTarget.local) && getRightArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.scarTarget.local) && getRightLegID(slave) !== 1)) { + _right = 1; + // make next checks easier + } + if (_left && _right) { + r.push(`${V.scarTarget.local}, or the`); + } + if (_right) { + r.push( + App.UI.DOM.link( + "right", + () => { + V.scarTarget.local = _rightTarget; + scarApplied = true; + App.Medicine.Modification.addScar(slave, _rightTarget, V.scarDesign.local); + billSurgery(); + degradation += 10; + refresh(); + } + ) + ); + } + if (!_left || !_right) { + r.push(`${V.scarTarget.local}?`); + } + } else { + if (slave.scar.hasOwnProperty(V.scarTarget.local)) { + if (slave.scar[V.scarTarget.local][V.scarDesign.local]) { + r.push(`${He} already has ${V.scarDesign.local} scars on ${his} V.scarTarget.local. You can make it worse.`); + } else { + // check how much scarring is on this part + const _scarTotalValue = (Object.values(slave.scar[V.scarTarget.local])).reduce((a, b) => a + b, 0); + if (_scarTotalValue) { + r.push(`That would be a new kind of scar to add to the growing collection on ${his} ${V.scarTarget.local}. Life can always be worse for a slave.`); + } + } + } + r.push( + App.UI.DOM.link( + "Scar", + () => { + let _scarArray; + if (V.scarTarget.local === "entire body" && V.scarDesign.local.includes("whip")) { + // Special case for whipping scene, produces two kinds of scars + App.Medicine.Modification.addScourged(slave); + } else { + // Normal entire body scarring + if (V.scarTarget.local === "entire body") { + _scarArray = ["left breast", "right breast", "back", "lower back", "left buttock", "right buttock"]; + if (getLeftArmID(slave) === 0) { + _scarArray.push("left upper arm"); + } + if (getRightArmID(slave) === 0) { + _scarArray.push("right upper arm"); + } + if (getLeftLegID(slave) === 0) { + _scarArray.push("left thigh"); + } + if (getRightLegID(slave) === 0) { + _scarArray.push("right thigh"); + } + } else { // Single scar + _scarArray = [V.scarTarget.local]; + } + for (const scar of _scarArray) { + App.Medicine.Modification.addScar(slave, scar, V.scarDesign.local); + degradation += 10; + } + } + billMod(); + scarApplied = true; + degradation += 10; + refresh(); + } + ) + ); + r.push(`with ${V.scarDesign.local} on the ${V.scarTarget.local}${(slave.scar[V.scarTarget.local]) ? `, adding to the scars that are already there?` : `.`}`); + } + App.Events.addNode(el, r, "div"); + + return el; + } + + function brand(slave, cheat = false) { + const el = new DocumentFragment(); + let p = document.createElement('p'); + let div = document.createElement('div'); + + App.UI.DOM.appendNewElement("h2", el, "Branding"); + + for (const brandPlace in slave.brand) { + div = document.createElement('div'); + div.append(`${His} ${brandPlace} is marked with ${slave.brand[brandPlace]}`); + if (slave.brand[brandPlace] === V.brandDesign.official) { + div.append(`, your `); + div.append(App.UI.DOM.passageLink("official brand", "Universal Rules")); + } + div.append(": "); + if (!cheat) { + div.append( + App.UI.DOM.link( + "Remove Brand", + () => { + brandApplied = false; + delete slave.brand[brandPlace]; + billSurgery(); + degradation -= 10; + refresh(); + }, + ) + ); + } else { + div.append( + App.UI.DOM.link( + "Remove Brand", + () => { + delete slave.brand[brandPlace]; + brandRefresh(); + }, + ) + ); + } + p.append(div); + } + + if (jQuery.isEmptyObject(slave.brand)) { + App.UI.DOM.appendNewElement("div", p, `${His} skin is unmarked.`); + } + + if (!(Object.values(slave.brand).includes(V.brandDesign.official)) && !cheat) { + div = document.createElement('div'); + div.append(`${He} lacks your `); + div.append(App.UI.DOM.passageLink("official brand", "Universal Rules")); + div.append(`, "${V.brandDesign.official}."`); + p.append(div); + } + + el.append(p); + p = document.createElement('p'); + + div = document.createElement('div'); + div.append(`Use ''${V.brandDesign.local}'' or choose another brand: `); + div.append(symbolOptions("personal")); + p.append(div); + + p.append(symbolBlock("dirtyWord")); + p.append(symbolBlock("genitalSymbol")); + p.append(symbolBlock("silhouettes")); + p.append(symbolBlock("FS")); + + div = document.createElement('div'); + div.append(`Or design your own: `); + div.append( + App.UI.DOM.makeTextBox( + V.brandDesign.local, + v => { + V.brandDesign.local = v; + brandRefresh(); + }, + ) + ); + p.append(div); + el.append(p); + + p = document.createElement('p'); + App.UI.DOM.appendNewElement("div", p, "Choose a site for branding: "); + const body = slaveBody(); + p.append(partLinks(body.head)); + p.append(partLinks(body.torso)); + p.append(partLinks(body.arms)); + p.append(partLinks(body.legs)); + + div = document.createElement('div'); + div.append(`Or a custom site: `); + div.append( + App.UI.DOM.makeTextBox( + V.brandTarget.local, + v => { + V.brandTarget.local = v; + brandRefresh(); + }, + ) + ); + p.append(div); + el.append(p); + + p = document.createElement('p'); + + if (["ankle", "breast", "buttock", "calf", "cheek", "ear", "foot", "hand", "lower arm", "shoulder", "testicle", "thigh", "upper arm", "wrist"].includes(V.brandTarget.local)) { + const leftTarget = ("left " + V.brandTarget.local); + const rightTarget = ("right " + V.brandTarget.local); + if (slave.brand[leftTarget]) { + p.append(`${His} ${leftTarget} is already marked with ${slave.brand[leftTarget]}. `); + } + if (slave.brand[rightTarget]) { + p.append(`${His} ${rightTarget} is already marked with ${slave.brand[rightTarget]}. `); + } + p.append(`Brand ${him} now with ''${V.brandDesign.local}'' on the `); // todo: break out bold + let _left; + let _right; + if ( + !(["upper arm", "lower arm", "wrist", "hand"].includes(V.brandTarget.local) && getLeftArmID(slave) !== 1) && + !(["thigh", "calf", "ankle", "foot"].includes(V.brandTarget.local) && getLeftLegID(slave) !== 1) + ) { + _left = 1;// make next checks easier + if (!cheat) { + p.append( + App.UI.DOM.link( + "left", + () => { + brandApplied = true; + slave.brand[leftTarget] = check(V.brandDesign.local); + billMod(); + degradation += 10; + refresh(); + }, + ) + ); + } else { + p.append( + App.UI.DOM.link( + "left", + () => { + slave.brand[leftTarget] = check(V.brandDesign.local); + refresh(); + }, + ) + ); + } + + if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.brandTarget.local) && getRightArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.brandTarget.local) && getRightLegID(slave) !== 1)) { + _right = 1; // make next checks easier + } + if (_left && _right) { + p.append(` ${V.brandTarget.local}, or the `); + } + if (_right) { + if (!cheat) { + p.append( + App.UI.DOM.link( + "right", + () => { + brandApplied = true; + slave.brand[rightTarget] = check(V.brandDesign.local); + billMod(); + degradation += 10; + refresh(); + }, + ) + ); + } else { + p.append( + App.UI.DOM.link( + "right", + () => { + slave.brand[rightTarget] = check(V.brandDesign.local); + refresh(); + }, + ) + ); + } + } + p.append(`? `); + if (!_left || !_right) { + p.append(` ${V.brandTarget.local}`); + App.UI.DOM.appendNewElement("span", p, `Branding will slightly reduce ${his} beauty but may slowly increase your reputation.`, "note"); + } + } + } else { + if (slave.brand[V.brandTarget.local] === V.brandDesign.local) { + p.append(`${He} already has ${V.brandDesign.local} on ${his} ${V.brandTarget.local}.`); + } else { + if (!cheat) { + p.append( + App.UI.DOM.link( + "Brand", + () => { + brandApplied = true; + slave.brand[V.brandTarget.local] = V.brandDesign.local; + billMod(); + degradation += 10; + refresh(); + }, + ) + ); + } else { + p.append( + App.UI.DOM.link( + "Brand", + () => { + slave.brand[V.brandTarget.local] = V.brandDesign.local; + refresh(); + }, + ) + ); + } + p.append(` with ${V.brandDesign.local} on the ${V.brandTarget.local}`); + if (slave.brand[V.brandTarget.local]) { + p.append(`, covering the "${slave.brand[V.brandTarget.local]}" that is already there? `); + } else { + p.append(`. `); + } + App.UI.DOM.appendNewElement("span", p, `Branding will slightly reduce ${his} beauty but may slowly increase your reputation.`, "note"); + } + } + el.append(p); + return el; + + function symbolBlock(brandList) { + const div = document.createElement('div'); + div.classList.add("choices"); + div.append(symbolOptions(brandList)); + return div; + } + + function symbolOptions(brandList) { + const list = App.Medicine.Modification.Brands[brandList]; + const array = []; + for (const brand in list) { + const frag = new DocumentFragment(); + if (!cheat && list[brand].hasOwnProperty("requirements")) { + if (!list[brand].requirements(slave)) { + continue; + } + } + if (brandList === "FS") { + App.UI.DOM.appendNewElement("span", frag, "FS ", "note"); + } + frag.append( + App.UI.DOM.link( + list[brand].displayName, + () => { + V.brandDesign.local = check(brand); + brandRefresh(); + } + ) + ); + array.push(frag); + } + return App.UI.DOM.generateLinksStrip(array); + } + + function slaveBody() { + const body = {}; + // Sorted head to toe + // Head + body.head = {}; + if (slave.earShape !== "none") { + body.head.ears = "Ears"; + } + body.head.cheek = "Cheeks"; + body.head.neck = "Neck"; + + // Torso + body.torso = {}; + body.torso.chest = "Chest"; + body.torso.breast = "Breasts"; + body.torso.back = "Back"; + body.torso["lower back"] = "Lower Back"; + body.torso.belly = "Belly"; + body.torso["pubic mound"] = "Pubic Mound"; + + if (slave.dick > 0) { + body.torso.penis = "Penis"; + } + if (slave.balls > 0 && slave.scrotum > 0) { + body.torso.testicle = "Testicles"; + } + + // Arms + body.arms = {}; + body.arms.shoulder = "Shoulders"; + if (hasAnyNaturalArms(slave)) { + body.arms["upper arm"] = "Arm, upper"; + body.arms["lower arm"] = "Arm, lower"; + body.arms.wrist = "Wrists"; + body.arms.hand = "Hands"; + } + + // Legs + body.legs = {}; + body.legs.buttock = "Buttocks"; + if (hasAnyNaturalLegs(slave)) { + body.legs.thigh = "Thighs"; + body.legs.calf = "Calves"; + body.legs.ankle = "Ankles"; + body.legs.foot = "Feet"; + } + return body; + } + + function partLinks(bodyPartObj) { + const div = document.createElement("div"); + div.classList.add("choices"); + const array = []; + for (const bp in bodyPartObj) { + array.push( + App.UI.DOM.link( + bodyPartObj[bp], + () => { + V.brandTarget.local = check(bp); + brandRefresh(); + } + ) + ); + } + div.append(App.UI.DOM.generateLinksStrip(array)); + return div; + } + + function check(brand) { + switch (brand) { + case "a big helping of your favorite food": + return "a big helping of " + V.PC.refreshment; + default: + return brand; + } + } + + function brandRefresh() { + jQuery('#brand-selection').empty().append(brand(slave, cheat)); + } + } + + + function refresh() { + jQuery("#body-modification").empty().append(createPage()); + } + + function billMod() { + if (!cheat) { + cashX(forceNeg(V.modCost), "slaveMod", slave); + } + } + + function billSurgery() { + if (!cheat) { + cashX(forceNeg(V.surgeryCost), "slaveSurgery", slave); + } + } +}; -- GitLab