Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • PantyNabber/fc-pregmod
  • pregmodfan/fc-pregmod
  • Alice.Grant/fc-pregmod
  • powerfful/fc-pregmod
  • elstumpo/fc-pregmod
  • Y/fc-pregmod
  • floer/fc-pregmod
  • oidocrop/fc-pregmod
  • hawk5005/fc-pregmod
  • nn/fc-pregmod
  • latios298/fc-pregmod
  • brpregmodfan/fc-pregmod
  • SomeoneTookMyUserName/fc-pregmod
  • 000-250-006/fc-pregmod
  • rewrica/fc-pregmod
  • Stuffedgame/fc-pregmod
  • wisepants314/fc-pregmod
  • fcanon/fc-pregmod
  • randomanon1/pregmod-mod-cyberfurry
  • teddy.buckland/fc-pregmod
  • farsinian_591b7a2d8b49d/fc-pregmod
  • FCShotadev/fc-pregmod
  • uselessartifact/fc-pregmod
  • irina_90/fc-pregmod
  • thaumx/fc-pregmod
  • MouseOfLight/fc-pregmod
  • empresssela/fc-pregmod
  • MasterAaran/fc-pregmod
  • ishy/fc-pregmod
  • psychofox/fc-pregmod
  • shadomancer/fc-pregmod
  • tycrakgg/fc-pregmod
  • azktaawc/fc-pregmod
  • andonno07/fc-pregmod
  • Onithyr/fc-pregmod
  • redneck987.jl/fc-pregmod
  • Farseeker/fc-pregmod
  • milliarc/fc-pregmod
  • BeefimusMaximus/fc-pregmod
  • magicknight79/fc-pregmod
  • hexall90/fc-pregmod
  • cantworkforever/fc-pregmod
  • jc052005/fc-pregmod
  • klorpa/fc-pregmod
  • doku/fc-pregmod
  • samhradh/fc-pregmod
  • scientist/fc-pregmod
  • albania420/fc-pregmod
  • Editoranon/fc-pregmod
  • Anony/fc-pregmod
  • deimios666/fc-pregmod
  • IvoHoe/fc-pregmod
  • bitty/fc-pregmod
  • RealAnon1800/fc-pregmod
  • brankirk/fc-pregmod
  • Amygdalan/fc-pregmod
  • DankWolf/fc-pregmod
  • Supot1951/fc-pregmod
  • bcy603/fc-pregmod
  • pwfxjpuv/fc-pregmod
  • ThreadAnon/fc-pregmod
  • Anon1800/fc-pregmod
  • Echoart/fc-pregmod
  • Dingotush/fc-pregmod
  • anonman/fc-pregmod
  • Arkerthan/fc-pregmod
  • svornost/fc-pregmod
  • wierdwierdos/fc-pregmod
  • wetwareAnon/fc-pregmod
  • QuartzHat/fc-pregmod
  • IchthysdeKilt/fc-pregmod
  • AnonAnonFC/fc-pregmod
  • Alexsis/fc-pregmod
  • LoyalTreeWP/fc-pregmod
  • aerialace/fc-pregmod
  • NurseryAnon/fc-pregmod
  • drakeashordcataclysm/fc-pregmod
  • AshVaris/fc-pregmod
  • purely0nothing/fc-pregmod
  • alex2011/fc-pregmod
  • Lindontree/fc-pregmod
  • FCaa/fc-pregmod
  • TR-8R/fc-pregmod
  • Jones/fc-pregmod
  • brr99/fc-pregmod
  • WriteAnon101/fc-pregmod
  • Drosil/fc-pregmod
  • Bob1221/fc-pregmod
  • vas/fc-pregmod
  • gitgud.user.937/fc-pregmod
  • D-K/fc-pregmod
  • AnonDev/fc-pregmod
  • madman23456/fc-pregmod
  • InarusLynx/fc-pregmod
  • Sonofrevvan/fc-pregmod
  • Randoisrando/fc-pregmod
  • cheez94/fc-pregmod
  • dldldl/fc-pregmod
  • alice321/fc-pregmod
  • Alexei91/fc-pregmod
  • darkcy/fc-pregmod
  • MapleMAD/fc-pregmod
  • pillarofsalt/fc-pregmod
  • vultureangels/fc-pregmod
  • kernel/fc-pregmod
  • nooneman/fc-pregmod
  • deepmurk/fc-pregmod
  • uglybead/fc-pregmod
  • lemongrab/fc-pregmod
  • temperence-chan/fc-pregmod
  • hcommenter/fc-pregmod
  • SpedeMemerson/fc-pregmod
  • qwijqwsf/fc-pregmod
  • BuDClow/fc-pregmod
  • HiveBro/fc-pregmod
  • shoku/fc-pregmod
  • ezsh/fc-pregmod
  • Blank/fc-pregmod
  • randoralcissian/fc-pregmod
  • benito92/fc-pregmod
  • balakart/fc-pregmod
  • wedonotsaw/fc-pregmod
  • Cayleth/fc-pregmod
  • Khip/fc-pregmod
  • Zfair/fc-pregmod
  • promethium/fc-pregmod
  • scyne/fc-pregmod
  • ZZC/fc-pregmod
  • SilverJanine/fc-pregmod
  • joxosix654email-9.co/fc-pregmod
  • Littlefootlittleguy/fc-pregmod
  • FelipeBA/fc-pregmod
  • bigtiddygothbf/fc-pregmod
  • Qotsafan/fc-pregmod
  • Zachpocalypse/fc-pregmod
  • milkanon66/fc-pregmod
  • GreGGoZZ/fc-pregmod
  • drsnarf86/fc-pregmod
  • valen102938/fc-pregmod
  • pregspammer/fc-pregmod
  • ponderin94/fc-pregmod
  • nook/fc-pregmod
  • carnifex34/fc-pregmod-mod-carni
  • SyntheticHigh/fc-pregmod
  • bob112211/fc-pregmod
  • amomynous0/fc-pregmod
  • oxone/fc-pregmod
  • MaxEuwe/fc-pregmod
  • nekoanon/fc-pregmod
  • preglocke/fc-pregmod
  • valen10293847/fc-pregmod
  • 2hu4u/fc-pregmod
  • mayibrad/fc-pregmod
  • Screm/fc-pregmod
  • Ansopedi/fc-pregmod
  • mrchaosbones/fc-pregmod
  • putrid/fc-pregmod
  • Kinnerman/fc-pregmod
  • gungrave1155/fc-pregmod
  • prndev/fc-pregmod
  • weresmilodon/fc-pregmod
  • auxxigobin/fc-pregmod
  • alice-chan/fc-pregmod
  • wigglie/fc-pregmod
  • jrliltfgb/fc-pregmod
  • Lord.alek.shade/fc-pregmod
  • truetailthesquire/fc-pregmod
  • lowercasedonkey/fc-pregmod
  • alice-chan9/fc-pregmod
  • eroglyphics/fc-pregmod
  • taliyent/fc-pregmod
  • zenzombie90/fc-pregmod
  • kjarik/fc-pregmod
  • wriggler/fc-pregmod
  • midnightblue/fc-pregmod
  • faraen/fc-pregmod
  • sigurd.cole/fc-pregmod
  • FCbuganon/fc-pregmod
  • kidkinster/fc-pregmod
  • Kar_Dragon/fc-pregmod
  • Zhafier/fc-pregmod
  • crcaretti/fc-pregmod
  • anond/fc-pregmod
  • tempmania/fc-pregmod
  • Dhanze/fc-pregmod
  • EstaUnCachucha/fc-pregmod
  • oniAnon/fc-pregmod
  • plebian/fc-pregmod
  • maxd569/fc-pregmod
  • Levarn/fc-pregmod
  • pumpkinspice/fc-pregmod
  • GammaXai/fc-pregmod
  • DanBackslide/fc-pregmod
  • i107760/fc-pregmod
  • Absimiliard/fc-pregmod
  • AmbrosiaCheesecake/fc-pregmod
  • fuguer/fc-pregmod
  • Azurel/fc-pregmod
  • Fake_Dev/fc-pregmod
  • ddongsanda/fc-pregmod
  • Combine456/fc-pregmod
  • UnwrappedGodiva/fc-pregmod
  • toyRuberDucky/fc-pregmod
  • zmobie/fc-pregmod
  • chuongk/fc-pregmod
  • BigWalnuts/fc-pregmod
  • Birdstrike/fc-pregmod
  • r3d/fc-pregmod
  • mawspa/fc-pregmod
  • sushila/fc-pregmod
  • DeathShip/fc-pregmod
  • eggrollsandwich/fc-pregmod
  • krayken/fc-pregmod
  • Reman/fc-pregmod
  • dwiafgts/fc-pregmod
  • jort93/fc-pregmod
  • teruterubouzu/fc-pregmod
  • flut/fc-pregmod
  • john-normal/fc-pregmod
  • Jonathan2405/fc-pregmod
  • Tyrgalon/fc-pregmod
  • NovX/fc-pregmod
  • Star1/fc-pregmod
  • Transhumanist01/fc-pregmod
  • m1017242/fc-pregmod
  • Rizal98798/fc-pregmod
  • jamezu369/fc-pregmod
  • thisisawittyname/fc-pregmod
  • KnightBoulegard/fc-pregmod
  • jblack/fc-pregmod
  • Souldrainr/fc-pregmod
  • torbjornhub/fc-pregmod
  • turnop/fc-pregmod
  • breadedpigeon/fc-pregmod
  • fire.maker/fc-pregmod
  • Inahaze/fc-pregmod
  • Waerjak/fc-pregmod
  • Trashman1138/fc-pregmod
  • supanintendo/fc-pregmod
  • _no0neman/fc-pregmod
  • Weslo/fc-pregmod
  • qw89/fc-pregmod
  • EvilDruid/fc-pregmod
  • dt25/fc-pregmod
  • Raou/fc-pregmod
  • DDouFu/fc-pregmod
  • Mauno/fc-pregmod
  • PandemoniumPenguin/fc-pregmod
  • AngelPuppet/fc-pregmod
  • DasUser79/fc-pregmod
  • Keaeag3s/fc-pregmod
  • HazeHazeHaze/fc-pregmod
  • hpotato/fc-pregmod
  • owouchthatbloodyhurt/fc-pregmod
  • v7Silent/fc-pregmod
  • nickylass/fc-pregmod
  • ThePrimer/fc-pregmod
  • PineCone/fc-pregmod
  • bruhmomentum17/fc-pregmod
  • CheatDude/fc-pregmod
  • synnove/fc-pregmod
  • en_bees/fc-pregmod
  • seronis/fc-pregmod
  • Nepidinepnep/fc-pregmod
  • Titanninja/fc-pregmod
  • Elohiem/fc-pregmod
  • cocoajazz/fc-pregmod
  • tfwncagf/fc-pregmod
  • ChunkyMonke/fc-pregmod
  • Dracoman671/fc-pregmod
  • jgl/fc-pregmod
  • Inev/fc-pregmod
  • jbige/fc-pregmod
  • MonsterMate/fc-pregmod
  • Konstantin6961/fc-pregmod
  • darth_ashi/fc-pregmod
  • shinx/fc-pregmod
  • Anu/fc-pregmod
  • Greytide/fc-pregmod
  • Bonafidemetal/fc-pregmod
  • Peje/fc-pregmod
  • Hexfy98/fc-pregmod
  • TooSlow/fc-pregmod
  • SoGu/fc-pregmod
  • CloudyCoffee/fc-pregmod
  • Welptard/fc-pregmod
  • Ploc/fc-pregmod-ploc
  • rain-/fc-pregmod
  • Pecanus/fc-pregmod
  • Jhortrax/fc-pregmod
  • valleytwo/fc-pregmod
  • QCmd/fc-pregmod
  • kung-wada/fc-pregmod
  • LolGaye/fc-pregmod
  • Exspiravit1/fc-pregmod
  • jadeddog/fc-pregmod
  • buster-scruggs/fs-antebellum-revivalism
  • policia123/fc-pregmod
  • evrgentesee/fc-pregmod
  • rko127/fc-pregmod
  • ExcalGrip12/fc-pregmod
  • BlackAion/fc-pregmod
  • Boss2020/fc-pregmod
  • Lawled/fc-pregmod
  • shiro/fc-pregmod
  • Skavenkeri/fc-pregmod
  • PooPooDooDooHead/fc-pregmod
  • Dugee/fc-pregmod
  • Portal124/fc-pregmod-vore
  • Fekenol/fc-pregmod
  • elGuapo/fc-pregmod
  • KelioSteel/fc-pregmod
  • sldlddk/fc-pregmod
  • lumepanter/fc-pregmod
  • ryuhana/fc-pregmod
  • Nene1009yb/fc-pregmod
  • DontAskDontTell/fc-pregmod-extra-events
  • Dulgi/fc-pregmod
  • Jate/fc-pregmod
  • percy365/fc-pregmod
  • franklygeorge/fc-pregmod
  • Dragneel117/fc-pregmod
  • vl96/fc-pregmod
  • Gorlom/fc-economicmod
  • NotAlive/fc-pregmod
  • Heretek/fc-pregmod
  • joeshmo828282/fc-pregmod
  • deswes/fc-pregmod
  • Nanana21/fc-pregmod
  • Gbr6/fc-pregmod
  • RandomNecro/fc-pregmod
  • Trinidad/fc-pregmod
  • anonymousey/fc-pregmod
  • macaronideath/fc-pregmod
  • fcbleh/fc-pregmod
  • jk3000/fc-pregmod
  • Akane/fc-pregmod
  • TheBoi/fc-pregmod
  • Sheenariel/fc-pregmod
  • Metapod/multi-custom
  • Banyanael/fc-pregmod
  • frogge/fc-pregmod
  • idkkk12385/fc-pregmod
  • Mirarara/fc-pregmod
  • DeaDa/fc-pregmod-thedeal
  • CobraCommander/fc-pregmod
  • bicobus/fc-pregmod
  • CardcaptorRLH85/fc-pregmod
  • temp-ui-start/fc-pregmod
  • PresidentConvert/fc-pregmod
  • delizious/fc-pregmod
  • Ducati/fc-pregmod
  • DerangedLoner/fc-pregmod-development-fork
  • ProjectVictory/fc-pregmod
  • forecastle/fc-pregmod
  • Apathy/fc-pregmod
  • indf/fc-pregmod-dev
  • GavAndAlt/fc-pregmod
  • hagamablabla/fc-pregmod
  • Alaco/fc-pregmod
  • DCoded/fc-pregmod
  • LittlePlague/fc-pregmod
  • MissOnahole/fc-pregmod
  • ishy2317/fc-pregmod
  • nielkazama/fc-pregmod
  • Phobos/fc-pregmod
  • kraster/fc-pregmod
  • JasWS/fc-pregmod
  • FelixJS/fc-pregmod
  • NCherfaoui/fc-pregmod
  • MidnightMoose/fc-pregmod
  • jjjjjj/fc-pregmod
  • Cl0ver/fc-pregmod
  • Pythoniqus/fc-pregmod
  • JohnMolotov/fc-pregmod
  • anonymouspregmodder/fc-pregmod-anonymouspregmodder
  • Fanatey/fc-pregmod
  • Mizako/fc-pregmod
  • Nithhogg/fc-pregmod
  • Bluecoffee/fc-pregmod
  • Exarch/pregmod-ai-clothing
