diff --git a/devTools/types/FC/desc.d.ts b/devTools/types/FC/desc.d.ts index 9c7d8a19156049a697812bb8bd7d6989e5de1c5c..913ff2eaff7dbd03d921fba98109432dbfe290c0 100644 --- a/devTools/types/FC/desc.d.ts +++ b/devTools/types/FC/desc.d.ts @@ -2,8 +2,8 @@ declare namespace FC { namespace Desc { interface LongSlaveOptions { descType?: DescType; - market?: Zeroable<SlaveMarketName | "starting">; - prisonCrime?: string; + market?: Zeroable<SlaveMarketName | SpecialMarketName | "starting">; + marketText?: string; /* extra text to be appended to the first line of the description, dependent on the original market */ noArt?: boolean; } } diff --git a/devTools/types/FC/misc.d.ts b/devTools/types/FC/misc.d.ts index 1fc19d9677d26ac73472a89cbc3e7531007e19f9..db86221fbeedac74bed8caf9e4271c8d23cd37c2 100644 --- a/devTools/types/FC/misc.d.ts +++ b/devTools/types/FC/misc.d.ts @@ -4,6 +4,7 @@ declare namespace FC { "neighbor" | "wetware" | "white collar" | SlaveSchoolName; type OrdinaryMarkets = "kidnappers" | "trainers" | "hunters" | "raiders" | "underage raiders" | "corporate"; type SlaveMarketName = LawlessMarkets | OrdinaryMarkets; + type SpecialMarketName = "Elite Slave" | "Household Liquidator" | "Custom Slave" | "Husk Slave" | "Slave Shelter" | "JobFulfillmentCenterOrder" | "Prestigious Slave" | "Special Slave"; type Gingering = Zeroable<"antidepressant" | "depressant" | "stimulant" | "vasoconstrictor" | "vasodilator" | "aphrodisiac" | "ginger">; type GingeringDetection = Zeroable<"slaver" | "mercenary" | "force">; type SlaveActs = "PCChildrenFathered" | "PCKnockedUp" | "anal" | "births" | "birthsTotal" | "cum" | "laborCount" | "mammary" | "milk" | "oral" | "penetrative" | "pitKills" | "miscarriages" | "publicUse" | "slavesFathered" | "slavesKnockedUp" | "vaginal" | "abortions" | "birth" | "bestiality"; diff --git a/src/data/backwardsCompatibility/datatypeCleanup.js b/src/data/backwardsCompatibility/datatypeCleanup.js index c13a5d635e2d7332d00551346e9b4d2b95ba89fc..af14361979dfaa92ceca64a05095bf32b9010935 100644 --- a/src/data/backwardsCompatibility/datatypeCleanup.js +++ b/src/data/backwardsCompatibility/datatypeCleanup.js @@ -2575,7 +2575,7 @@ App.Entity.Utils.RARuleDatatypeCleanup = function() { for (const piercing of Object.keys(App.Data.Piercings)) { const oldPiercing = `${oldPiercings.get(piercing) || piercing}Piercing`; // the old variable names were sometimes plural and sometimes singular. The new standard is to use singular when possible. set.piercing[piercing].desc = null; - if (piercing === "gentials" && set.piercing[piercing].smart === undefined) { + if (piercing === "genitals" && set.piercing[piercing].smart === undefined) { set.piercing[piercing].smart = null; } if (set.hasOwnProperty(oldPiercing)) { diff --git a/src/gui/multipleInspect.js b/src/gui/multipleInspect.js index faef31b80cf933d0e77ab64ecc825e74e3c59d25..c5843146501a222b81950d2a8d42c5fe44bceb39 100644 --- a/src/gui/multipleInspect.js +++ b/src/gui/multipleInspect.js @@ -2,14 +2,16 @@ * Provide a mechanism to inspect multiple slaves at once (for example, for Household Liquidators and recETS). * @param {Array<App.Entity.SlaveState>} slaves * @param {boolean} showFamilyTree - * @param {FC.SlaveMarketName} [market] + * @param {FC.SlaveMarketName | FC.SpecialMarketName} [market] + * @param {Map<number, string>} [marketText] map of Slave ID to text * @returns {DocumentFragment} */ -App.UI.MultipleInspect = function(slaves, showFamilyTree, market) { +App.UI.MultipleInspect = function(slaves, showFamilyTree, market, marketText) { const tabBar = new App.UI.Tabs.TabBar("MultipleInspect"); for (const slave of slaves) { - tabBar.addTab(slave.slaveName, `slave${slave.ID}`, App.Desc.longSlave(slave, {market: market})); + const text = marketText ? marketText.get(slave.ID) : null; + tabBar.addTab(slave.slaveName, `slave${slave.ID}`, App.Desc.longSlave(slave, {market: market, marketText: text})); } if (slaves.length > 1 && showFamilyTree) { diff --git a/src/js/slaveCostJS.js b/src/js/slaveCostJS.js index fbf8596cb9b2e8801f3681d730ce2f1b8ade8f2f..ff84712c598730bd117fd9092d4e1273ea527ac3 100644 --- a/src/js/slaveCostJS.js +++ b/src/js/slaveCostJS.js @@ -2321,7 +2321,7 @@ globalThis.FResultTooltip = function(slave, forSale = 0) { * @param {boolean} [isStartingSlave=false] is the slave a "starting slave" * @param {boolean} [followLaws=false] Apply cost variations from enacted Slave Market Regulations * @param {boolean} [isSpecial=false] is this slave a special/hero slave - * @param {object} [fromMarket=null] is this slave from the market + * @param {object} [fromMarket=null] is this slave from the market * @param {boolean} [returnDOM] * @returns {number|Object} */ diff --git a/src/markets/gingering.js b/src/markets/gingering.js index cc25ffaed694f40b791681f214950fc897362b63..f0f9873e20d5911429bcc65b1a5ba44d87808436 100644 --- a/src/markets/gingering.js +++ b/src/markets/gingering.js @@ -1,7 +1,7 @@ App.Entity.GingeringParameters = class { /** Get gingering parameters for a particular slave and market. * @param {App.Entity.SlaveState} slave - * @param {FC.Zeroable<FC.SlaveMarketName>} market + * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market * @param {number} [arcIndex] - arcology index if market is "neighbor" */ constructor(slave, market, arcIndex) { @@ -13,7 +13,7 @@ App.Entity.GingeringParameters = class { this.detected = false; // figure out what type of gingering applies, if any - if (applyLawCheck(market) === 1 && V.policies.SMR.honestySMR === 1) { + if (applyLawCheck(market) && V.policies.SMR.honestySMR === 1) { /* SMR prohibits gingering and is enforced for this seller - do nothing */ } else if (App.Data.misc.schools.has(market)) { /* slave schools have a reputation to maintain, and will never ginger their slaves */ @@ -95,7 +95,7 @@ globalThis._makeGingeredSlaveHandler = function(gParams, gKeys) { /** Get a gingered proxy for a slave. * @param {App.Entity.SlaveState} slave - * @param {FC.Zeroable<FC.SlaveMarketName>} market + * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market * @param {number} arcIndex - arcology number if market is "neighbor" * @returns {FC.GingeredSlave} * */ diff --git a/src/markets/marketUI.js b/src/markets/marketUI.js index be8770010fe5471e0dc092770768419d64500b18..05013f197e830479a0e01d98f8e8192946156ce2 100644 --- a/src/markets/marketUI.js +++ b/src/markets/marketUI.js @@ -9,14 +9,18 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", sTitlePlural = "slaves", costMod = 1} = {}) { const el = new DocumentFragment(); const {slave, text} = generateMarketSlave(slaveMarket, (V.market.numArcology || 1)); - const limitReached = V.slavesSeen > V.slaveMarketLimit; - const cost = getCost().cost; + const {He, him, his} = getPronouns(slave); let prisonCrime = ""; if (slaveMarket === V.prisonCircuit[V.prisonCircuitIndex]) { - prisonCrime = pronounsForSlaveProp(slave, text); + prisonCrime = `${He} ${pronounsForSlaveProp(slave, text)}`; } else { $(el).append(` ${text}`); } + + const applyLaw = applyLawCheck(slaveMarket); + const complianceResult = applyLaw ? App.Desc.lawCompliance(slave, slaveMarket) : ``; + const limitReached = V.slavesSeen > V.slaveMarketLimit; + const cost = getCost().cost; App.Events.addParagraph(el, [ `The offered price is`, App.UI.DOM.combineNodes(getCost().report, "."), @@ -27,12 +31,11 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", return el; function getCost() { - const {cost, report} = slaveCost(slave, false, !App.Data.misc.lawlessMarkets.includes(slaveMarket), false, true, {limitReached, costMod}); + const {cost, report} = slaveCost(slave, false, applyLaw, false, true, {limitReached, costMod}); return {cost, report}; } function choices() { - const {him, his} = getPronouns(slave); const el = document.createElement("p"); let title = {}; V.slavesSeen++; @@ -133,7 +136,7 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", ); } - el.append(App.Desc.longSlave(slave, {market: slaveMarket, prisonCrime})); + el.append(App.Desc.longSlave(slave, {market: slaveMarket, marketText: prisonCrime || complianceResult})); return el; function student() { @@ -146,10 +149,10 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", }; /** Construct the market global - * @param {string} market + * @param {FC.SlaveMarketName | FC.SpecialMarketName} market */ App.Markets.Global = function(market) { - /** @type {string} - overlaps with but is not contained by @see {FC.SlaveMarketName} */ + /** @type {FC.SlaveMarketName | FC.SpecialMarketName} */ this.slaveMarket = market; this.introType = ""; /** @type {Array<FC.GingeredSlave>} */ diff --git a/src/markets/specificMarkets/eliteSlave.js b/src/markets/specificMarkets/eliteSlave.js index 9ce1dfee79920b6272dd63ae2b91c5146b954b6a..6aa670ec4e332abd00629a579a06f49410bfb0ce 100644 --- a/src/markets/specificMarkets/eliteSlave.js +++ b/src/markets/specificMarkets/eliteSlave.js @@ -20,11 +20,10 @@ App.Markets["Elite Slave"] = function() { maxAge = 40; } let race; - let races; if (V.arcologies[0].FSSupremacist !== "unset") { race = V.arcologies[0].FSSupremacistRace; } else if (V.arcologies[0].FSSubjugationist !== "unset") { - races = ["amerindian", "asian", "asian", "asian", "asian", "asian", "asian", "black", "black", "indo-aryan", "indo-aryan", "latina", "latina", "latina", "malay", "malay", "middle eastern", "middle eastern", "mixed race", "pacific islander", "semitic", "semitic", "southern european", "southern european", "white", "white", "white", "white", "white", "white", "white", "white", "white"]; + let races = ["amerindian", "asian", "asian", "asian", "asian", "asian", "asian", "black", "black", "indo-aryan", "indo-aryan", "latina", "latina", "latina", "malay", "malay", "middle eastern", "middle eastern", "mixed race", "pacific islander", "semitic", "semitic", "southern european", "southern european", "white", "white", "white", "white", "white", "white", "white", "white", "white"]; races = races.delete(V.arcologies[0].FSSubjugationistRace); race = races.random(); } @@ -213,7 +212,7 @@ App.Markets["Elite Slave"] = function() { slave.dick = random(2, 5); slave.balls = random(2, 10); slave.scrotum = slave.balls; - slave.prostate = random(1, 3); + slave.prostate = either(1, 2, 3); } slave.makeup = 2; slave.nails = 1; @@ -251,8 +250,8 @@ App.Markets["Elite Slave"] = function() { slave.pubertyXX = 1; slave.breedingMark = 1; - let cost = slaveCost(slave, false, true); - + const complianceText = App.Desc.lawCompliance(slave, "Elite Slave") + const cost = slaveCost(slave, false, true); App.UI.DOM.appendNewElement("p", el, `It will take ${cashFormat(cost)} to win the auction.`); @@ -272,7 +271,7 @@ App.Markets["Elite Slave"] = function() { App.UI.DOM.appendNewElement("p", el, `You lack the necessary funds to place a winning bid.`, "note"); } - el.append(App.Desc.longSlave(slave, {market: "Elite Slave"})); + el.append(App.Desc.longSlave(slave, {market: "Elite Slave", marketText: complianceText})); } return el; }; diff --git a/src/markets/specificMarkets/householdLiquidator.js b/src/markets/specificMarkets/householdLiquidator.js index 2416fe2641293d5c8976a85ba40b8c796d5be3e8..4892ac3ee2e545be04b3edfc2f4194d8abcc22c8 100644 --- a/src/markets/specificMarkets/householdLiquidator.js +++ b/src/markets/specificMarkets/householdLiquidator.js @@ -3,16 +3,14 @@ App.Markets["Household Liquidator"] = function() { V.market.newSlavesDone = 0; const el = new DocumentFragment(); - let slave; - let r = []; - const newSlaves = []; - let totalCost; + /** @type {ReturnType<prepSlaveSale>} */ + let newSlaves = null; if (jsRandom(1, 100) > 50) { // Old enough to have a younger sibling who can be a slave. - slave = GenerateNewSlave(null, { + const slave = GenerateNewSlave(null, { minAge: (V.minimumSlaveAge + 2), disableDisability: 1 }); - finishSlave(); + finishSlave(slave); setMissingParents(slave); // Create opposite sex chance of relative @@ -20,30 +18,21 @@ App.Markets["Household Liquidator"] = function() { App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering a set of siblings for sale. You are permitted to inspect both slaves.`); const relativeSlave = generateRelatedSlave(slave, "younger sibling", oppositeSex); - newSlaves.push(slave); - newSlaves.push(relativeSlave); - - let cost = slaveCost(slave, false, true); - if (V.slavesSeen > V.slaveMarketLimit) { - cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1)); - } - totalCost = cost * 3; + + newSlaves = prepSlaveSale(3.0, slave, relativeSlave); } else if (jsRandom(1, 100) > 20) { // Old enough to have a child who can be a slave. - slave = GenerateNewSlave(null, { + const slave = GenerateNewSlave(null, { minAge: (V.fertilityAge + V.minimumSlaveAge), maxAge: 42, ageOverridesPedoMode: 1, disableDisability: 1 }); - if (slave.vagina > -1) { - slave.boobs += 100; - } slave.butt += 1; if (slave.vagina > -1) { + slave.boobs += 100; slave.vagina += 1; - } - if (slave.vagina > -1) { slave.counter.birthsTotal = 1; } - finishSlave(); + finishSlave(slave); + // Create opposite sex chance of relative const oppositeSex = (slave.genes !== GenerateChromosome()); const {his, mother} = getPronouns(slave); @@ -51,50 +40,36 @@ App.Markets["Household Liquidator"] = function() { const relativeSlave = generateRelatedSlave(slave, "child", oppositeSex); const {daughter} = getPronouns(relativeSlave); - r.push(`The household liquidator is offering a ${mother} and ${his} ${daughter} for sale. You are permitted to inspect both slaves.`); - App.UI.DOM.appendNewElement("p", el, r.join(" ")); - - newSlaves.push(slave); - newSlaves.push(relativeSlave); + App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering a ${mother} and ${his} ${daughter} for sale. You are permitted to inspect both slaves.`); - let cost = slaveCost(slave, false, true); - if (V.slavesSeen > V.slaveMarketLimit) { - cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1)); - } - totalCost = cost * 3; + newSlaves = prepSlaveSale(3.0, slave, relativeSlave); } else { - slave = GenerateNewSlave(null, {disableDisability: 1}); - finishSlave(); + const slave = GenerateNewSlave(null, {disableDisability: 1}); + finishSlave(slave); setMissingParents(slave); App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering something special: identical twins. The markup is huge, but the merchandise isn't something you see every day.`); const relativeSlave = generateRelatedSlave(slave, "twin"); - newSlaves.push(slave); - newSlaves.push(relativeSlave); - let cost = slaveCost(slave, false, true); - if (V.slavesSeen > V.slaveMarketLimit) { - cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1)); - } - totalCost = cost * 4; + newSlaves = prepSlaveSale(4.0, slave, relativeSlave); } el.append(`The price is `); - el.append(App.UI.DOM.cashFormat(totalCost)); + el.append(App.UI.DOM.cashFormat(newSlaves.cost)); el.append(`.`); if (V.slavesSeen > V.slaveMarketLimit) { el.append(` You have cast such a wide net for slaves this week that it is becoming more expensive to find more for sale. Your reputation helps determine your reach within the slave market.`); } - if (V.cash >= totalCost) { + if (V.cash >= newSlaves.cost) { App.UI.DOM.appendNewElement( "div", el, App.UI.DOM.link( `Buy their slave contracts`, () => { - V.market.newSlaves = newSlaves; - V.market.newSlaves.forEach((s) => cashX(forceNeg(totalCost / V.market.newSlaves.length), "slaveTransfer", s)); + V.market.newSlaves = newSlaves.slaves; + V.market.newSlaves.forEach((s) => cashX(forceNeg(newSlaves.cost / V.market.newSlaves.length), "slaveTransfer", s)); V.returnTo = "Buy Slaves"; }, [], @@ -116,12 +91,38 @@ App.Markets["Household Liquidator"] = function() { "Market" ) ); - App.UI.DOM.appendNewElement("p", el, App.UI.MultipleInspect(newSlaves, true, "Household Liquidators")); + App.UI.DOM.appendNewElement("p", el, App.UI.MultipleInspect(newSlaves.slaves, true, "Household Liquidator", newSlaves.text)); return el; - function finishSlave() { - slave = Object.assign(slave, { + /** + * @param {number} costFactor + * @param {Array<FC.GingeredSlave>} slaves + */ + function prepSlaveSale(costFactor, ...slaves) { + const bundle = { + /** @type {Array<FC.GingeredSlave>} */ + slaves: [], + /** @type {Map<number, string>} */ + text: new Map(), + cost: 0 + }; + for (const slave of slaves) { + const complianceText = App.Desc.lawCompliance(slave, "Household Liquidator") + bundle.slaves.push(slave); + bundle.text.set(slave.ID, complianceText); + } + let cost = slaveCost(slaves[0], false, true); + if (V.slavesSeen > V.slaveMarketLimit) { + cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1)); + } + bundle.cost = cost * costFactor; + return bundle; + } + + /** @param {FC.GingeredSlave} slave */ + function finishSlave(slave) { + Object.assign(slave, { origin: "You bought $him from the household liquidator.", devotion: jsRandom(-75, -25), trust: jsRandom(-45, -25), diff --git a/src/markets/theMarket/marketData.js b/src/markets/theMarket/marketData.js index 72ee489efebbfe3ed0f8ba409754aebac7a542eb..4e2d1d9410aaced212c7fa8cb0ad4f61534ea1bd 100644 --- a/src/markets/theMarket/marketData.js +++ b/src/markets/theMarket/marketData.js @@ -5,7 +5,7 @@ /** * @typedef {object} market * @property {string} title - * @property {string} [marketType] + * @property {FC.SlaveMarketName | FC.SpecialMarketName} [marketType] * @property {string} [note] * @property {string} [encyclopedia] * @property {string} [sale] diff --git a/src/npc/descriptions/longSlave.js b/src/npc/descriptions/longSlave.js index 5d55dfc4c661ad6edd17fec48e1c84099a99ea8d..095cbddbeda4543323c0baf59dd86bf6e6efc701 100644 --- a/src/npc/descriptions/longSlave.js +++ b/src/npc/descriptions/longSlave.js @@ -3,7 +3,7 @@ * @param {FC.Desc.LongSlaveOptions} params * @returns {DocumentFragment} */ -App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt} = {}) { +App.Desc.longSlave = function(slave, {descType, market = 0, marketText, noArt} = {}) { const { He, His, him, he, his } = getPronouns(slave); @@ -12,7 +12,6 @@ App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt} let frag; let p; let r; - const applyLaw = (market !== "starting") && applyLawCheck(market); SlaveStatClamp(slave); descType = descType || (market ? DescType.MARKET : DescType.NORMAL); @@ -37,17 +36,14 @@ App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt} p.append("(", App.UI.DOM.makeElement('span', slave.custom.label, "custom-label"), ") "); } - if (market && V.ui !== "start") { - if (applyLaw === 1) { + if (market && market !== "starting") { + if (applyLawCheck(market)) { p.append(`has passed inspection to be sold in your arcology. `); - $(p).append(App.Desc.lawCompliance(slave, market)); - p.append(` `); } else { p.append(`is for sale and is available to inspect. `); - if (prisonCrime) { - // reports a slave's crime in the criminal market - p.append(`${He} ${prisonCrime} `); - } + } + if (marketText) { + $(p).append(marketText, ` `); } $(p).append(reportGingering(slave)); el.appendChild(p); diff --git a/src/npc/generate/lawCompliance.js b/src/npc/generate/lawCompliance.js index c4018d3685061a6a854f125a0c8a67649e4a5e17..ee337eda42df03c75828b834d6d68a09b46cb055 100644 --- a/src/npc/generate/lawCompliance.js +++ b/src/npc/generate/lawCompliance.js @@ -1,6 +1,6 @@ /** * @param {App.Entity.SlaveState} slave - * @param {number | string} [market=0] + * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market=0] * @returns {string} */ App.Desc.lawCompliance = function(slave, market = 0) { @@ -146,6 +146,8 @@ App.Desc.lawCompliance = function(slave, market = 0) { if (market !== "Elite Slave" && policies.countEugenicsSMRs() > 0) { r.push(eugenicsSMRsCount()); } + + SlaveStatClamp(slave); return r.join(" "); diff --git a/src/npc/generate/slaveGenerationJS.js b/src/npc/generate/slaveGenerationJS.js index dba524cf81ef38b9813cf28cfc1f47dc16ddc669..aea42dcc3584ad1718f4eb8d694c52b8b8abf461 100644 --- a/src/npc/generate/slaveGenerationJS.js +++ b/src/npc/generate/slaveGenerationJS.js @@ -1447,13 +1447,9 @@ globalThis.applyAgeImplantOlder = function(slave) { /** * Determine whether a given market should apply SMR laws or not. - * @param {FC.Zeroable<FC.SlaveMarketName>} [market] - * @returns {number} [1|0] + * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market] + * @returns {boolean} */ globalThis.applyLawCheck = function(market) { - if (typeof market !== "string" || App.Data.misc.lawlessMarkets.includes(market)) { - return 0; - } else { - return 1; - } + return (typeof market === "string" && !App.Data.misc.lawlessMarkets.includes(market)) };