diff --git a/src/com/lilithsthrone/game/character/GameCharacter.java b/src/com/lilithsthrone/game/character/GameCharacter.java index 3b0d1434cd..36f147ba1a 100644 --- a/src/com/lilithsthrone/game/character/GameCharacter.java +++ b/src/com/lilithsthrone/game/character/GameCharacter.java @@ -291,6 +291,8 @@ import com.lilithsthrone.utils.XMLSaving; import com.lilithsthrone.utils.colours.Colour; import com.lilithsthrone.utils.colours.PresetColour; +import com.lilithsthrone.utils.time.AstronomicalSign; +import com.lilithsthrone.utils.time.ChineseZodiac; import com.lilithsthrone.world.AbstractWorldType; import com.lilithsthrone.world.Cell; import com.lilithsthrone.world.World; @@ -341,7 +343,7 @@ public abstract class GameCharacter implements XMLSaving { protected LocalDateTime birthday; protected int ageAppearanceDifference; protected int ageAppearanceAbsolute; - + protected Occupation occupation; protected Set desiredJobs; protected Set personalityTraits; @@ -382,7 +384,7 @@ public abstract class GameCharacter implements XMLSaving { /** Clothing which has been temporarily unequipped as part of a scene which requires this character to be naked. */ private Map holdingClothing; private List creampieRetentionAreas; - + // Attributes, perks & status effects: protected Map attributes; @@ -458,7 +460,7 @@ public abstract class GameCharacter implements XMLSaving { protected AbstractWorldType slaveStationWorldType; protected Vector2i slaveStationLocation; - + // protected String slaveCategory = ""; // protected String slave_notes = ""; @@ -570,7 +572,7 @@ protected GameCharacter( } ageAppearanceDifference = 0; ageAppearanceAbsolute = 0; - + this.worldLocation = worldLocation; this.homeWorldLocation = worldLocation; location = new Vector2i(0, 0); @@ -635,7 +637,7 @@ protected GameCharacter( savedOutfits = new ArrayList<>(); creampieRetentionAreas = new ArrayList<>(); - + scars = new HashMap<>(); tattoos = new HashMap<>(); lipstickMarks = new HashMap<>(); @@ -920,7 +922,7 @@ public Element saveAsXML(Element parentElement, Document doc) { e.setTextContent(orifice.toString()); } } - + characterCoreInfo.getParentNode().insertBefore(comment, characterCoreInfo); @@ -1346,7 +1348,7 @@ public Element saveAsXML(Element parentElement, Document doc) { XMLUtil.addAttribute(doc, ssle, "x", String.valueOf(this.getSlaveStationLocation().getX())); XMLUtil.addAttribute(doc, ssle, "y", String.valueOf(this.getSlaveStationLocation().getY())); } - + Element slavesOwned = doc.createElement("slavesOwned"); slaveryElement.appendChild(slavesOwned); for(String slave : this.getSlavesOwned()) { @@ -1395,7 +1397,7 @@ public Element saveAsXML(Element parentElement, Document doc) { XMLUtil.addAttribute(doc, slaveAssignedJobs, "h"+String.valueOf(i), workHours[i].toString()); } } - + // Useful externally. XMLUtil.createXMLElementWithValue(doc, slaveryElement, "value", String.valueOf(this.getValueAsSlave(true))); @@ -1571,7 +1573,7 @@ public static void loadGameCharacterVariablesFromXML(GameCharacter character, St character.parserTarget = loadedParserTarget; Main.game.getCharacterUtils().appendToImportLog(log, "
Set parserTarget: " + loadedParserTarget); } - + // Name: Element nameElement = (Element) element.getElementsByTagName("name").item(0); String nameElementValue = nameElement.getAttribute("value"); @@ -1623,7 +1625,7 @@ public static void loadGameCharacterVariablesFromXML(GameCharacter character, St character.setAgeAppearanceAbsolute(Integer.valueOf(((Element)element.getElementsByTagName("ageAppearanceAbsolute").item(0)).getAttribute("value"))); Main.game.getCharacterUtils().appendToImportLog(log, "
Set ageAppearanceAbsolute: " + Integer.valueOf(((Element)element.getElementsByTagName("ageAppearanceAbsolute").item(0)).getAttribute("value"))); } - + // Birthday: try { int day = Integer.valueOf(((Element)element.getElementsByTagName("dayOfBirth").item(0)).getAttribute("value")); @@ -1940,7 +1942,7 @@ public static void loadGameCharacterVariablesFromXML(GameCharacter character, St } } } - + // Knows area map: try { if(Main.isVersionOlderThan(version, "0.2.10")) { @@ -2849,7 +2851,7 @@ public static void loadGameCharacterVariablesFromXML(GameCharacter character, St ex.printStackTrace(); } } - + for(int i=0; i<((Element) slaveryElement.getElementsByTagName("slavesOwned").item(0)).getElementsByTagName("slave").getLength(); i++){ Element e = ((Element)slaveryElement.getElementsByTagName("slave").item(i)); @@ -3453,7 +3455,7 @@ public static void loadGameCharacterVariablesFromXML(GameCharacter character, St character.setAgeAppearanceAbsolute(character.getAppearsAsAgeValue()); } } - + character.calculateStatusEffects(0); character.recalculateSleepHours(); for(String moveId : movesToEquip) { // Equip moves after status effects have been calculated: @@ -3490,7 +3492,7 @@ public void loadImages(boolean forceReload) { artworkList.clear(); artworkFolderName = folder; } - + if(!folder.isEmpty()) { if(!this.isUnique() || this.isPlayer()) { File f = new File("data/images/"+Main.game.getId()+"/characters/" + folder); @@ -3574,7 +3576,7 @@ public boolean isDoll() { } return getBody().isDoll(); } - + /** * @return true if this character is a doll, is working as a statue at the current game.getHourOfDay(), and has not been ordered to stop acting as a statue, determined from SlaveDialogue.isDollStatueInterrupted() */ @@ -3584,7 +3586,7 @@ public boolean isDollStatue() { && getSlaveJob(Main.game.getHourOfDay())==SlaveJob.DOLL_STATUE && this.isAtWork(); } - + public boolean isRaceConcealed() { return raceConcealed; } @@ -3610,7 +3612,7 @@ public String getParserTarget() { public void setParserTarget(String parserTarget) { this.parserTarget = parserTarget; } - + public String getMapIcon() { if(isRaceConcealed()) { return SVGImages.SVG_IMAGE_PROVIDER.getRaceUnknown(); @@ -3722,7 +3724,7 @@ public String getCharacterInformationScreen(boolean includePerkTree) { + this.getDescription()); boolean incubated = this.getIncubator()!=null; - + boolean append = false; if(Main.game.getPlayer().getId().equals(this.getMotherId())) { append = true; @@ -3731,12 +3733,12 @@ public String getCharacterInformationScreen(boolean includePerkTree) { ?" After being incubated by "+(this.getIncubator().isPlayer()?"yourself":this.getIncubator().getName())+", [npc.she] [npc.was] born" :" You gave birth to [npc.herHim]") +" on the "+this.getBirthdayString())); - + } else if(this.isPlayer() || (this.isPlayerKnowsName() && (this.getAffection(Main.game.getPlayer())>=AffectionLevel.POSITIVE_ONE_FRIENDLY.getMinimumValue() || this.isSlave()))) { append = true; if(this.isDoll()) { infoScreenSB.append(UtilText.parse(this, " [npc.She] [npc.was] created on the "+this.getBirthdayString())); - + } else { infoScreenSB.append(UtilText.parse(this, (incubated @@ -3759,14 +3761,21 @@ public String getCharacterInformationScreen(boolean includePerkTree) { :"") + " makes [npc.herHim] "+Util.intToString(this.getAgeValue())+" years old.")); } - + } else { infoScreenSB.append("."); } } + AstronomicalSign astronomicalSign = AstronomicalSign.getSignFromDate(this.getBirthday()); + infoScreenSB.append(UtilText.parse(this, + " [npc.HerPos] astronomical sign is " + +astronomicalSign.getName() + +" ("+astronomicalSign.getHtmlDisplay()+")" + +" and [npc.herPos] chinese zodiac animal is " + +ChineseZodiac.getSignFromDate(this.getBirthday()).getName()+".")); infoScreenSB.append("

"); - + infoScreenSB.append("
Relationships
" + "

"); String relationships = this.getRelationshipStrTo(Main.game.getPlayer()); @@ -3798,12 +3807,12 @@ public String getCharacterInformationScreen(boolean includePerkTree) { } } } - + if(relationshipsSB.length()>0) { infoScreenSB.append(relationshipsSB.toString()); infoScreenSB.append("
"); } - + int affection = (int)this.getAffection(Main.game.getPlayer()); infoScreenSB.append(AffectionLevel.getDescription(this, Main.game.getPlayer(), true)+" ("+(affection>0?"+":"")+affection+")"); @@ -3849,7 +3858,7 @@ public String getCharacterInformationScreen(boolean includePerkTree) { } } infoScreenSB.append("

"); - + infoScreenSB.append("
Personality
" + "

"); int i=0; @@ -4254,7 +4263,7 @@ protected void additionalBodySetup(Gender gender, AbstractRacialBody startingRac sexualOrientation = startingRace.getSexualOrientation(gender); initPerkTreeAndBackgroundPerks(); - + // This is the same as the age setting in CharacterUtil.randomiseBody() if(Main.game.isStarted() && startingSpeciesType.isDoesNotAge() && this.getAgeAppearanceAbsolute()==0) { this.setAgeAppearanceAbsolute(Math.min(this.getAgeValue(), 18+Util.random.nextInt(19))); // Range of real age to 36 @@ -4523,7 +4532,7 @@ public LocalDateTime getBirthday() { public void setBirthday(LocalDateTime birthday) { this.birthday = birthday; } - + public void setAge(int years) { this.birthday = LocalDateTime.of(Main.game.getStartingDate().getYear()-(years-MINIMUM_AGE), birthday.getMonth(), (birthday.getMonth()==Month.FEBRUARY&&birthday.getDayOfMonth()==29?28:birthday.getDayOfMonth()), 12, 0); } @@ -4560,9 +4569,9 @@ public int getAgeDifferenceUpperLimit() { } return 10; } - + // Age appearance based on difference: - + public int getAgeAppearanceDifference() { return ageAppearanceDifference; } @@ -4586,11 +4595,11 @@ public void setAgeAppearanceDifferenceToAppearAsAge(int targetedAge) { } // Age appearance based on an absolute value: - + public int getAgeAppearanceAbsolute() { return ageAppearanceAbsolute; } - + /** * This age appearance is an absolute value and does not advance based on the passage of time. * This character will always appear to be this age no matter the date. @@ -4601,13 +4610,13 @@ public int getAgeAppearanceAbsolute() { public void setAgeAppearanceAbsolute(int ageAppearanceAbsolute) { this.ageAppearanceAbsolute = ageAppearanceAbsolute; } - + public void incrementAgeAppearanceAbsolute(int increment) { setAgeAppearanceAbsolute(getAgeAppearanceAbsolute() + increment); } - + // Birthdays: - + public Month getBirthMonth() { return birthday.getMonth(); } @@ -4872,15 +4881,15 @@ public boolean isAbleToRefuseSexAsCompanion() { } // Slave stuff: - + public AbstractWorldType getSlaveStationWorldType() { return slaveStationWorldType; } - + public void setSlaveStationWorldType(AbstractWorldType slaveStationWorldType) { this.slaveStationWorldType = slaveStationWorldType; } - + public Vector2i getSlaveStationLocation() { return slaveStationLocation; } @@ -5048,7 +5057,7 @@ public float getBaseStaminaForSlaveJobs() { } return 24f; } - + public float getDailySlaveJobStamina() { float fatigue = getBaseStaminaForSlaveJobs(); for(SlaveJob job : workHours) { @@ -5071,7 +5080,7 @@ public void setSlaveJob24Hours(SlaveJob slaveJob) { workHours[i] = slaveJob; } } - + /** * @return true if this character has a sleeping-related status effect, or if in sex, is immobilised via ImmobilisationType.SLEEP */ @@ -5081,19 +5090,19 @@ public boolean isAsleep() { } return this.hasStatusEffect(StatusEffect.SLEEPING) || this.hasStatusEffect(StatusEffect.SLEEPING_HEAVY); } - + public void wakeUp() { // Joe Biden... Wake up... 9/11... this.removeStatusEffect(StatusEffect.SLEEPING); this.removeStatusEffect(StatusEffect.SLEEPING_HEAVY); } - + public boolean isAffectedBySleepingStatusEffect() { return this.isPlayer() // || (!this.isUnique() || (this.isSlave() && this.getOwner().isPlayer())) // This makes all NPCs sleep, often at very inconvenient times (such as during encounters and combat...) || (this.isSlave() && this.getOwner().isPlayer()) || Main.game.getPlayer().getFriendlyOccupants().contains(this.getId()); } - + public boolean isSleepingAtHour(int hour) { if(!sleepTimesInitialised) { recalculateSleepHours(); @@ -6408,7 +6417,7 @@ public int getExperience() { public void setExperience(int experience) { this.experience = experience; } - + public int getExperienceNeededForNextLevel(int level) { return level * 10; } @@ -6423,12 +6432,12 @@ public String incrementExperience(int increment, boolean withExtraModifiers) { return ""; } if(this.isDoll()) { - return UtilText.parse(this, + return UtilText.parse(this, "

" + "As [npc.nameIsFull] a sex doll, [npc.she] cannot gain experience..." +"

"); } - + int xpIncrement = (int) Math.max(0, increment * (withExtraModifiers&&this.hasTrait(Perk.JOB_WRITER, true)?1.25f:1)); if(withExtraModifiers @@ -7275,7 +7284,7 @@ public String addFetish(AbstractFetish fetish, boolean shortDescription) { + UtilText.parse(this, "[style.colourDisabled(As a sex doll, [npc.nameIsFull] unable to gain fetishes...)]") +"

"; } - + fetishes.add(fetish); applyFetishGainEffects(fetish); @@ -7658,7 +7667,7 @@ public boolean addStatusEffect(AbstractStatusEffect statusEffect, long lastTimeA getAppliedStatusEffect(statusEffect).setSecondsRemaining(secondsRemaining); return false; } - + if(this.isDoll() && (statusEffect==StatusEffect.PSYCHOACTIVE || statusEffect==StatusEffect.THIRST_QUENCHED @@ -7671,7 +7680,7 @@ public boolean addStatusEffect(AbstractStatusEffect statusEffect, long lastTimeA )) { return false; } - + statusEffects.add(new AppliedStatusEffect(statusEffect, lastTimeAppliedEffect, secondsPassed, secondsRemaining)); // Bonus attributes are not incremented for status effects, as they can vary while a character is under the effects of them. @@ -8527,8 +8536,8 @@ public int calculateSexTypeWeighting(SexType type, GameCharacter target, List getBackupVirginityLossMap() { return backupVirginityLossMap; } @@ -15631,10 +15640,10 @@ private String getAreaRevealWhileImmobilised(GameCharacter characterBeingReveale // Prioritise sleeping, then command, then any other: if(immobilisation.containsKey(ImmobilisationType.SLEEP)) { sb.append("[npc.Name] [npc.verb(remain)] asleep and thus unable to react as [npc2.namePos] "+areaString+" "+(plural?"are":"is")+" revealed."); - + } else if(immobilisation.containsKey(ImmobilisationType.COMMAND)) { sb.append("[npc.Name] [npc.verb(remain)] completely motionless and doesn't react as [npc2.namePos] "+areaString+" "+(plural?"are":"is")+" revealed."); - + } else { Entry entry = immobilisation.entrySet().iterator().next(); switch(entry.getKey()) { @@ -15663,7 +15672,7 @@ private String getAreaRevealWhileImmobilised(GameCharacter characterBeingReveale return UtilText.parse(characterReacting, characterBeingRevealed, sb.toString()); } } - + public String getAssRevealDescription(GameCharacter characterBeingRevealed, GameCharacter characterReacting, boolean locationSpecific) { if(locationSpecific) { switch(this.getGenitalArrangement()) { @@ -16553,7 +16562,7 @@ public String getMoundRevealDescription(GameCharacter characterBeingRevealed, Ga private static String generateGenericPenetrationDescription(GameCharacter characterPenetrating, SexAreaPenetration penetrationType, GameCharacter characterPenetrated, SexAreaInterface orifice) { boolean immobile = Main.sex.isCharacterImmobilised(characterPenetrating) && Main.sex.isCharacterInanimateFromImmobilisation(characterPenetrating); - + // Kissing: if(penetrationType == SexAreaPenetration.TONGUE && orifice == SexAreaOrifice.MOUTH) { if(immobile) { @@ -16570,7 +16579,7 @@ private static String generateGenericPenetrationDescription(GameCharacter charac "[npc.NamePos] [npc.do]n't move at all as [npc.her] mouth presses against [npc2.namePos] [npc2.lips+].", "Acting like an inanimate sex doll, [npc.name] [npc.verb(stay)] totally still and silent as [npc.she] [npc.verb(press)] [npc.her] [npc.lips+] against [npc2.namePos].")); } - + } else { switch(Main.sex.getSexPace(characterPenetrating)) { case DOM_GENTLE: @@ -16629,7 +16638,7 @@ private static String generateGenericPenetrationDescription(GameCharacter charac "[npc.NamePos] [npc.do]n't move at all as [npc.she] [npc.verb(continue)] holding [npc2.namePos] [npc2.hand+].", "Acting like an inanimate sex doll, [npc.name] [npc.verb(hold)] [npc2.namePos] [npc2.hand+] while staying totally still and silent.")); } - + } else { switch(Main.sex.getSexPace(characterPenetrating)) { case DOM_GENTLE: @@ -16997,7 +17006,7 @@ private static String generateGenericPenetrationDescription(GameCharacter charac "Acting like an inanimate sex doll, [npc.name] [npc.do]n't make a move even as [npc.her] "+penetratorName+" "+(orifice.isPlural()?"are":"is")+" "+insertion+target+orificeName+".", "Remaining still and silent, [npc.name] [npc.do]n't react to anything while keeping [npc.her] "+penetratorName+insertion+target+orificeName+".")); } - + } else { if(Math.random()<0.5f) { return UtilText.parse(characterPenetrating, characterPenetrated, @@ -17005,7 +17014,7 @@ private static String generateGenericPenetrationDescription(GameCharacter charac penetratedPrefix+" as [npc.name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+".", penetratedPrefix+" as [npc.name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+".", "[npc.Name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+", "+penetratedPostfix+".", - "[npc.Name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+", "+penetratedPostfix+".")); + "[npc.Name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+", "+penetratedPostfix+".")); } else { return UtilText.parse(characterPenetrating, characterPenetrated, UtilText.returnStringAtRandom( @@ -17015,8 +17024,8 @@ private static String generateGenericPenetrationDescription(GameCharacter charac "[npc.Name] "+penetratingQualifier+" "+penetratingAction+" [npc.her] "+penetratorName+" "+penetrationDescription+target+orificeName+", "+penetratedPostfix+".")); } } - - + + } private String getGenericInitialPenetration(GameCharacter characterPenetrating, SexAreaPenetration penetrationType, GameCharacter characterPenetrated, SexAreaInterface orifice) { @@ -17099,7 +17108,7 @@ private String getGenericInitialPenetration(GameCharacter characterPenetrating, "[npc.Name] [npc.verb(remain)] totally motionless as [npc.her] " +penetrationType.getName(characterPenetrating)+" pushes "+penetrationAdjective+" "+ownerName+" "+orifice.getName(characterPenetrated)+"."); } - + } else { return UtilText.parse(characterPenetrating, characterPenetrated, "[npc.Name] [npc.verb(let)] out [npc.a_moan+] as [npc.she] [npc.verb("+penetrationAdverb+" "+penetrationVerb+")] [npc.her] " @@ -17326,7 +17335,7 @@ public String getPenetrationDescription(boolean initialPenetration, GameCharacte boolean sleepingPenetrating = characterPenetrating.isAsleep(); boolean immobilePenetrated = Main.sex.isCharacterImmobilised(characterPenetrated) && Main.sex.isCharacterInanimateFromImmobilisation(characterPenetrated); boolean sleepingPenetrated = characterPenetrated.isAsleep(); - + if(penetrationType == SexAreaPenetration.FINGER && orifice == SexAreaPenetration.PENIS) { if(initialPenetration) { if(characterPenetrating.equals(characterPenetrated)) { @@ -17381,7 +17390,7 @@ public String getPenetrationDescription(boolean initialPenetration, GameCharacte "Silent and unmoving, [npc2.name] [npc2.verb(continue)] to act like an inanimate sex doll as [npc2.her] [npc2.foot+] is pushed onto [npc.namePos] [npc.cock+].", ParserTag.SEX_DESCRIPTION); } - + } else { return UtilText.parse(characterPenetrated, characterPenetrating, "[npc2.Name] [npc2.verb(let)] out [npc2.a_moan+] as [npc2.she] [npc2.verb(push)] [npc2.her] [npc2.foot+] into [npc.namePos] groin, before starting to rub and press down on [npc.her] [npc.cock+].", @@ -17398,7 +17407,7 @@ public String getPenetrationDescription(boolean initialPenetration, GameCharacte "Silent and unmoving, [npc2.name] [npc2.verb(continue)] to act like an inanimate sex doll as [npc2.her] [npc2.feet+] are pushed onto [npc.namePos] [npc.cock+].", ParserTag.SEX_DESCRIPTION); } - + } else { return UtilText.parse(characterPenetrated, characterPenetrating, "[npc2.Name] [npc2.verb(let)] out [npc2.a_moan+] as [npc2.she] [npc2.verb(wrap)] [npc2.her] [npc2.feet+] around [npc.namePos] [npc.cock+], before starting to give [npc.herHim] [npc2.a_footjob].", @@ -18072,7 +18081,7 @@ private static String getGenericInitialPenetrationDepthDescription( String orificeNameStandard = orifice.getName(characterPenetrated, true); boolean selfPenetration = characterPenetrating.equals(characterPenetrated); - + if(internalOrifice==SexAreaOrifice.MOUTH) { orificeName = "throat"; } @@ -18083,7 +18092,7 @@ private static String getGenericInitialPenetrationDepthDescription( boolean sleeping = characterPenetrating.isAsleep(); boolean immobile2 = Main.sex.isCharacterImmobilised(characterPenetrated) && Main.sex.isCharacterInanimateFromImmobilisation(characterPenetrated); boolean sleeping2 = characterPenetrated.isAsleep(); - + if(!Main.game.isInSex() || Main.sex.isDom(characterPenetrating)) { // The character doing the penetrating is the dom: if(length<=comfortable) { if(internalOrifice==SexAreaOrifice.MOUTH) { @@ -18187,8 +18196,8 @@ private static String getGenericInitialPenetrationDepthDescription( } else if(knotting) { sb.append("Desperate to push [npc.her] fat knot inside of [npc2.name], [npc.name] fully [npc.verb(hilt)] [npc.her] "+nameDesc+" [style.italicsMinorGood(as deep as physically possible)] " +(internalOrifice==SexAreaOrifice.MOUTH?"down":"into")+" [npc2.her] "+orificeName+"!"); - - + + } else if(characterPenetrated.hasFetish(Fetish.FETISH_SIZE_QUEEN) && Main.sex.getSexPace(characterPenetrated)!=SexPace.SUB_RESISTING && !immobile2) { @@ -18212,7 +18221,7 @@ private static String getGenericInitialPenetrationDepthDescription( } if(immobile2) { sb.append("."); - + } else if(characterPenetrated.hasFetish(Fetish.FETISH_MASOCHIST)) { sb.append(", causing [npc2.herHim] to let out a gargled masochistic scream of pleasure!"); @@ -18239,11 +18248,11 @@ private static String getGenericInitialPenetrationDepthDescription( } else { sb.append("."); } - + } else { if(characterPenetrated.hasFetish(Fetish.FETISH_MASOCHIST)) { sb.append(", causing [npc2.herHim] to let out a masochistic scream of pleasure"); - + } else { if(Main.game.isSadisticSexContent()) { sb.append(", causing [npc2.herHim] to let out a distressed scream"); @@ -18254,7 +18263,7 @@ private static String getGenericInitialPenetrationDepthDescription( if(isStomachBulging(characterPenetrating, penetrationType, characterPenetrated, orifice)) { sb.append(" as [npc2.her] stomach visibly bulges out"); } - + if(penetrationType==SexAreaPenetration.PENIS && Main.sex.getSexPace(characterPenetrated)!=SexPace.SUB_RESISTING) { if(characterPenetrated.hasFetish(Fetish.FETISH_MASOCHIST)) { sb.append(", [npc2.speechNoEffects("); @@ -18263,7 +18272,7 @@ private static String getGenericInitialPenetrationDepthDescription( "~Ow!~ You're so big! ~Ooh!~ Fuck!", "~Ooh!~ Fuck! ~Ow!~ You're so big!")); sb.append(")]"); - + } else { sb.append(", [npc2.speechNoEffects("); sb.append(UtilText.returnStringAtRandom( @@ -18388,7 +18397,7 @@ private static String getGenericInitialPenetrationDepthDescription( sb.append("Although [npc2.name] [npc2.verb(remain)] completely still, [npc.namePos] momentum forces [npc2.name] to take [npc.her] "+nameDesc+" [style.italicsMinorGood(as deep as physically possible)] " +(internalOrifice==SexAreaOrifice.MOUTH?"down":"into")+" [npc2.her] "+orificeName+"!"); } - + } else if(knotting) { if(internalOrifice==SexAreaOrifice.MOUTH) { sb.append("Desperate to take [npc.namePos] fat knot inside of [npc2.herHim], [npc2.name] [npc2.verb(take)] [npc.her] "+nameDesc+" [style.italicsMinorGood(as deep as physically possible)] down [npc2.her] "+orificeName+"!"); @@ -18495,7 +18504,7 @@ private static String getGenericOngoingPenetrationDepthDescription( String orificeName = orifice.getName(characterPenetrated); boolean immobilePenetrated = Main.sex.isCharacterImmobilised(characterPenetrated) && Main.sex.isCharacterInanimateFromImmobilisation(characterPenetrated); - + if(internalOrifice==SexAreaOrifice.MOUTH) { orificeName = "throat"; } @@ -18657,7 +18666,7 @@ protected String getVirginityExperienceDescription(GameCharacter character) { } return ""; } - + protected String getPenileVirginityLossDescription(GameCharacter characterPenetrated, SexAreaOrifice orifice){ return UtilText.parse(characterPenetrated, this, (characterPenetrated.equals(this) @@ -18893,7 +18902,7 @@ public String getStretchingFinishedDescription(SexAreaOrifice orifice) { boolean immobile = Main.sex.getAllParticipants().contains(this) && Main.sex.isCharacterImmobilised(this) && (Main.sex.getImmobilisationTypes(this).containsKey(ImmobilisationType.COMMAND) || Main.sex.getImmobilisationTypes(this).containsKey(ImmobilisationType.SLEEP)); - + String prefix = "[npc.Name] [npc.verb(let)] out [npc.a_moan+]"; if(immobile) { if(this.isAsleep()) { @@ -18904,7 +18913,7 @@ public String getStretchingFinishedDescription(SexAreaOrifice orifice) { } else if(orifice==SexAreaOrifice.MOUTH) { prefix = "[npc.Name] [npc.verb(let)] out a muffled [npc.moan]"; } - + switch(orifice) { case ARMPITS: break; @@ -19008,7 +19017,7 @@ public String ingestFluid(GameCharacter charactersFluid, AbstractSubspecies subs orificeIngestedThrough, millilitres); } - + public String ingestFluid(GameCharacter charactersFluid, Body body, AbstractFluidType fluidType, SexAreaOrifice orificeIngestedThrough, float millilitres) { FluidInterface fluid = null; switch(fluidType.getBaseType()) { @@ -19026,7 +19035,7 @@ public String ingestFluid(GameCharacter charactersFluid, Body body, AbstractFlui } public String ingestFluid(GameCharacter charactersFluid, AbstractSubspecies subspecies, AbstractSubspecies halfDemonSubspecies, FluidInterface fluid, SexAreaOrifice orificeIngestedThrough, float millilitres) { - return ingestFluid(charactersFluid, + return ingestFluid(charactersFluid, subspecies==Subspecies.HALF_DEMON ?Main.game.getCharacterUtils().generateHalfDemonBody(charactersFluid, charactersFluid==null?Gender.M_P_MALE:charactersFluid.getGender(), halfDemonSubspecies, false) :Main.game.getCharacterUtils().generateBody(charactersFluid, charactersFluid==null?Gender.M_P_MALE:charactersFluid.getGender(), subspecies, charactersFluid==null?RaceStage.GREATER:charactersFluid.getRaceStage()), @@ -19201,10 +19210,10 @@ public String ingestFluid(GameCharacter charactersFluid, Body cumBody, FluidInte // } fluidIngestionSB.append(this.rollForPregnancy(charactersFluid, cumBody, millilitres, newFluid.isCumVirile(), newFluid.getVirility(), Main.game.isInSex(), FertilisationType.NORMAL)); //GGameCharacter partner, Body partnerBody, float cumQuantity, boolean isPartnerVirile, float partnerVirility, boolean directSexInsemination, FertilisationType fertilisationType - + // if(charactersFluid!=null) { // fluidIngestionSB.append(this.rollForPregnancy(charactersFluid, millilitres, Main.game.isInSex())); -// +// // } else { // fluidIngestionSB.append(this.rollForPregnancy(cumBody, millilitres, Main.game.isInSex())); // } @@ -20655,7 +20664,7 @@ public void performImpregnationCheck(boolean directSexImpregnation) { // System.out.println(UtilText.parse(this, "2b: Rolling for [npc.name] impregnated by "+fs.getFluid().getName(null))); // } this.rollForPregnancy(partner, fs.getBody(), fs.getMillilitres(), fs.isCumVirile(), fs.getVirility(), directSexImpregnation, FertilisationType.NORMAL); - + // if(partner!=null) { // this.rollForPregnancy(partner, fs.getMillilitres(), directSexImpregnation); // } @@ -20664,7 +20673,7 @@ public void performImpregnationCheck(boolean directSexImpregnation) { } } } - + /** * When called, the next call to any rollForPregnancy() method will guarantee that this character gets pregnant, regardless of any status effects, fertility, or partner virility values. *
After the rollForPregnancy() method is called, the guarantee is reset. @@ -20701,7 +20710,7 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // public String rollForPregnancy(GameCharacter partner, float cumQuantity, boolean directSexInsemination, AbstractAttribute virilityAttribute) { // return rollForPregnancy(partner, cumQuantity, directSexInsemination, FertilisationType.NORMAL, Attribute.VIRILITY); // } -// +// // public String rollForPregnancy(GameCharacter partner, float cumQuantity, boolean directSexInsemination, FertilisationType fertilisationType, AbstractAttribute virilityAttribute) { // // Elemental handling: // if(this.isElemental()) { @@ -20715,7 +20724,7 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // + "[style.italicsMinorBad(Elementals cannot impregnate anyone!)]" // + "

"; // } -// +// // // Doll handling: // if(this.isDoll()) { // return PregnancyDescriptor.NO_CHANCE.getDescriptor(this, partner, directSexInsemination) @@ -20728,22 +20737,22 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // + "[style.italicsMinorBad(Dolls cannot impregnate anyone!)]" // + "

"; // } -// +// // if(isVisiblyPregnant()) { // return PregnancyDescriptor.ALREADY_PREGNANT.getDescriptor(this, partner, directSexInsemination); // } // if(this.getIncubationLitter(SexAreaOrifice.VAGINA)!=null) { // return PregnancyDescriptor.ALREADY_PREGNANT_EGGS.getDescriptor(this, partner, directSexInsemination); // } -// +// // float pregnancyChance = 0.1f; -// +// // boolean partnerVirile = partner.isVirile(virilityAttribute); // boolean selfFertile = this.isFertile(); -// +// // if(!partnerVirile || !selfFertile || !isAbleToBeImpregnated()) { // pregnancyChance = 0; -// +// // } else if(isAbleToBeImpregnated()) { // pregnancyChance += (partner.getAttributeValue(virilityAttribute)/100f)/2f; // pregnancyChance += (getAttributeValue(Attribute.FERTILITY)/100f)/2f; @@ -20753,14 +20762,14 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // pregnancyChance = 1; // guaranteePregnancyOnNextRoll = false; // } -// +// // PregnancyPossibility pregPoss = new PregnancyPossibility(this.getId(), partner.getId(), pregnancyChance); -// +// // this.addPotentialPartnerAsMother(pregPoss); // partner.addPotentialPartnerAsFather(pregPoss); -// +// // String pregnancyDescription = PregnancyDescriptor.getPregnancyDescriptorBasedOnProbability(pregnancyChance).getDescriptor(this, partner, directSexInsemination); -// +// // // Now roll for pregnancy: // if (!isPregnant()) { // if (!hasStatusEffect(StatusEffect.PREGNANT_0)) { @@ -20768,7 +20777,7 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // } // if (pregnancyChance>0 && Math.random() <= pregnancyChance) { // AbstractRace litterSizeBasedOn = null; -// +// // if (this.getBodyMaterial() == BodyMaterial.SLIME) { // litterSizeBasedOn = Race.SLIME; // } else { @@ -20779,10 +20788,10 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // litterSizeBasedOn = Optional.ofNullable(vaginaType.getRace()).orElseGet(this::getRace); // } // } -// +// // int minimumNumberOfChildren = litterSizeBasedOn.getNumberOfOffspringLow(); // int maximumNumberOfChildren = litterSizeBasedOn.getNumberOfOffspringHigh(); -// +// // // if(this.hasTraitActivated(Perk.FETISH_BROODMOTHER)) { // maximumNumberOfChildren *= 2; @@ -20790,16 +20799,16 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // if(partner.hasTraitActivated(Perk.FETISH_SEEDER)) { // maximumNumberOfChildren *= 2; // } -// +// // int numberOfChildren = minimumNumberOfChildren + Util.random.nextInt((maximumNumberOfChildren-minimumNumberOfChildren)+1); -// +// // if(this.hasStatusEffect(StatusEffect.BROODMOTHER_PILL)) { // numberOfChildren *= 2; // } // if(partner.hasStatusEffect(StatusEffect.BROODMOTHER_PILL)) { // numberOfChildren *= 2; // } -// +// // List offspring = new ArrayList<>(numberOfChildren); // for (int i = 0; i < numberOfChildren; i++) { // Add children here: // OffspringSeed os = new OffspringSeed(this, partner); @@ -20810,12 +20819,12 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // e.printStackTrace(); // } // } -// +// // pregnantLitter = new Litter(Main.game.getDateNow(), Main.game.getDateNow(), this, partner, fertilisationType, offspring); // this.resetAllPregnancyReactions(); // } // } -// +// // return pregnancyDescription; // } @@ -20829,11 +20838,11 @@ public boolean isVirile(AbstractAttribute virilityAttribute) { // public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSubspecies partnerHalfDemonSubspecies, float cumQuantity, boolean directSexInsemination) { // return rollForPregnancy(partnerSubspecies, partnerHalfDemonSubspecies, cumQuantity, directSexInsemination, FertilisationType.NORMAL); // } - + // public String rollForPregnancy(Body partnerBody, float cumQuantity, boolean directSexInsemination) { // return rollForPregnancy(partnerBody, cumQuantity, directSexInsemination, FertilisationType.NORMAL); // } - + /** * A variation for the pregnancy check when the owner of the cum is null. * @param partnerSubspecies The subspecies of the cum owner (if they were to exist). @@ -20851,24 +20860,24 @@ public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSub directSexInsemination, fertilisationType); } - + // public String rollForPregnancy(Body partnerBody, float cumQuantity, boolean directSexInsemination, FertilisationType fertilisationType) { // if(partnerBody.getRace()==Race.ELEMENTAL) { // return PregnancyDescriptor.NO_CHANCE.getDescriptor(this, null, directSexInsemination) // +"

[style.italicsMinorBad(Elementals cannot impregnate anyone!)]
[style.italicsDisabled(I will add support for impregnating/being impregnated by elementals later on!)]

"; // } -// +// // if(isVisiblyPregnant()) { // return PregnancyDescriptor.ALREADY_PREGNANT.getDescriptor(this, null, directSexInsemination); // } // if(this.getIncubationLitter(SexAreaOrifice.VAGINA)!=null) { // return PregnancyDescriptor.ALREADY_PREGNANT_EGGS.getDescriptor(this, null, directSexInsemination); // } -// +// // float pregnancyChance = 0.1f; // int baseVirility = 25; // boolean selfFertile = this.isFertile(); -// +// // if(selfFertile && isAbleToBeImpregnated()) { // pregnancyChance += (baseVirility/100f)/2f; // pregnancyChance += (getAttributeValue(Attribute.FERTILITY)/100f)/2f; @@ -20880,14 +20889,14 @@ public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSub // pregnancyChance = 1; // guaranteePregnancyOnNextRoll = false; // } -// +// // String partnerId = Subspecies.getIdFromSubspecies(partnerBody.getSubspecies()) + Main.game.getSecondsPassed(); // This should be fine as this method is never used in sex? :s // PregnancyPossibility pregPoss = new PregnancyPossibility(this.getId(), partnerId, pregnancyChance); -// +// // this.addPotentialPartnerAsMother(pregPoss); -// +// // String pregnancyDescription = PregnancyDescriptor.getPregnancyDescriptorBasedOnProbability(pregnancyChance).getDescriptor(this, null, directSexInsemination); -// +// // // Now roll for pregnancy: // if (!isPregnant()) { // if (!hasStatusEffect(StatusEffect.PREGNANT_0)) { @@ -20895,7 +20904,7 @@ public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSub // } // if (Math.random() <= pregnancyChance) { // AbstractRace litterSizeBasedOn = null; -// +// // if (this.getBodyMaterial() == BodyMaterial.SLIME) { // litterSizeBasedOn = Race.SLIME; // } else { @@ -20906,16 +20915,16 @@ public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSub // litterSizeBasedOn = Optional.ofNullable(vaginaType.getRace()).orElseGet(this::getRace); // } // } -// +// // int minimumNumberOfChildren = litterSizeBasedOn.getNumberOfOffspringLow(); // int maximumNumberOfChildren = litterSizeBasedOn.getNumberOfOffspringHigh(); -// +// // if (hasTraitActivated(Perk.FETISH_BROODMOTHER)) { // maximumNumberOfChildren *= 2; // } -// +// // int numberOfChildren = minimumNumberOfChildren + Util.random.nextInt((maximumNumberOfChildren-minimumNumberOfChildren)+1); -// +// // List offspring = new ArrayList<>(numberOfChildren); // for (int i = 0; i < numberOfChildren; i++) { // Add children here: // OffspringSeed os = new OffspringSeed(this, partnerBody); @@ -20926,23 +20935,23 @@ public String rollForPregnancy(AbstractSubspecies partnerSubspecies, AbstractSub // e.printStackTrace(); // } // } -// +// // pregnantLitter = new Litter(Main.game.getDateNow(), Main.game.getDateNow(), this, null, fertilisationType, offspring); // pregnantLitter.setFatherRace(partnerBody.getSubspecies()); // this.resetAllPregnancyReactions(); // } // } -// +// // return pregnancyDescription; // } - + //TODO new methods: // For null partner public String rollForPregnancy(Body partnerBody, float cumQuantity, boolean directSexInsemination, FertilisationType fertilisationType) { return rollForPregnancy(null, partnerBody, cumQuantity, true, 25, directSexInsemination, fertilisationType); } - + public String rollForPregnancy(GameCharacter partner, Body partnerBody, float cumQuantity, boolean directSexInsemination, FertilisationType fertilisationType, AbstractAttribute partnerVirilityAttribute) { if(partner==null) { return rollForPregnancy(partnerBody, cumQuantity, directSexInsemination, fertilisationType); @@ -20963,7 +20972,7 @@ public String rollForPregnancy(GameCharacter partner, Body partnerBody, float cu + "[style.italicsMinorBad(Elementals cannot impregnate anyone!)]" + "

"; } - + // Doll handling: if(this.isDoll()) { return PregnancyDescriptor.NO_CHANCE.getDescriptor(this, partner, directSexInsemination) @@ -20976,7 +20985,7 @@ public String rollForPregnancy(GameCharacter partner, Body partnerBody, float cu + "[style.italicsMinorBad(Dolls cannot impregnate anyone!)]" + "

"; } - + if(this.isVisiblyPregnant()) { return PregnancyDescriptor.ALREADY_PREGNANT.getDescriptor(this, partner, directSexInsemination); } @@ -21076,7 +21085,7 @@ public String rollForPregnancy(GameCharacter partner, Body partnerBody, float cu return pregnancyDescription; } - + public boolean isPregnant() { return pregnantLitter != null; } @@ -21706,7 +21715,7 @@ public boolean isCharactersCumInOrifice(SexAreaOrifice orifice, String character public boolean isAnyFluidStoredInOrifices() { return fluidsStoredMap.values().stream().anyMatch(l->!l.isEmpty()); } - + public List getAllFluidsStored() { List list = new ArrayList<>(); for(List stored : fluidsStoredMap.values()) { @@ -22586,7 +22595,7 @@ public String inventoryFullText() { public void resetInventory(boolean includeWeapons){ resetInventory(includeWeapons, false); } - + /** * First unequips all clothing into void, so that clothing effects are preserved. */ @@ -22695,7 +22704,7 @@ public String incrementEssenceCount(int increment, boolean withGainModifiers) { + "[style.colourDisabled(As [npc.sheIsFull] a sex doll, [npc.nameIsFull] unable to absorb essences...)]" + "

"); } - + String additional = ""; if(withGainModifiers && increment>0) { if(this.hasStatusEffect(StatusEffect.WEATHER_STORM) || this.hasStatusEffect(StatusEffect.WEATHER_STORM_VULNERABLE)) { @@ -23234,11 +23243,11 @@ public String useItem(AbstractItem item, GameCharacter target, boolean removingF public Map getAllWeaponsInInventory() { return inventory.getAllWeaponsInInventory(); } - + public String addWeapon(AbstractWeapon weapon) { return addWeapon(weapon, 1, false, false); } - + public String addWeapon(AbstractWeapon weapon, boolean removingFromFloor) { return addWeapon(weapon, 1, removingFromFloor, false); } @@ -24010,7 +24019,7 @@ public List getClothingCurrentlyEquipped() { public List getCreampieRetentionAreas() { return creampieRetentionAreas; } - + public boolean hasCreampieRetentionArea(SexAreaOrifice area) { return creampieRetentionAreas.contains(area); } @@ -24022,7 +24031,7 @@ public boolean addCreampieRetentionArea(SexAreaOrifice area) { public boolean removeCreampieRetentionArea(SexAreaOrifice area) { return creampieRetentionAreas.remove(area); } - + /** * @param characterViewing The character who is trying to view this character's inventory slots. * @return A Map of concealed InventorySlots, corresponding to a value of the List of clothing which is responsible for concealing that slot. @@ -24130,7 +24139,7 @@ private void applyEquipClothingEffects(AbstractClothing newClothing, InventorySl if(characterClothingEquipper!=null && newClothing.isSealed() && newClothing.getItemTags().contains(ItemTag.PROVIDES_KEY)) { characterClothingEquipper.addToUnlockKeyMap(this.getId(), slot); } - + if(this.getClothingCurrentlyEquipped().contains(newClothing)) { // If this has been removed in getCondomEquipEffects(), don't go through it. if(Main.game.isInSex() && Main.sex.getAllParticipants().contains(this)) { //TODO what even is this? @@ -24271,7 +24280,7 @@ private void applyUnequipClothingEffects(AbstractClothing clothing, InventorySlo if(Main.game.isInSex()) { Main.sex.clearAmountCummedOnSlot(this, slot); } - + if(Main.game.isInSex() && Main.sex.getAllParticipants().contains(this)) { if(clothing.getItemTags().contains(ItemTag.DILDO_OTHER)) { for(GameCharacter character : Main.sex.getCharacterOngoingSexArea(this, SexAreaPenetration.PENIS)) { @@ -26231,7 +26240,7 @@ public List getSelfTransformationRaces(boolean includeNoneRace) { if(this.getSubspecies()==Subspecies.HALF_DEMON) { races.add(this.getHalfDemonSubspecies().getRace()); } - + ArrayList unavailableRaces = Util.newArrayListOfValues(Race.ELEMENTAL, Race.SLIME); // Never have these TF options if(this.hasPerkAnywhereInTree(Perk.POWER_OF_LOVIENNE_2) || this.hasPerkAnywhereInTree(Perk.POWER_OF_LOVIENNE_2_DEMON)) { // I'm assuming you defeat Lovienne last @@ -26413,13 +26422,13 @@ public String postTransformationCalculation(boolean displayColourDiscovered) { if(this.getTrueSubspecies().isDoesNotAge() && this.getAgeAppearanceAbsolute()==0) { //System.out.println("Appears as: "+this.getAppearsAsAgeValue()+", Is: "+this.getAgeValue()); this.setAgeAppearanceAbsolute(this.getAppearsAsAgeValue()); - + } else if(!this.getTrueSubspecies().isDoesNotAge() && this.getAgeAppearanceAbsolute()!=0) { this.setAgeAppearanceDifferenceToAppearAsAge(this.getAppearsAsAgeValue()); this.setAgeAppearanceAbsolute(0); } } - + return postTFSB.toString(); } @@ -26651,7 +26660,7 @@ public Map getTattoos() { public boolean hasAnyTattoos() { return !tattoos.isEmpty(); } - + public void addTattoo(InventorySlot invSlot, Tattoo tattoo) { removeTattoo(invSlot); tattoos.put(invSlot, tattoo); @@ -27769,7 +27778,7 @@ public String setBodyMaterial(BodyMaterial type) { + "[npc.NamePos] body is still made out of [style.boldTfGeneric(slime)]!" + "

"); } - + if(type == BodyMaterial.SLIME) { // Slimes can't wear makeup: for(AbstractBodyCoveringType bct : BodyCoveringType.getAllMakeupTypes()) { @@ -27895,9 +27904,9 @@ public String setBodyMaterial(BodyMaterial type) { for(AbstractBodyCoveringType bct : BodyCoveringType.getAllSiliconeTypes()) { this.addBodyCoveringTypesDiscovered(bct); } - + AbstractBodyCoveringType baseCoveringType = BodyCoveringType.getMaterialBodyCoveringType(BodyMaterial.SILICONE, BodyCoveringCategory.MAIN_SKIN); - + String colourBasic = this.getCovering(baseCoveringType).getPrimaryColour().getName(); try { if(this.getCovering(baseCoveringType).getPrimaryColour().getRainbowColours()!=null) { @@ -27907,14 +27916,14 @@ public String setBodyMaterial(BodyMaterial type) { } } catch(Exception ex) { } - + body.setBodyMaterial(type); postTransformationCalculation(false); //TODO - + // Effects: - + // *** Physical:*** // //TODO move to racial body? // Standard attributes @@ -28030,7 +28039,7 @@ public String setBodyMaterial(BodyMaterial type) { if(!hadSpinneret) { this.setTailType(originalTail); } - + // *** Other:*** // // Birthday of a doll is when they're created this.setBirthday(Main.game.getDateNow()); @@ -28074,7 +28083,7 @@ public String setBodyMaterial(BodyMaterial type) { this.addSlavePermissionSetting(SlavePermission.SLEEPING, SlavePermissionSetting.SLEEPING_DEFAULT); this.addSlavePermissionSetting(SlavePermission.DIET, SlavePermissionSetting.FOOD_NORMAL); this.addSlavePermissionSetting(SlavePermission.EXERCISE, SlavePermissionSetting.EXERCISE_NORMAL); - + tfDescription = UtilText.parse(this, "

" + "[npc.NameIsFull] now an extremely high-quality sex doll, made entirely out of "+colourBasic+" silicone!" @@ -28089,10 +28098,10 @@ public String setBodyMaterial(BodyMaterial type) { + "- [npc.Her] orifices are able to accommodate extremely long penetrations!" + "" + "

"); - + return tfDescription; } - + if(type==BodyMaterial.FLESH) { tfDescription = UtilText.parse(this, "

" @@ -28441,7 +28450,7 @@ public String setBodyMaterial(BodyMaterial type) { } // The DOLL changes don't get to here - + body.setBodyMaterial(type); postTransformationCalculation(false); @@ -28798,7 +28807,7 @@ public String removeNippleOrificeModifier(OrificeModifier modifier) { public void clearNippleOrificeModifiers() { body.getBreast().getNipples().getOrificeNipples().clearOrificeModifiers(); } - + // Milk: public FluidMilk getMilk() { return body.getBreast().getMilk(); @@ -29173,7 +29182,7 @@ public String removeNippleCrotchOrificeModifier(OrificeModifier modifier) { public void clearNippleCrotchOrificeModifiers() { body.getBreastCrotch().getNipples().getOrificeNipples().clearOrificeModifiers(); } - + // Milk: public FluidMilk getMilkCrotch() { return body.getBreastCrotch().getMilk(); @@ -29707,7 +29716,7 @@ public String removeFaceOrificeModifier(OrificeModifier modifier) { public void clearFaceOrificeModifiers() { body.getFace().getMouth().getOrificeMouth().clearOrificeModifiers(); } - + // ------------------------------ Genital arrangement: ------------------------------ // @@ -30509,7 +30518,7 @@ public String addUrethraOrificeModifier(OrificeModifier modifier) { public String removeUrethraOrificeModifier(OrificeModifier modifier) { return getCurrentPenis().getOrificeUrethra().removeOrificeModifier(this, modifier); } - + // ------------------------------ Testicles: ------------------------------ // public AbstractBodyCoveringType getTesticlesCovering() { diff --git a/src/com/lilithsthrone/utils/time/AstronomicalSign.java b/src/com/lilithsthrone/utils/time/AstronomicalSign.java new file mode 100644 index 0000000000..6e1512e0ec --- /dev/null +++ b/src/com/lilithsthrone/utils/time/AstronomicalSign.java @@ -0,0 +1,77 @@ +package com.lilithsthrone.utils.time; + +import java.time.LocalDateTime; +import java.time.Month; + +public enum AstronomicalSign { + ARIES("aries", "♈", Month.MARCH, 21, Month.APRIL, 19), + TAURUS("taurus", "♉", Month.APRIL, 20, Month.MAY, 20), + GEMINI("gemini", "♊", Month.MAY, 21, Month.JUNE, 20), + CANCER("cancer", "♋", Month.JUNE, 21, Month.JULY, 22), + LEO("leo", "♌", Month.JULY, 23, Month.AUGUST, 22), + VIRGO("virgo", "♍", Month.AUGUST, 23, Month.SEPTEMBER, 22), + LIBRA("libra", "♎", Month.SEPTEMBER, 23, Month.OCTOBER, 21), + SCORPIO("scorpio", "♏", Month.OCTOBER, 22, Month.NOVEMBER, 21), + SAGITTARIUS("sagittarius", "♐", Month.NOVEMBER, 22, Month.DECEMBER, 21), + CAPRICORN("capricorn", "♑", Month.DECEMBER, 22, Month.JANUARY, 19), + AQUARIUS("aquarius", "♒", Month.JANUARY, 20, Month.FEBRUARY, 18), + PISCES("pisces", "♓", Month.FEBRUARY, 19, Month.MARCH, 20); + + private final String name; + private final String htmlDisplay; + private final Month startMonth; + private final int startDay; + private final Month endMonth; + private final int endDay; + + AstronomicalSign(String name, String htmlDisplay, Month startMonth, int startDay, Month endMonth, int endDay) { + this.name = name; + this.htmlDisplay = htmlDisplay; + this.startMonth = startMonth; + this.startDay = startDay; + this.endMonth = endMonth; + this.endDay = endDay; + } + + public String getName() { + return name; + } + + public String getHtmlDisplay() { + return htmlDisplay; + } + + public Month getStartMonth() { + return startMonth; + } + + public int getStartDay() { + return startDay; + } + + public Month getEndMonth() { + return endMonth; + } + + public int getEndDay() { + return endDay; + } + + @Override + public String toString() { + return getName(); + } + + public static AstronomicalSign getSignFromDate(LocalDateTime dateTime) { + int day = dateTime.getDayOfMonth(); + Month month = dateTime.getMonth(); + + for (AstronomicalSign sign : AstronomicalSign.values()) { + if ((month == sign.getStartMonth() && day>=sign.getStartDay()) + || (month == sign.getEndMonth() && day<=sign.getEndDay())) { + return sign; + } + } + return null; // This should be impossible... + } +} diff --git a/src/com/lilithsthrone/utils/time/ChineseCalendar.java b/src/com/lilithsthrone/utils/time/ChineseCalendar.java new file mode 100644 index 0000000000..8a4843f4de --- /dev/null +++ b/src/com/lilithsthrone/utils/time/ChineseCalendar.java @@ -0,0 +1,70 @@ +package com.lilithsthrone.utils.time; + +import java.time.LocalDateTime; + +public enum ChineseCalendar { + ONE(0, 2, 5), + TWO(1, 1, 24), + THREE(2, 2, 13), + FOUR(3, 2, 2), + FIVE(4, 1, 23), + SIX(5, 2, 10), + SEVEN(6, 1, 30), + EIGHT(7, 2, 17), + NINE(8, 2, 6), + TEN(9,1, 26), + ELEVEN(10, 2, 14), + TWELVE(11, 2, 4), + THIRTEEN(12, 1, 24), + FOURTEEN(13, 2, 11), + FIFTEEN(14, 1, 31), + SIXTEEN(15, 2, 19), + SEVENTEEN(16, 2, 8), + EIGHTEEN(17, 1, 27), + NINETEEN(18, 2, 15); + + private final int index; + private final int startMonth; + private final int startDay; + ChineseCalendar(int index, int startMonth, int startDay) { + this.index = index; + this.startMonth = startMonth; + this.startDay = startDay; + } + + public int getIndex() { + return index; + } + + public int getStartMonth() { + return startMonth; + } + + public int getStartDay() { + return startDay; + } + + public static ChineseCalendar getCycleFromIndex(int index) { + for(ChineseCalendar cycle : ChineseCalendar.values()) { + if (cycle.index == index) { + return cycle; + } + } + return null; + } + + public static int getCycleFromDate(LocalDateTime dateTime) { + int day = dateTime.getDayOfMonth(); + int month = dateTime.getMonthValue(); + // Remove 5 from the year to bring it inline with the chinese calendar https://en.wikipedia.org/wiki/Chinese_zodiac#Years + int index = (dateTime.getYear() - 5) % 19; + ChineseCalendar cycle = getCycleFromIndex(index); + if (cycle == null) { + return -1; + } + if (month < cycle.getStartMonth() || (month == cycle.getStartMonth() && day < cycle.getStartDay())) { + return index - 1; + } + return index; + } +} diff --git a/src/com/lilithsthrone/utils/time/ChineseZodiac.java b/src/com/lilithsthrone/utils/time/ChineseZodiac.java new file mode 100644 index 0000000000..179ca87fd1 --- /dev/null +++ b/src/com/lilithsthrone/utils/time/ChineseZodiac.java @@ -0,0 +1,56 @@ +package com.lilithsthrone.utils.time; + +import java.time.LocalDateTime; + +public enum ChineseZodiac { + RAT("rat", 0), + COW("cow", 1), + TIGER("tiger", 2), + RABBIT("rabbit", 3), + DRAGON("dragon", 4), + LAMIA("lamia", 5), + HORSE("horse", 6), + GOAT("goat", 7), + HARPY("harpy", 8), + DEMON("demon", 9), + DOG("dog", 10), + PIG("pig", 11); + + private final String name; + private final int index; + + ChineseZodiac(String name, int index) { + this.name = name; + this.index = index; + } + + public String getName() { + return name; + } + + public int getIndex() { + return index; + } + + @Override + public String toString() { + return getName(); + } + + public static ChineseZodiac getSignFromDate(LocalDateTime dateTime) { + // Remove 4 from the year to bring it inline with the chinese calendar https://en.wikipedia.org/wiki/Chinese_zodiac#Years + int signIndex = (dateTime.getYear() - 4) % 12; + if (ChineseCalendar.getCycleFromDate(dateTime) != (dateTime.getYear() - 5) % 19) { + signIndex--; + if (signIndex < 0) { + signIndex = 11; + } + } + for(ChineseZodiac sign : ChineseZodiac.values()) { + if (signIndex == sign.index) { + return sign; + } + } + return null; // This should be impossible... + } +}