381 results
Show changes
Showing
with 483 additions and 53 deletions
......@@ -19,15 +19,9 @@ App.Events.SERetire = class SERetire extends App.Events.BaseEvent {
const slave = getSlave(id);
if (slave) {
App.UI.DOM.appendNewElement("div", node, App.Events.retire(slave));
node.append(sectionBreak());
node.append(App.UI.sectionBreak());
}
}
function sectionBreak() {
const hr = document.createElement("hr");
hr.style.margin = "0";
return hr;
}
}
};
......
......@@ -88,16 +88,40 @@ App.EndWeek.nextWeek = function() {
} else {
V.PC.fertPeak = 0;
}
} else if (V.PC.pregWeek < 0) {
} else if (V.PC.pregWeek < 0 || V.PC.preg > 0) {
V.PC.fertPeak = 2;
} else {
if (V.PC.fertPeak === 0) {
// Assume a standard 4 week cycle
V.PC.fertPeak += 3;
// Assume a standard 4 week cycle (safe - period - safe - risky)
V.PC.fertPeak += V.PC.geneMods.progenitor === 1 ? 0 : 3;
V.PC.fertLate--;
} else if (V.PC.fertPeak > 2) {
if (V.menstruationKnown > 0 && random(1, 100) > 95) {
// late period trolling
V.PC.fertPeak += 1;
}
V.PC.fertPeak--;
V.PC.fertLate--;
} else if (isMenstruating(V.PC)) {
// period
V.PC.fertPeak--;
V.PC.fertLate = 3;
V.PC.fertKnown = 1;
} else if (V.PC.fertPeak > 0) {
V.PC.fertPeak--;
V.PC.fertLate--;
} else if (V.PC.fertPeak < 0) {
// This logics lets you go into the negatives. This allows multiple risky weeks in a row.
V.PC.fertPeak++;
}
if (V.PC.geneMods.progenitor) {
V.PC.fertKnown = 1;
V.PC.fertLate = 0;
}
}
if (V.PC.pregKnown) {
V.PC.fertKnown = 1;
}
} else if (V.PC.geneticQuirks.superfetation === 2 && V.PC.womb.length > 0) {
if (V.PC.fertPeak === 0) {
V.PC.fertPeak = 1;
......@@ -106,6 +130,9 @@ App.EndWeek.nextWeek = function() {
} else if (V.PC.fertPeak !== 0) {
V.PC.fertPeak = 0;
}
if (V.policies.contraceptivesBan && V.PC.preg === -1) {
V.PC.preg = 0;
}
/* irregular leptin production weight gain/loss setter */
if (V.PC.geneticQuirks.wGain === 2 && V.PC.geneticQuirks.wLoss === 2) {
V.PC.weightDirection = either(-1, 1);
......@@ -234,20 +261,39 @@ App.EndWeek.nextWeek = function() {
} else {
slave.fertPeak = 0;
}
} else if (slave.preg > 0) {
slave.fertPeak = 2;
} else if (slave.pregWeek < 0 || slave.preg > 0) {
slave.fertPeak = 2;
} else {
if (slave.fertPeak === 0) {
// Assume a standard 4 week cycle
slave.fertPeak += 3;
// Assume a standard 4 week cycle (safe - period - safe - risky)
slave.fertPeak += slave.geneMods.progenitor === 1 ? 0 : 3;
slave.fertLate--;
} else if (slave.fertPeak > 2) {
if (V.menstruationKnown > 0 && random(1, 100) > 95) {
// late period trolling
slave.fertPeak += 1;
}
slave.fertPeak--;
slave.fertLate--;
} else if (isMenstruating(slave)) {
// period
slave.fertPeak--;
slave.fertLate = 3;
slave.fertKnown = 1;
} else if (slave.fertPeak > 0) {
slave.fertPeak--;
slave.fertLate--;
} else if (slave.fertPeak < 0) {
// This logics lets you go into the negatives. This allows multiple risky weeks in a row.
slave.fertPeak++;
}
if (slave.geneMods.progenitor) {
slave.fertKnown = 1;
slave.fertLate = 0;
}
}
if (slave.pregKnown) {
slave.fertKnown = 1;
}
} else if (slave.geneticQuirks.superfetation === 2 && slave.womb.length > 0) {
if (slave.fertPeak === 0) {
......@@ -257,6 +303,9 @@ App.EndWeek.nextWeek = function() {
} else if (slave.fertPeak !== 0) {
slave.fertPeak = 0;
}
if (V.policies.contraceptivesBan && slave.preg === -1) {
slave.preg = 0;
}
slave.trust = Number(slave.trust.toFixed(1));
slave.devotion = Number(slave.devotion.toFixed(1));
slave.oldDevotion = slave.devotion;
......@@ -482,7 +531,7 @@ App.EndWeek.nextWeek = function() {
V.returnTo = "";
// reset transistion object
// reset transition object
App.Utils.resetTransitionObject();
if (V.autosave !== 0) {
......
......@@ -69,19 +69,29 @@ App.EndWeek.Player.inflation = function(PC = V.PC) {
r.push(`${cow.slaveName} is having trouble producing enough ${PC.inflationType} to satisfy`);
if (harvest < 2) {
r.push(`you at all. <span class="yellow">You are unable continue to inflate yourself by this means.</span>`);
deflate(PC);
} else if (harvest < 4) {
r.push(`you, so you'll have to settle for a meager two liters of the stuff.`);
r.push(`you, so <span class="yellow">you'll have to settle for a meager two liters of the stuff.</span>`);
PC.inflation = 1;
SetBellySize(PC);
} else {
r.push(`you, so you'll have to settle for a mere four liters of the stuff.`);
r.push(`you, so <span class="yellow">you'll have to settle for a mere four liters of the stuff.</span>`);
PC.inflation = 2;
SetBellySize(PC);
}
} else if (PC.inflation === 2 && harvest < 4) {
r.push(`${cow.slaveName} is having trouble producing enough ${PC.inflationType} to satisfy`);
if (harvest < 2) {
r.push(`you at all. <span class="yellow">You are unable continue to inflate yourself by this means.</span>`);
deflate(PC);
} else {
r.push(`you, so you'll have to settle for a mere two liters of the stuff.`);
r.push(`you, so <span class="yellow">you'll have to settle for a mere two liters of the stuff.</span>`);
PC.inflation = 1;
SetBellySize(PC);
}
} else if (PC.inflation === 1 && harvest < 2) {
r.push(`you at all. <span class="yellow">You are unable continue to inflate yourself by this means.</span>`);
r.push(`${cow.slaveName} is having trouble producing enough ${PC.inflationType} to satisfy you at all. <span class="yellow">You are unable continue to inflate yourself by this means.</span>`);
deflate(PC);
}
} else if (PC.inflationType === "undigested food" && PC.diet !== PCDiet.WEANING) {
if (PC.digestiveSystem === "normal") { // successfully weaned - backup in case cheating player skips weaningDuration 10-13 period
......
......@@ -16,7 +16,9 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
if (PC.pubertyXX === 0 || PC.pubertyXY === 0) {
puberty();
}
// Period stuff will go here, remember progenitor mod causes periods to be heavy.
if (V.menstruation && (V.PC.ovaries > 0 || V.PC.mpreg > 0)) {
period();
}
r.push(App.EndWeek.Player.pregnancy());
if (PC.bellyFluid >= 100) {
r.push(App.EndWeek.Player.inflation());
......@@ -568,12 +570,13 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
if (PC.energy.isBetween(0, 41)) {
PC.energy += 10;
}
// in the future, improve this to hide puberty up until period?
r.push(`After several days of unrelenting pain in your stomach, you have your first period.`);
if (PC.genes === GenderGenes.FEMALE) {
r.push(`<span class="puberty">You're a woman now.</span>`);
} else {
r.push(`<span class="puberty">Your ladyparts have matured</span> and are now fully functional.`);
if (V.menstruation === 0) {
r.push(`After several days of unrelenting pain in your stomach, you have your first period.`);
if (PC.genes === GenderGenes.FEMALE) {
r.push(`<span class="puberty">You're a woman now.</span>`);
} else {
r.push(`<span class="puberty">Your ladyparts have matured</span> and are now fully functional.`);
}
}
if (PC.geneticQuirks.gigantomastia === 3 && random(1, 100) < PC.hormoneBalance) {
PC.geneticQuirks.gigantomastia = 2;
......@@ -588,7 +591,7 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
}
}
} else if (PC.physicalAge >= PC.pubertyAgeXX - 1 && PC.hormoneBalance > 20 && PC.energy < 30) {
r.push(`You feel a little <span class="libido inc">tingly</span> inside whenever you notice a mans bulge or catch sight of a slave's penis.`);
r.push(`You feel a little <span class="libido inc">tingly</span> inside whenever you notice a man's bulge or catch sight of a slave's penis.`);
PC.energy++;
}
}
......@@ -626,6 +629,31 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
}
}
function period() {
// TODO: progenitor mod causes periods to be heavy.
if (V.PC.ovaries === 0 || V.PC.vagina >= 0) { // catch for womb but no vagina
if (V.PC.fertPeak === 2) {
if (PC.fertKnown === 0 && V.PC.pubertyXX === 1) {
r.push(`After several days of unrelenting pain in your stomach, <span class="period">you have your first period.</span>`);
if (PC.genes === GenderGenes.FEMALE) {
r.push(`<span class="puberty">You're a woman now.</span>`);
} else {
r.push(`<span class="puberty">Your ladyparts have matured</span> and are now fully functional.`);
}
PC.fertKnown = 1;
} else {
r.push(`<span class="period">You had your period;</span> sex should be risk free for a couple weeks.`);
}
} else if (PC.fertKnown === 0 && PC.fertLate < 0) {
if (PC.fertLate === -1) {
r.push(`You missed your period; it's probably just late.`);
} else {
r.push(`Your period is ${Math.abs(PC.fertLate)} weeks late; you might be pregnant...`);
}
}
}
}
function bellySagging() {
if (PC.belly >= 1000000) {
if (PC.bellySag < 50) {
......
......@@ -354,7 +354,7 @@ App.EndWeek.Player.pregnancy = function(PC = V.PC) {
}
}
} else if (boobSize < boobTarget) {
if (PC.weight <= 35 && random(random(1, 100) > 50)) {
if (PC.weight <= 35 && random(1, 100) > 50) {
r.push(`You can't help but <span class="change negative">put on a little baby weight</span> due to your increased appetite.`);
PC.weight += 1;
}
......
......@@ -100,7 +100,7 @@ App.EndWeek.spaReport = function() {
r.push(`${He}'s so intelligent ${he} can suss out the cause of slaves' emotional issues and counsel them effectively.`);
idleBonus++;
}
if (isFertile(S.Attendant) || (S.Attendant.bellyPreg >= 1500) || (S.Attendant.counter.birthsTotal > 0) || (S.Attendant.bellyImplant >= 1500)) {
if (isFertile(S.Attendant, true) || (S.Attendant.bellyPreg >= 1500) || (S.Attendant.counter.birthsTotal > 0) || (S.Attendant.bellyImplant >= 1500)) {
r.push(`${He} has a natural mothering instinct and really makes ${his} charges feel at home.`);
idleBonus++;
healthBonus++;
......
......@@ -1033,6 +1033,19 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
r.push(`</span> to serve ${his} owner.`);
slave.devotion += 2;
}
} else if (V.menstruation) {
if (isFertile(slave) && slave.fertPeak === 2) {
r.push(`${He} has been acting moody this week;`);
if (slave.devotion < slave.oldDevotion) {
r.push(`<span class="devotion dec">every little slight is a massively overblown.</span>`);
slave.devotion -= Math.trunc((slave.oldDevotion - slave.devotion) / 2);
} else if (slave.trust > slave.oldTrust) {
r.push(`${he} <span class="trust inc">latches on to even the most trivial gestures of support.</span>`);
slave.trust += Math.trunc((slave.oldTrust - slave.trust) / 2);
} else {
r.push(`you managed to successfully navigate the emotional minefield.`);
}
}
}
if (slave.sexualQuirk === SexualQuirk.UNFLINCHING) {
if (slave.devotion <= 95) {
......
......@@ -57,7 +57,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
deflate(slave);
} else if (slave.bellyImplant >= 1500) {
if (slave.inflation > 1) {
r.push(`Due to the mounting pressure from ${his} filled abdominal implant, ${he} can no longer fill ${himself} as large as ${he} used to.`);
r.push(`Due to the mounting pressure from ${his} filled abdominal implant, <span class="yellow">${he} can no longer fill ${himself} as large as ${he} used to.</span>`);
slave.inflation = 1;
SetBellySize(slave);
} else {
......@@ -66,7 +66,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
}
} else if (slave.bellyPreg >= 1500) {
if (slave.inflation > 1) {
r.push(`Due to the mounting pressure from ${his} growing pregnancy, ${he} can no longer fill ${himself} as large as ${he} used to.`);
r.push(`Due to the mounting pressure from ${his} growing pregnancy, <span class="yellow">${he} can no longer fill ${himself} as large as ${he} used to.</span>`);
slave.inflation = 1;
SetBellySize(slave);
} else {
......@@ -76,22 +76,32 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
} else if ((slave.inflationType === InflationLiquid.MILK || slave.inflationType === InflationLiquid.CUM) && slave.inflationMethod === 3) {
const harvest = (slave.inflationType === InflationLiquid.MILK) ? Math.trunc(milkAmount(cow) / 14) : Math.trunc(cumAmount(cow) / 70);
if (slave.inflation === 3 && harvest < 8) {
r.push(`${cow.slaveName} is having trouble producing the requested amount of ${slave.inflationType}`);
r.push(`${cow.slaveName} is having trouble producing the requested amount of`);
if (harvest < 2) {
r.push(`needed to even fill ${him}. <span class="yellow">${His} inflation regimen has been ended.</span>`);
r.push(`${slave.inflationType} needed to even fill ${him}. <span class="yellow">${His} inflation regimen has been ended.</span>`);
deflate(slave);
} else if (harvest < 4) {
r.push(`so ${his} serving has been reduced to a mere two liters.`);
r.push(`${slave.inflationType}, so <span class="yellow">${his} serving has been reduced to a mere two liters.</span>`);
slave.inflation = 1;
SetBellySize(slave);
} else {
r.push(`so ${his} serving has been reduced to four liters.`);
r.push(`${slave.inflationType}, so <span class="yellow">${his} serving has been reduced to four liters.</span>`);
slave.inflation = 2;
SetBellySize(slave);
}
} else if (slave.inflation === 2 && harvest < 4) {
r.push(`${cow.slaveName} is having trouble producing the requested amount of`);
if (harvest < 2) {
r.push(`needed to even fill ${him}. <span class="yellow">${His} inflation regimen has been ended.</span>`);
r.push(`${slave.inflationType} needed to even fill ${him}. <span class="yellow">${His} inflation regimen has been ended.</span>`);
deflate(slave);
} else {
r.push(`so ${his} serving has been reduced to two liters.`);
r.push(`${slave.inflationType}, so <span class="yellow">${his} serving has been reduced to two liters.</span>`);
slave.inflation = 1;
SetBellySize(slave);
}
} else if (slave.inflation === 1 && harvest < 2) {
r.push(`and can't even give ${him} a simple meal. <span class="yellow">${His} inflation regimen has been ended.</span>`);
r.push(`${cow.slaveName} is having trouble producing the requested amount of ${slave.inflationType}, and can't even give ${him} a simple meal. <span class="yellow">${His} inflation regimen has been ended.</span>`);
deflate(slave);
}
}
}
......
......@@ -1137,6 +1137,9 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
}
}
}
if (V.menstruation === 1) {
slave.fertPeak = 1;
}
}
}
if (slave.balls > 0 && slave.ballType !== "sterile") {
......
......@@ -797,7 +797,11 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
if (isFertile(slave)) {
needToBreed(slave);
}
if (canGetPregnant(slave) && ((slave.assignment === Job.DAIRY && V.dairyPregSetting === 0) || slave.assignment !== Job.DAIRY)) {
/**
* Prevent slaves that attempted manual impregnation via actions from running through standard impregnation events via V.temp.slaveIdsImpregnanted.
* Should only matter when unknown cycle is enabled as those events are 100% guarenteed to succeed otherwise.
*/
if (canGetPregnant(slave) && ((slave.assignment === Job.DAIRY && V.dairyPregSetting === 0) || slave.assignment !== Job.DAIRY) && !V.temp.slaveIdsImpregnanted?.has(slave.ID)) {
impregnation(slave);
}
if (slave.ovaImplant === OvaryImplantType.ASEXUAL && isFertile(slave) && (slave.preg === 0 || (slave.preg >= 0 && slave.geneticQuirks.superfetation === 2))) {
......
......@@ -1156,7 +1156,11 @@ App.EndWeek.Rules.consentDiscoversFetish = function(slave) {
}
App.UI.DOM.appendNewElement("span", el, `${he}'s an anal slut!`, ["lightcoral"]);
} else if (slave.fetish === Fetish.BOOBS) {
el.append(`fondles ${his} breasts, and ${he} loves it; `);
if (slave.boobs >= 300) {
el.append(`fondles ${his} breasts, and ${he} loves it; `);
} else {
el.append(`plays with ${his} nipples, and ${he} loves it; `);
}
App.UI.DOM.appendNewElement("span", el, `${he}'s a boob fetishist!`, ["lightcoral"]);
if (slave.lactation > 0) {
slave.lactationDuration = 2;
......
......@@ -20,6 +20,12 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
const incomeStats = gatherStatistics(slave);
// Does the customer know slave is fertile? Either biometrics is active or slave has a preg fetish and will advertise this.
const customerKnowsFertile = fertKnown(slave) &&
isFertile(slave) &&
slave.pregKnown === 0 &&
(slave.collar === "preg biometrics" || slave.fetish === Fetish.PREGNANCY || slave.sexualFlaw === SexualFlaw.BREEDER);
if (slave.assignment === Job.CLUB) {
// By being at the end, every slave after the first will get a bonus. By moving it up, the first can enjoy it too. slaveJobValues() checks Edo Revivalist, so here we are.
applyFSDecoration(slave);
......@@ -879,6 +885,12 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
slave.boobsMilk = 0;
}
if (V.menstruation === 1 && arcologyInfo.fsActive('FSRepopulationFocus') && customerKnowsFertile) {
if (slave.mpreg === 0 ? canDoVaginal(slave) && vaginalUse > 0 : canDoAnal(slave) && analUse > 0) {
r += ` ${His} fertility this week made ${his} ${slave.mpreg === 0 ? 'pussy' : 'ass'} extra inviting.`;
}
}
if (slave.bellyPreg >= 1500 || App.Data.misc.fakeBellies.includes(slave.bellyAccessory) || slave.bellyImplant >= 1500) {
if (arcologyInfo.fsActive('FSRepopulationFocus')) {
r += ` In the new culture of ${arcology.name}, ${his}`;
......@@ -1293,6 +1305,12 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
analUse = 0;
if (canDoAnal(slave)) {
analUse = (V.analUseWeight + (slave.skill.anal / 30) - slave.anus);
if (V.menstruation === 1 && slave.mpreg === 1) {
// Increase anal weight for those that want to impreg
if (FutureSocieties.isActive("FSRepopulationFocus") && customerKnowsFertile) {
analUse += 2;
}
}
if (analUse < 0) {
analUse = 0;
}
......@@ -1300,6 +1318,12 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
vaginalUse = 0;
if (canDoVaginal(slave)) {
vaginalUse = (V.vaginalUseWeight + (slave.skill.vaginal / 30) - slave.vagina);
if (V.menstruation === 1 && slave.mpreg === 0) {
// Increase vaginal weight for those that want to impreg
if (FutureSocieties.isActive("FSRepopulationFocus") && customerKnowsFertile) {
vaginalUse += 2;
}
}
if (vaginalUse < 0) {
vaginalUse = 0;
}
......
......@@ -745,7 +745,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
}
if (tutorForSlave(slave)) {
const skill = slave.skill[tutorKeyToSkillKey(tutorForSlave(slave))];
r += ` ${He} is currently ${Math.round((skill / Constant.MASTERED_XP) * 100)}% through training and should be finished in approximately ${numberWithPluralOne(Math.round((Constant.MASTERED_XP - skill) / lessonSkillIncrease(slave)), "week")}.`;
r += ` ${He} is currently ${Math.round((skill / Constant.MASTERED_XP) * 100)}% through training and should be finished in approximately ${numberWithPluralOne(Math.ceil((Constant.MASTERED_XP - skill) / lessonSkillIncrease(slave)), "week")}.`;
}
}
......
......@@ -26,6 +26,12 @@ App.SlaveAssignment.whore = function(slave) {
const incomeStats = gatherStatistics(slave);
// Does the customer know slave is fertile? Either biometrics is active or slave has a preg fetish and will advertise this.
const customerKnowsFertile = fertKnown(slave) &&
isFertile(slave) &&
slave.pregKnown === 0 &&
(slave.collar === "preg biometrics" || slave.fetish === Fetish.PREGNANCY || slave.sexualFlaw === SexualFlaw.BREEDER);
updateNonSlaveVariables(slave); // must be run before applyFSDecoration() or you will face NaNs
if (slave.assignment === Job.BROTHEL || slave.assignment === Job.MADAM) {
// By being at the end, every slave after the first will get a bonus. By moving it up, the first can enjoy it too. slaveJobValues() checks Edo Revivalist, so here we are.
......@@ -974,6 +980,12 @@ App.SlaveAssignment.whore = function(slave) {
slave.boobsMilk = 0;
}
if (V.menstruation === 1 && arcologyInfo.fsActive('FSRepopulationFocus') && customerKnowsFertile) {
if (slave.mpreg === 0 ? canDoVaginal(slave) : canDoAnal(slave)) {
r += ` ${His} fertility this week made ${his} ${slave.mpreg === 0 ? 'pussy' : 'ass'} extra inviting to customers.`;
}
}
if (slave.bellyPreg >= 1500 || App.Data.misc.fakeBellies.includes(slave.bellyAccessory) || slave.bellyImplant >= 1500) {
if (arcologyInfo.fsActive('FSRepopulationFocus')) {
r += ` In the new culture of ${arcology.name}, ${his} pregnancy makes ${him} a profitable whore`;
......@@ -1388,6 +1400,13 @@ App.SlaveAssignment.whore = function(slave) {
analUse = 0;
if (canDoAnal(slave)) {
analUse = (V.analUseWeight + (slave.skill.anal / 30) - slave.anus);
if (V.menstruation === 1 && slave.mpreg === 1) {
// Increase anal weight for those that want to impreg
if (FutureSocieties.isActive("FSRepopulationFocus") && customerKnowsFertile) {
analUse += 2;
}
}
if (analUse < 0) {
analUse = 0;
}
......@@ -1395,6 +1414,12 @@ App.SlaveAssignment.whore = function(slave) {
vaginalUse = 0;
if (canDoVaginal(slave)) {
vaginalUse = (V.vaginalUseWeight + (slave.skill.vaginal / 30) - slave.vagina);
if (V.menstruation === 1 && slave.mpreg === 0) {
// Increase vaginal weight for those that want to impreg
if (FutureSocieties.isActive("FSRepopulationFocus") && customerKnowsFertile) {
vaginalUse += 2;
}
}
if (vaginalUse < 0) {
vaginalUse = 0;
}
......
......@@ -133,6 +133,13 @@ App.EndWeek.slaveAssignmentReport = function() {
} else {
slave.need = slave.energy / 5;
}
if (V.menstruation && isFertile(slave, true)) {
if (slave.fertPeak === 0) {
slave.need *= 1.10;
} else if (V.PC.fertPeak === 2) {
slave.need *= 0.75;
}
}
if (slave.balls > 0 && slave.pubertyXY === 1 && slave.physicalAge <= (slave.pubertyAgeXY + 1) && (slave.physicalAge > slave.pubertyAgeXY) && slave.physicalAge < 18) {
slave.need *= 1.25;
}
......
......@@ -710,6 +710,8 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
slave.bellySagPreg = 3;
slave.preg = 0;
slave.pregWeek = -4;
slave.fertPeak = 2;
slave.fertKnown = 1;
slave.counter.birthsTotal = 2;
slave.counter.births = 2;
slave.skill.vaginal = 30;
......
......@@ -31,7 +31,7 @@ App.Events.RESSDesperateBreeder = class RESSDesperateBreeder extends App.Events.
execute(node) {
const [eventSlave] = this.actors.map(a => getSlave(a));
const {
He, he, His, his, him, himself, women, girl
He, he, His, his, him, himself, girl
} = getPronouns(eventSlave);
const {title: Master, say} = getEnunciation(eventSlave);
const {womenPC} = getPronouns(V.PC).appendSuffix("PC");
......@@ -112,7 +112,7 @@ App.Events.RESSDesperateBreeder = class RESSDesperateBreeder extends App.Events.
t.push(`If ${he} wants to do all the work, then you'll just lie back and enjoy the show.`);
if (canTalk(eventSlave)) {
t.push(`${He} squeels with glee,`);
t.push(`${He} squeals with glee,`);
t.push(`"${Spoken(eventSlave, `Sure thing! That first load should be just about ready, so we'll definitely be going again. You don't mind that, right ${Master}?`)}"`);
} else {
t.push(`${He} smiles happily as ${he} begins riding your dick.`);
......@@ -209,7 +209,7 @@ App.Events.RESSDesperateBreeder = class RESSDesperateBreeder extends App.Events.
} else {
t.push(`You can't help but notice ${his} limp dick flopping about and the precum being flung from its tip. You order ${him} to get it under control, but ${he} is too lost in ${his} growing pleasure to listen. Worse still, you're pinned under ${his} weight and have no chance of avoiding the coming ejaculation, nor any that follow it.`);
if (cumShot >= 1) {
t.push(`Like a loose firehose, ${he} sprays a jet semen hapazardly across you, ${himself} and ${his} surroundings, thoroughly soaking everything in range.`);
t.push(`Like a loose firehose, ${he} sprays a jet semen haphazardly across you, ${himself} and ${his} surroundings, thoroughly soaking everything in range.`);
} else if (cumShot >= .1) {
t.push(`You block most of ${his} cum from splashing across your face, but it still ends up all over the place.`);
} else {
......@@ -279,7 +279,7 @@ App.Events.RESSDesperateBreeder = class RESSDesperateBreeder extends App.Events.
} else {
t.push(`${He} glares ferociously at you, obviously intending to take what ${he} came here for by force if necessary.`);
}
t.push(`As ${he} slames down on your erect dick, it becomes clear just how far ${he} planned this out; ${he} has been keeping you on edge so that just a few quick bounces are all it takes to bring you to climax.`);
t.push(`As ${he} slams down on your erect dick, it becomes clear just how far ${he} planned this out; ${he} has been keeping you on edge so that just a few quick bounces are all it takes to bring you to climax.`);
if (canTalk(eventSlave)) {
t.push(`${He} leans in close and whispers,`);
t.push(`"${Spoken(eventSlave, `Thank you, ${Master}, you're the best,`)}"`);
......
......@@ -61,6 +61,7 @@ App.Events.RESSFirstPeriod = class RESSFirstPeriod extends App.Events.BaseEvent
}
r.push(`You check ${his} records and discover ${he} has very likely just become a woman.`);
slave.pubertyXX = 1;
slave.fertKnown = 1;
App.Events.addParagraph(node, r);
const choices = [];
......
......@@ -15,6 +15,9 @@ App.Intro.acquisition = function() {
if (V.freshPC === 1 || V.saveImported === 0) {
PCSetup();
}
if (V.policies.contraceptivesBan && V.PC.preg === -1) {
V.PC.preg = 0;
}
SetBellySize(V.PC);
parentSetup();
......@@ -354,6 +357,9 @@ App.Intro.acquisition = function() {
}
}
}
if (V.policies.contraceptivesBan && slave.preg === -1) {
slave.preg = 0;
}
}
if (slavesContributing !== 0) {
V.averageTrust = V.averageTrust / slavesContributing;
......@@ -540,6 +546,13 @@ App.Intro.acquisition = function() {
// if you don't have a vagina you can't prefer it
V.PC.preferredHole = 0;
}
if (V.menstruation === 1) {
V.PC.fertPeak = 1;
if (V.PC.pubertyXX === 1) {
// regardless of settings, the player is aware of their own menstrual cycle
V.PC.fertKnown = 1;
}
}
}
function parentSetup() {
......
......@@ -6,6 +6,12 @@ App.Intro.CustomSlaveTrade = function() {
/** @type {adjustModeValue} */
let adjustMode = "c2";
const dynamicModeSettings = {
travelFrictionExponent: App.Data.World.TravelFrictionExponent,
popScaleFactor: App.Data.World.PopScaleFactor,
debugView: 0,
};
const outerContainer = new DocumentFragment();
App.UI.DOM.appendNewElement("p", outerContainer, `When civilization turned upon itself, some countries readily took to enslaving their own. Others were raided by their neighbors for their desirable, and profitable, citizens. Which nationalities were most affected by the booming slave trade, and thus, likely to appear in your local slave markets?`);
......@@ -35,6 +41,7 @@ App.Intro.CustomSlaveTrade = function() {
const tabBar = new App.UI.Tabs.TabBar("customST");
tabBar.addTab("Adjust slave populations", "custom", customControls());
tabBar.addTab("Presets", "presets", presetControls());
tabBar.addTab("Dynamic World", "dynamic", worldMap());
tabBar.addTab("Import/Export", "import-export", importExport());
f.append(tabBar.render());
......@@ -46,7 +53,7 @@ App.Intro.CustomSlaveTrade = function() {
f.append(filters());
f.append(adjustSelector());
f.append(resetOptions());
f.append(sectionBreak());
f.append(App.UI.sectionBreak());
f.append(popControls());
return f;
}
......@@ -310,7 +317,7 @@ App.Intro.CustomSlaveTrade = function() {
}
/**
* @param {Map<string, Object.<string,number>>} presets
* @param {Map<string, Record<string,number>>} presets
* @returns {HTMLDivElement}
*/
function generatePresetLinks(presets) {
......@@ -358,6 +365,248 @@ App.Intro.CustomSlaveTrade = function() {
}
}
/**
* @returns {DocumentFragment}
*/
function worldMap() {
const f = new DocumentFragment();
App.UI.DOM.appendNewElement("p", f, `Slave trade is global, yet local trade is dominated by local availability. Where is your arcology located?`);
App.UI.DOM.appendNewElement("p", f, "Click anywhere on the map to create a new slave trade distribution.", ["note"]);
addMapSelector(f);
let options = new App.UI.OptionsGroup();
options.customRefresh(refresh);
options.addSlider("Travel Friction Exponent", -2, 1, "travelFrictionExponent", dynamicModeSettings)
.setStep(0.01)
.addEndLabels("Global", "Local").addTextBox();
options.addSlider("Population Scale Factor", 1, 50, "popScaleFactor", dynamicModeSettings)
.setStep(0.1)
.addEndLabels("Homogenous", "Diverse").addTextBox();
/** @type {Array<[string,number,number]>} */
const presets = [["Default (Balanced)", App.Data.World.TravelFrictionExponent, App.Data.World.PopScaleFactor],
["Greenland (Local)", 0.55, 10],
["Hyper Global Realistic", -2, 50], ["Hyper Global Equal", -2, 1]];
const o = options.addCustomOption("Presets");
for (const p of presets) {
o.addButton(p[0], () => {
dynamicModeSettings.travelFrictionExponent = p[1];
dynamicModeSettings.popScaleFactor = p[2];
refresh();
});
}
options.addOption("Debug View", "debugView", dynamicModeSettings)
.addValue("Off", 0).off()
.addValue("A", 1).on()
.addValue("B", 2).on();
f.append(options.render());
return f;
}
/**
* @param {DocumentFragment} container
*/
function addMapSelector(container) {
// SVG size
const width = 5760;
const height = 2880;
// TODO: it may make sense to create a new toplevel svg instead of cloning the entire svg
const svg = App.Data.Art.OtherSVG.get("world").cloneNode(true);
d3.select(svg)
.style("width", null)
.style("height", null)
.style("width", "100%")
.style("height", "100%")
.on("click", listen);
if (dynamicModeSettings.debugView > 0) {
const g = d3.select(svg)
.append("g")
.attr("transform", `scale(${width / 360}, -${height / 180}) translate(180, -90)`);
for (const p of App.Data.World.gridPoints()) {
const pop = App.Data.World.populationAt(p);
if (dynamicModeSettings.debugView === 1) {
if (pop > 0) {
const [lat, lon] = App.Data.World.gridPointToCoordinate(p);
const r = 10 * Math.log(pop / 10) / 100;
const nation = App.Data.World.nationIdAt(p);
let color = nation / 230 * (255 * 255 * 255);
g.append("circle").attr("cx", lon).attr("cy", lat).attr("r", r)
.attr("fill", "#" + Math.round(color).toString(16))
.attr("opacity", "0.5");
}
} else if (dynamicModeSettings.debugView === 2) {
if (p[0] === -1) {
const [lat, lon] = App.Data.World.gridPointToCoordinate(p);
g.append("circle").attr("cx", lon).attr("cy", lat).attr("r", 0.5).attr("fill", "red");
}
}
}
}
container.append(svg);
function listen(ev) {
let [x, y] = d3.pointer(ev);
let lon = (x / width) * 360 - 180;
let lat = ((y / height) * 180 - 90) * -1;
console.log(lon, lat);
const popScaleCache = createPopScaleCache();
populateFromCoordinates(lat, lon, popScaleCache);
normalizePopulation();
refresh();
}
}
/**
* Create scaling factors for each nationality based on total population
* @returns {{[key:string]:number}}
*/
function createPopScaleCache() {
/** @type {{[key:string]:number}} */
const cache = {};
// Build pop cache
for (const p of App.Data.World.gridPoints()) {
const nation = App.Data.World.nationAt(p);
const pop = App.Data.World.populationAt(p);
if (cache.hasOwnProperty(nation)) {
cache[nation] += pop;
} else {
cache[nation] = pop;
}
}
// Build pop scale cache
for (const p in cache) {
const pop = cache[p];
const a = Math.exp(dynamicModeSettings.popScaleFactor);
const scaled = a * Math.log1p(pop / a);
cache[p] = scaled / pop;
}
return cache;
}
/**
* Normalize population numbers and round them.
*/
function normalizePopulation() {
// Find max
let max = 0;
for (const n in V.nationalities) {
if (V.nationalities[n] > max) {
max = V.nationalities[n];
}
}
// Scale everything relative to max
// relMax controls at which point small populations are cut off. The larger, the smaller allowed populations
// will be. This does not change their relative likelihood.
const relMax = 1000.0;
const scale = relMax / max;
for (const n in V.nationalities) {
const val = Math.round(V.nationalities[n] * scale);
if (val > 0) {
V.nationalities[n] = val;
} else {
delete V.nationalities[n];
}
}
}
/**
* The distribution of population within the grid square must be uniform, but the data is essentially concentrated at the center point
* When the user clicks within a populated grid square, provide an alternative "distance" for that grid square's population
* Basically this stabilizes the weight of the grid you clicked inside, so clicking at the center or the edge of the cell doesn't affect it
* @param {number} lat Latitude of grid cell center
* @param {number} lon Longitude of grid cell center
*/
function altDist(lat, lon) {
const lonCell = 360 / App.Data.World.GridDimensions.width;
const latCell = 180 / App.Data.World.GridDimensions.height;
// take one third of the corner-to-corner measurement of the grid cell as our alternate distance.
// ideally we want the average distance between coordinates within the grid cell and the grid cell's center, but that's hard to calculate, and it's about the same.
// the actual value for a square is around 0.3826, and for an equilateral triangle is 0.289. grid cells are almost square near the equator and almost triangular near the poles.
return distanceInKmBetweenEarthCoordinates(lat - 0.5 * latCell, lon - 0.5 * lonCell, lat + 0.5 * latCell, lon + 0.5 * lonCell) / 3;
}
/**
* @param {number} lat
* @param {number} lon
* @param {{[key:string]:number}} popScaleData
*/
function populateFromCoordinates(lat, lon, popScaleData) {
V.nationalities = {};
const thisGP = App.Data.World.coordinateToGridPoint(lat, lon);
for (const p of App.Data.World.gridPoints()) {
const pop1 = 1; // or App.Data.World.populationAt(thisGP);
const pop2 = App.Data.World.populationAt(p);
if (pop2 > 0) {
const coords = App.Data.World.gridPointToCoordinate(p);
// is this the grid square we clicked inside of? use alternate distance if so
const clickedGrid = (p[0] === thisGP[0] && p[1] === thisGP[1]);
const dist = clickedGrid ? altDist(coords[0], coords[1]) : distanceInKmBetweenEarthCoordinates(lat, lon, coords[0], coords[1]);
let gravity = (pop1 * pop2) / (dist * dist * frictionForDistance(dist));
if (gravity < 1 && clickedGrid) {
gravity = 1; // always give _some_ population for the grid cell you clicked on, if it's inhabited
}
const nation = App.Data.World.nationAt(p);
// console.log(nation, gravity, Math.round(gravity));
addNationality(nation, popScaleData[nation] * gravity);
}
}
}
/**
* Compute travel friction factor for a given distance. More friction means less influence for distant grid points
* Ideally we'd also do some pathing between the grid points (traveling over an unpopulated ocean/desert should be more expensive), but skip that for now
* @param {number} distance In km
* @returns {number} greater 0
*/
function frictionForDistance(distance) {
// theoretically there should be a constant factor here too but it gets cancelled out during normalization, so we ignore it
const e = dynamicModeSettings.travelFrictionExponent; // 0 is no scaling
return Math.pow(distance, e);
}
// https://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates
/**
* @param {number} lat1
* @param {number} lon1
* @param {number} lat2
* @param {number} lon2
*/
function distanceInKmBetweenEarthCoordinates(lat1, lon1, lat2, lon2) {
const earthRadiusKm = 6371;
const dLat = degreesToRadians(lat2 - lat1);
const dLon = degreesToRadians(lon2 - lon1);
lat1 = degreesToRadians(lat1);
lat2 = degreesToRadians(lat2);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadiusKm * c;
}
/**
* @param {number} degrees
*/
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
function importExport() {
const f = new DocumentFragment();
const span = document.createElement("p");
......@@ -420,12 +669,6 @@ App.Intro.CustomSlaveTrade = function() {
$(container).empty().append(textArea, button);
}
function sectionBreak() {
const hr = document.createElement("hr");
hr.style.margin = "0";
return hr;
}
function refresh() {
return $(dynamicDiv).empty().append(dynamicContent());
}
......