def _PlanSimulationInternal(self, simulation: ReproductionShared.Simulation, reproductiveTimeMultiplier: float) -> None: quickMode = Settings.QuickMode.Get() # type: bool if not self.Fertilized: decayTick = ReproductionShared.ReproductiveMinutesToTicks( self.TimeRemaining, reproductiveTimeMultiplier) # type: int if decayTick <= 0: decayTick = 1 if simulation.RemainingTicks >= decayTick: simulation.Schedule.AddPoint(decayTick) else: if quickMode: if simulation.RemainingTicks >= 1: simulation.Schedule.AddPoint(1) else: implantationTick = ReproductionShared.ReproductiveMinutesToTicks( self.TimeUntilImplantation, reproductiveTimeMultiplier) # type: int if simulation.RemainingTicks >= implantationTick: simulation.Schedule.AddPoint(implantationTick)
def _SimulateInternal (self, simulation: ReproductionShared.Simulation, ticks: int, reproductiveTimeMultiplier: typing.Union[float, int]) -> None: ageTicks = ReproductionShared.ReproductiveMinutesToTicks(self.Age, reproductiveTimeMultiplier) # type: typing.Union[float, int] decayTick = ReproductionShared.ReproductiveMinutesToTicks(self.Lifetime, reproductiveTimeMultiplier) # type: typing.Union[float, int] decaying = False # type: bool decayed = False # type: bool if ageTicks < decayTick <= (ageTicks + ticks): decaying = True if decayTick <= (ageTicks + ticks): decayed = True if not decayed: decayingAmount = self.DecayingTicks(ticks, reproductiveTimeMultiplier) # type: int else: decayingAmount = self.SpermCount # type: int self.Age = ReproductionShared.TicksToReproductiveMinutes(ageTicks + ticks, reproductiveTimeMultiplier) self.SpermCount -= decayingAmount if decaying: if self.DecayedCallback is None: Debug.Log("Missing callback to be triggered on sperm decay.", This.Mod.Namespace, Debug.LogLevels.Warning, group = This.Mod.Namespace, owner = __name__, lockIdentifier = __name__ + ":" + str(Python.GetLineNumber())) else: self.DecayedCallback(self)
def _SimulateInternal (self, ticks: int) -> None: if ticks <= 0: return if self.TimeSinceCycleStart is None: return cycleReproductiveTimeMultiplier = _GetCycleReproductiveTimeMultiplier() # type: float lastTimeSinceCycleStart = self.TimeSinceCycleStart # type: float lastTickSinceCycleStart = ReproductionShared.ReproductiveMinutesToTicks(lastTimeSinceCycleStart, cycleReproductiveTimeMultiplier) # type: int nextTickSinceCycleStart = lastTickSinceCycleStart + ticks # type: int nextTimeSinceCycleStart = ReproductionShared.TicksToReproductiveMinutes(nextTickSinceCycleStart, cycleReproductiveTimeMultiplier) # type: float if self.ShowFertilityNotifications and self.Enabled: currentCycleState = self._GetCycle(lastTimeSinceCycleStart) # type: DotCycle currentTicksUntilOvulationStarts = ReproductionShared.GameMinutesToTicks(currentCycleState.GetTimeUntilPhaseStarts(CycleShared.MenstrualCyclePhases.Ovulation)) # type: int ticksBeforeOvulationToNotify = ReproductionShared.GameMinutesToTicks(_GetSpermHalfLifeTime()) # type: int if ticksBeforeOvulationToNotify < self.MinimumTicksBeforeOvulationToNotify: ticksBeforeOvulationToNotify = self.MinimumTicksBeforeOvulationToNotify if currentTicksUntilOvulationStarts > 0 and ticksBeforeOvulationToNotify < currentTicksUntilOvulationStarts: nextCycleState = self._GetCycle(nextTimeSinceCycleStart) # type: DotCycle nextTicksUntilOvulationStarts = ReproductionShared.GameMinutesToTicks(nextCycleState.GetTimeUntilPhaseStarts(CycleShared.MenstrualCyclePhases.Ovulation)) # type: int if ticksBeforeOvulationToNotify > nextTicksUntilOvulationStarts: UIDot.ShowFertilityNotification(self.TargetSimInfo) self.TimeSinceCycleStart = nextTimeSinceCycleStart
def _PrepareForSimulation( self, simulation: ReproductionShared.Simulation) -> None: super()._PrepareForSimulation(simulation) simulation.RegisterPhase( ReproductionShared.SimulationPhase( 30, self._PregnancyCachedTestSimulationPhase)) simulation.RegisterPhase( ReproductionShared.SimulationPhase( -30, self._PregnancyVisualsSimulationPhase))
def _CycleSimulationPhase (self, simulation: ReproductionShared.Simulation, ticks: int) -> None: reproductiveTimeMultiplier = self.ReproductiveTimeMultiplier # type: typing.Union[float, int] simulatingMinutes = ReproductionShared.TicksToReproductiveMinutes(ticks, reproductiveTimeMultiplier) # type: typing.Union[float, int] simulationMemoryKey = self.SimulationMemoryKey simulationMemoryExists = simulationMemoryKey in simulation.Memory # type: bool simulationMemory = simulation.Memory.get(simulationMemoryKey, CycleTrackerSimulationMemory()) # type: CycleTrackerSimulationMemory if self.TimeSinceLastCycle is not None: self.TimeSinceLastCycle += simulatingMinutes if self.CurrentCycle is not None: cycleTicksRemaining = ReproductionShared.ReproductiveMinutesToTicks(self.CurrentCycle.TimeRemaining, reproductiveTimeMultiplier) # type: typing.Union[float, int] if cycleTicksRemaining < ticks: Debug.Log("Simulation stepped over the end of a cycle by %s ticks, this may cause lost time for the tracking sim.\n%s" % (str(ticks - cycleTicksRemaining), self.DebugInformation), This.Mod.Namespace, Debug.LogLevels.Warning, group = This.Mod.Namespace, owner = __name__, lockIdentifier = __name__ + ":" + str(Python.GetLineNumber()), lockReference = self.TrackingSystem) self.CurrentCycle.Simulate(simulation, ticks, reproductiveTimeMultiplier) else: if simulationMemory.CycleStartTesting is None: Debug.Log("Expected the 'CycleStartTesting' to be in the simulation memory, but all we found was None.\n" + self.DebugInformation, This.Mod.Namespace, Debug.LogLevels.Warning, group = This.Mod.Namespace, owner = __name__, lockIdentifier = __name__ + ":" + str(Python.GetLineNumber()), lockReference = self.TrackingSystem) simulationMemory.CycleStartTesting = self.DoCycleStartTesting() elif not simulationMemoryExists: simulationMemory.CycleStartTesting = self.DoCycleStartTesting() if simulationMemory.CycleStartTesting.GetCanStart(): timeUntilCycleStart = simulationMemory.CycleStartTesting.GetTimeUntilStart() # type: typing.Optional[float] cycleStartPlanned = timeUntilCycleStart is not None # type: bool if cycleStartPlanned: ticksUntilCycleStart = ReproductionShared.ReproductiveMinutesToTicks(timeUntilCycleStart, reproductiveTimeMultiplier) # type: int else: ticksUntilCycleStart = 1 # type: int if ticksUntilCycleStart <= ticks: if cycleStartPlanned and ticksUntilCycleStart < ticks: Debug.Log("Simulation stepped over the start of a cycle by %s ticks, this may cause lost time for the tracking sim.\n%s" % (str(ticks - ticksUntilCycleStart), self.DebugInformation), This.Mod.Namespace, Debug.LogLevels.Warning, group = This.Mod.Namespace, owner = __name__, lockIdentifier = __name__ + ":" + str(Python.GetLineNumber()), lockReference = self.TrackingSystem) cycleTypeIdentifier = simulationMemory.CycleStartTesting.GetCycleTypeIdentifier() # type: str cycle = self.GenerateCycle(cycleTypeIdentifier) self.CycleStartTestingSeed = None simulationMemory.CycleStartTesting = None self.CurrentCycle = cycle else: simulationMemory.CycleStartTesting.IncreaseTimeSinceTest(simulatingMinutes) if not simulationMemoryExists: simulation.Memory[simulationMemoryKey] = simulationMemory
def _SimulateInternal(self, simulation: ReproductionShared.Simulation, ticks: int, reproductiveTimeMultiplier: float) -> None: simulatingMinutes = ReproductionShared.TicksToReproductiveMinutes( ticks, reproductiveTimeMultiplier) # type: float gameMinutes = ReproductionShared.TicksToGameMinutes( ticks) # type: float effectGuide = self.BirthControlPillsEffectGuide # type: CycleGuides.BirthControlPillsEffectGuide currentNeed = self.Need # type: float nextNeed = self.Need # type: float if effectGuide.NeedPerGameMinute != 0: if effectGuide.NeedPerGameMinute > 0: if currentNeed != 1: nextNeed += gameMinutes * effectGuide.NeedPerGameMinute else: if currentNeed != 0: nextNeed += gameMinutes * effectGuide.NeedPerGameMinute nextNeed = min(max(nextNeed, 0), 1) currentEntrenchmentRate = effectGuide.EntrenchmentPerReproductiveMinute.Evaluate( currentNeed) # type: float nextEntrenchmentRate = effectGuide.EntrenchmentPerReproductiveMinute.Evaluate( nextNeed) # type: float averageEntrenchmentRate = ( currentEntrenchmentRate + nextEntrenchmentRate ) / 2 # type: float # This is probably only right if the curve is linear. currentEntrenchment = self.Entrenchment nextEntrenchment = self.Entrenchment if averageEntrenchmentRate != 0: if averageEntrenchmentRate > 0: if currentEntrenchment != 1: nextEntrenchment += simulatingMinutes * averageEntrenchmentRate else: if currentEntrenchment != 0: nextEntrenchment += simulatingMinutes * averageEntrenchmentRate nextEntrenchment = min(max(nextEntrenchment, 0), 1) gameMinutesSinceLastPill = self.GameMinutesSinceLastPill # type: float self.Need = nextNeed self.Entrenchment = nextEntrenchment if gameMinutesSinceLastPill is not None: self.GameMinutesSinceLastPill = gameMinutesSinceLastPill + gameMinutes
def GetSpermTrackerReproductiveTimeMultiplier() -> float: multiplier = ReproductionShared.GetGeneralReproductiveTimeMultiplier() if multiplier < SpermQuickModeMinimumTimeMultiplier: multiplier = SpermQuickModeMinimumTimeMultiplier return multiplier
def _PlanSimulation (self, simulation: ReproductionShared.Simulation) -> None: super()._PlanSimulation(simulation) reproductiveTimeMultiplier = self.ReproductiveTimeMultiplier # type: typing.Union[float, int] simulationMemory = CycleTrackerSimulationMemory() # type: CycleTrackerSimulationMemory if self.CurrentCycle is None: cycleStartTesting = self.DoCycleStartTesting() # type: CycleEvents.CycleStartTestingArguments if cycleStartTesting.GetCanStart(): timeUntilCycleStart = cycleStartTesting.GetTimeUntilStart() # type: typing.Optional[float] cycleStartPlanned = timeUntilCycleStart is not None # type: bool if cycleStartPlanned: ticksUntilCycleStart = ReproductionShared.ReproductiveMinutesToTicks(timeUntilCycleStart, reproductiveTimeMultiplier) # type: int else: ticksUntilCycleStart = 1 # type: int if simulation.RemainingTicks >= ticksUntilCycleStart: simulation.Schedule.AddPoint(ticksUntilCycleStart) simulationMemory.CycleStartTesting = cycleStartTesting else: self.CurrentCycle.PlanSimulation(simulation, reproductiveTimeMultiplier) simulation.Memory[self.SimulationMemoryKey] = simulationMemory
def _PrepareForSimulation( self, simulation: ReproductionShared.Simulation) -> None: super()._PrepareForSimulation(simulation) simulation.RegisterPhase( ReproductionShared.SimulationPhase(-15, self._HandlerSimulationPhase))
def _PlanSimulationInternal (self, simulation: ReproductionShared.Simulation, reproductiveTimeMultiplier: typing.Union[float, int]) -> None: decayTick = ReproductionShared.ReproductiveMinutesToTicks(self.TimeRemaining, reproductiveTimeMultiplier) # type: int if decayTick <= 0: decayTick = 1 if simulation.RemainingTicks >= decayTick: simulation.Schedule.AddPoint(decayTick)
def _PlanSimulation(self, simulation: ReproductionShared.Simulation) -> None: if self._cachedPregnancyTestGameMinutesRemaining is not None: ticksUntilPregnancyTestCacheReset = ReproductionShared.GameMinutesToTicks( self._cachedPregnancyTestGameMinutesRemaining) if simulation.RemainingTicks >= ticksUntilPregnancyTestCacheReset: simulation.Schedule.AddPoint(ticksUntilPregnancyTestCacheReset)
def DecayingTicks (self, ticks: int, reproductiveTimeMultiplier: typing.Union[float, int]) -> int: """ Get the amount of sperm cells that will decay within this many ticks. """ if not isinstance(ticks, (int,)): raise Exceptions.IncorrectTypeException(ticks, "ticks", (int,)) if not isinstance(reproductiveTimeMultiplier, (float, int)): raise Exceptions.IncorrectTypeException(reproductiveTimeMultiplier, "reproductiveTimeMultiplier", (float, int)) if ticks < 0: raise ValueError("The parameter 'ticks' cannot be less than 0.") if reproductiveTimeMultiplier <= 0: raise ValueError("The parameter 'reproductiveTimeMultiplier' cannot be less than or equal to 0.") if self.SpermCount == 0: return 0 if ticks == 0: return 0 currentAgeTicks = ReproductionShared.ReproductiveMinutesToTicks(self.Age, reproductiveTimeMultiplier) # type: int currentAge = ReproductionShared.TicksToReproductiveMinutes(currentAgeTicks, reproductiveTimeMultiplier) # type: typing.Union[float, int] # This is slightly faster than getting using the GetClosestPreciseReproductiveMinute function. nextAgeTicks = currentAgeTicks + ticks # type: typing.Union[float, int] nextAge = ReproductionShared.TicksToReproductiveMinutes(nextAgeTicks, reproductiveTimeMultiplier) # type: typing.Union[float, int] lifeTimeTick = ReproductionShared.ReproductiveMinutesToTicks(self.Lifetime, reproductiveTimeMultiplier) if nextAgeTicks >= lifeTimeTick: return self.SpermCount currentPercentageRemaining = 1.0 - self.LifetimeDistribution.CumulativeDistribution(currentAge) # type: typing.Union[float, int] nextPercentageRemaining = 1.0 - self.LifetimeDistribution.CumulativeDistribution(nextAge) # type: typing.Union[float, int] percentageRemainingChange = currentPercentageRemaining - nextPercentageRemaining # type: typing.Union[float, int] originalSpermCount = int(self.SpermCount / currentPercentageRemaining) # type: int decayingSpermCount = int(originalSpermCount * percentageRemainingChange) # type: int if decayingSpermCount < 0: Debug.Log("Calculated a decaying sperm count of less than zero (%s)." % decayingSpermCount, This.Mod.Namespace, Debug.LogLevels.Warning, group = This.Mod.Namespace, owner = __name__, lockIdentifier = __name__ + ":" + str(Python.GetLineNumber())) return int(originalSpermCount * percentageRemainingChange)
def _PregnancyCachedTestSimulationPhase( self, simulation: ReproductionShared.Simulation, ticks: int) -> None: simulatingGameMinutes = ReproductionShared.TicksToGameMinutes( ticks) # type: float if self._cachedPregnancyTestGameMinutesRemaining is not None: self._cachedPregnancyTestGameMinutesRemaining -= simulatingGameMinutes if self._cachedPregnancyTestGameMinutesRemaining <= 0: self._ResetCachedPregnancyTestResults()
def _SimulateInternal(self, simulation: ReproductionShared.Simulation, ticks: int, reproductiveTimeMultiplier: float) -> None: simulatingMinutes = ReproductionShared.TicksToReproductiveMinutes( ticks, reproductiveTimeMultiplier) # type: float quickMode = Settings.QuickMode.Get() # type: bool if not self.Fertilized: decaying = False # type: bool if self.Age < self.TimeRemaining <= (self.Age + simulatingMinutes): decaying = True if decaying: #TODO set age first? if self.DecayedCallback is None: Debug.Log( "Missing callback to be triggered on ovum decay.", This.Mod.Namespace, Debug.LogLevels.Warning, group=This.Mod.Namespace, owner=__name__, lockIdentifier=__name__ + ":" + str(Python.GetLineNumber())) else: self.DecayedCallback(self) else: if quickMode: implanting = True # type: bool self.Age = self.ImplantationTime else: implanting = False # type: bool if self.Age < self.TimeUntilImplantation <= ( self.Age + simulatingMinutes): implanting = True if implanting: if self.AttemptImplantationCallback is None: Debug.Log( "Missing callback to be triggered on ovum implantation attempt.", This.Mod.Namespace, Debug.LogLevels.Warning, group=This.Mod.Namespace, owner=__name__, lockIdentifier=__name__ + ":" + str(Python.GetLineNumber())) else: self.AttemptImplantationCallback(self) self.Age += simulatingMinutes
def InitiateReproductiveSystem( simInfo: sim_info.SimInfo) -> ReproductionShared.ReproductiveSystem: """ Create a blank reproductive system for this sim. If one already exists the existing one will be returned as opposed of creating a new one. :param simInfo: The target sim's info. :type simInfo: sim_info.SimInfo """ if not isinstance(simInfo, sim_info.SimInfo): raise Exceptions.IncorrectTypeException(simInfo, "simInfo", (sim_info.SimInfo, )) if SimHasSystem(simInfo): return GetSimSystem(simInfo) simReproductiveSystem = ReproductionShared.ReproductiveSystem(simInfo) RegisterReproductiveSystem(simReproductiveSystem) return simReproductiveSystem
def _SimulateInternal (self, simulation: ReproductionShared.Simulation, ticks: int, reproductiveTimeMultiplier: float) -> None: simulatingGameMinutes = ReproductionShared.TicksToGameMinutes(ticks) # type: float if self.BuffCoolDown is not None: self.BuffCoolDown -= simulatingGameMinutes if self.BuffCoolDown <= 0: self.BuffCoolDown = None if self.BuffCoolDown is not None and self.BuffCoolDown > 0: return if not self.AffectingSystem.SimInfo.is_instanced(): return # The game doesn't appear to allow us to add the buffs to non-instanced sims. if not simulation.LastTickStep: return cycleTracker = self.AffectingSystem.GetTracker(FemalesShared.CycleTrackerIdentifier) # type: typing.Optional[CycleTracker.CycleTracker] if cycleTracker is None: return currentCycle = cycleTracker.CurrentCycle # type: typing.Optional[CycleMenstrual.MenstrualCycle] if currentCycle is None: return if currentCycle.TypeIdentifier != CycleShared.MenstrualCycleTypeIdentifier: return buffSelectionTesting = self.DoBuffSelectionTesting() # type: CycleEvents.MenstrualEffectBuffSelectionTestingArguments selectedBuffType, hadValidSelections = buffSelectionTesting.SelectAppropriateBuff(currentCycle) # type: typing.Optional[typing.Type[BuffsMenstrual.MenstrualBuffBase]], bool if not hadValidSelections: return if selectedBuffType is None: self.ApplyBuffAbstainedEffects() return self.AffectingSystem.SimInfo.Buffs.add_buff_from_op(selectedBuffType, BuffsShared.ReproductiveSystemBuffReason.GetLocalizationString())
def _SimulateInternal(self, simulation: ReproductionShared.Simulation, ticks: int, reproductiveTimeMultiplier: float) -> None: simulatingMinutes = ReproductionShared.TicksToReproductiveMinutes( ticks, reproductiveTimeMultiplier) # type: float effectGuide = self.EmergencyContraceptivePillEffectGuide # type: CycleGuides.EmergencyContraceptivePillEffectGuide currentStrength = self.Strength # type: float nextStrength = self.Strength # type: float if effectGuide.StrengthPerReproductiveMinute != 0: if effectGuide.StrengthPerReproductiveMinute > 0: if currentStrength != 1: nextStrength += simulatingMinutes * effectGuide.StrengthPerReproductiveMinute else: if currentStrength != 0: nextStrength += simulatingMinutes * effectGuide.StrengthPerReproductiveMinute nextStrength = min(max(nextStrength, 0), 1) self.Strength = nextStrength
def _PlanUpdateCallback (self, owner, eventArguments: CycleEvents.PlanUpdateArguments) -> None: if not self.AffectingSystem.SimInfo.is_instanced(): return # The game doesn't appear to allow us to add the buffs to non-instanced sims. cycleTracker = self.AffectingSystem.GetTracker(FemalesShared.CycleTrackerIdentifier) # type: typing.Optional[CycleTracker.CycleTracker] if cycleTracker is None: return if cycleTracker.CurrentCycle is None: return currentCycle = cycleTracker.CurrentCycle # type: typing.Optional[CycleMenstrual.MenstrualCycle] if currentCycle.TypeIdentifier != CycleShared.MenstrualCycleTypeIdentifier: return if self.BuffCoolDown is not None and self.BuffCoolDown > 0: eventArguments.RequestTick(ReproductionShared.GameMinutesToTicks(self.BuffCoolDown)) return soonestBuffValidTick = None # type: typing.Optional[int] for menstrualBuff in BuffsMenstrual.GetAllMenstrualBuffs(): # type: BuffsMenstrual.MenstrualBuffBase ticksUntilBuffValid = menstrualBuff.CyclePhaseTest.TicksUntilValidPhase(currentCycle, cycleTracker.ReproductiveTimeMultiplier) # type: int if ticksUntilBuffValid is None: continue if soonestBuffValidTick is None or soonestBuffValidTick > ticksUntilBuffValid: soonestBuffValidTick = ticksUntilBuffValid if soonestBuffValidTick is None or soonestBuffValidTick <= 0: return eventArguments.RequestTick(soonestBuffValidTick)
def _GetPregnancyRate() -> float: reproductiveTimeMultiplier = Settings.PregnancySpeed.Get() # type: float pregnancyGuide = CycleGuides.HumanPregnancyGuide.Guide # type: CycleGuides.PregnancyGuide pregnancyTime = pregnancyGuide.PregnancyTime # type: float pregnancyGameTime = ReproductionShared.ReproductiveMinutesToGameMinutes( pregnancyTime, reproductiveTimeMultiplier) # type: float if pregnancyGameTime != 0: pregnancyRate = 100 / pregnancyGameTime # type: float else: Debug.Log( "Calculated a pregnancy game time to be 0 minutes, this is probably not intentional.", This.Mod.Namespace, Debug.LogLevels.Warning, group=This.Mod.Namespace, owner=__name__, lockIdentifier=__name__ + ":" + str(Python.GetLineNumber()), lockThreshold=1) pregnancyRate = 0 return pregnancyRate
def GetSpermProductionTrackerReproductiveTimeMultiplier() -> float: return ReproductionShared.GetGeneralReproductiveTimeMultiplier()
def GetHandlerTrackerReproductiveTimeMultiplier() -> float: return ReproductionShared.GetGeneralReproductiveTimeMultiplier()
def TicksSinceLastCycle (self) -> typing.Union[float, int, None]: """ The amount of game ticks since the last cycle ended. This will be None if the sim has not had a cycle yet. """ return ReproductionShared.ReproductiveMinutesToTicks(self.TimeSinceLastCycle, self.ReproductiveTimeMultiplier)
def TicksUntilValidPhase( self, testingCycle: CycleMenstrual.MenstrualCycle, reproductiveTimeMultiplier: float) -> typing.Optional[int]: """ Get the number of game ticks until the input cycle reaches a valid phase. This will return none if the cycle will never enter a valid phase. :param testingCycle: The menstrual cycle to find valid phases for. :type testingCycle: CycleMenstrual.MenstrualCycle :param reproductiveTimeMultiplier: The reproductive time multiplier used to simulate the testing cycle :type reproductiveTimeMultiplier: float """ if not isinstance(testingCycle, CycleMenstrual.MenstrualCycle): raise Exceptions.IncorrectTypeException( testingCycle, "testingCycle", (CycleMenstrual.MenstrualCycle, )) if len(self.PhaseStates) == 0: return 0 hasWhitelistedPhase = False # type: bool whitelistedTimes = list( ) # type: typing.List[typing.Tuple[float, float]] blacklistedTimes = list( ) # type: typing.List[typing.Tuple[float, float]] for phaseState in self.PhaseStates: if phaseState.MatchType == CyclePhaseTest.PhaseState.MatchTypes.Whitelist: hasWhitelistedPhase = True if testingCycle.GetPhaseCompleted(phaseState.Phase): continue timeUntilPhaseStart = testingCycle.GetTimeUntilPhaseStarts( phaseState.Phase) # type: float timeUntilPhaseEnd = testingCycle.GetTimeUntilPhaseEnds( phaseState.Phase) # type: float if timeUntilPhaseEnd < 0: Debug.Log( "A menstrual cycle indicated a phase was not complete, but its end time was %s minutes ago. Phase: %s" % (str(timeUntilPhaseEnd), str(phaseState.Phase)), This.Mod.Namespace, Debug.LogLevels.Warning, group=This.Mod.Namespace, owner=__name__, lockIdentifier=__name__ + ":" + str(Python.GetLineNumber())) continue phaseLength = timeUntilPhaseEnd - timeUntilPhaseStart # type: float if phaseLength <= 0: Debug.Log( "Calculated a menstrual cycle phase length as less than or equal to 0. Phase: %s" % str(phaseState.Phase), This.Mod.Namespace, Debug.LogLevels.Warning, group=This.Mod.Namespace, owner=__name__, lockIdentifier=__name__ + ":" + str(Python.GetLineNumber())) continue if phaseState.StartCompletion is None: listedStartTime = timeUntilPhaseStart else: listedStartTime = timeUntilPhaseStart + phaseLength * phaseState.StartCompletion if phaseState.EndCompletion is None: listedEndTime = timeUntilPhaseEnd else: listedEndTime = timeUntilPhaseStart + phaseLength * phaseState.EndCompletion if phaseState.MatchType == CyclePhaseTest.PhaseState.MatchTypes.Whitelist: whitelistedTimes.append((listedStartTime, listedEndTime)) elif phaseState.MatchType == CyclePhaseTest.PhaseState.MatchTypes.Blacklist: blacklistedTimes.append((listedStartTime, listedEndTime)) soonestValidTime = None # type: typing.Optional[float] def setTimeIfSooner(testingValidTime: float) -> None: nonlocal soonestValidTime if soonestValidTime is None: soonestValidTime = testingValidTime if soonestValidTime < testingValidTime: soonestValidTime = testingValidTime if hasWhitelistedPhase: for whitelistedStartTime, whitelistedEndTime in whitelistedTimes: # type: float, float if soonestValidTime is not None and whitelistedStartTime >= soonestValidTime: continue validTime = whitelistedStartTime # type: float intervalBlacklisted = False # type: bool for blackListedStartTime, blackListedEndTime in blacklistedTimes: # type: float, float if blackListedStartTime <= validTime and blackListedEndTime >= whitelistedEndTime: intervalBlacklisted = True break if blackListedStartTime <= validTime: validTime = blackListedEndTime if not intervalBlacklisted: setTimeIfSooner(max(validTime, 0.0)) if soonestValidTime == 0: break else: for blackListedStartTime, blackListedEndTime in blacklistedTimes: # type: float, float if soonestValidTime is not None and blackListedEndTime >= soonestValidTime: continue blacklistContinues = False # type: bool for otherBlackListedStartTime, otherBlackListedEndTime in blacklistedTimes: # type: float, float if otherBlackListedStartTime <= blackListedEndTime < otherBlackListedEndTime: blacklistContinues = True pass if not blacklistContinues: setTimeIfSooner(max(blackListedEndTime, 0.0)) if soonestValidTime == 0: break if soonestValidTime is None: return None else: return ReproductionShared.ReproductiveMinutesToTicks( soonestValidTime, reproductiveTimeMultiplier)
class DotInformation(Savable.SavableExtension): HostNamespace = This.Mod.Namespace MinimumTicksBeforeOvulationToNotify = ReproductionShared.GameMinutesToTicks(date_and_time.MINUTES_PER_HOUR * 8) def __init__ (self, targetSimInfoOrID: typing.Union[sim_info.SimInfo, int, None]): """ An object to save information for and handle interactions with the dot menstrual cycle tracker app. """ if not isinstance(targetSimInfoOrID, (sim_info.SimInfo, int)) and targetSimInfoOrID is not None: raise Exceptions.IncorrectTypeException(targetSimInfoOrID, "targetSimInfoOrID", (sim_info.SimInfo, int, None)) super().__init__() self._targetSimPointer = SimPointer.SimPointer() self._targetSimPointer.ChangePointer(targetSimInfoOrID) self._simulating = False # type: bool self.Enabled = False self.TrackingMode = TrackingMode.Cycle self.ShowFertilityNotifications = False self.TimeSinceCycleStart = None self.LastSimulatedTick = services.time_service().sim_now.absolute_ticks() encodeEnum = lambda value: value.name if value is not None else None decodeTrackingMode = lambda valueString: Parse.ParsePythonEnum(valueString, TrackingMode) self.RegisterSavableAttribute(Savable.StandardAttributeHandler("Enabled", "Enabled", self.Enabled)) self.RegisterSavableAttribute(Savable.StandardAttributeHandler("TrackingMode", "TrackingMode", self.TrackingMode, encoder = encodeEnum, decoder = decodeTrackingMode)) self.RegisterSavableAttribute(Savable.StandardAttributeHandler("ShowFertilityNotifications", "ShowFertilityNotifications", self.ShowFertilityNotifications)) self.RegisterSavableAttribute(Savable.StandardAttributeHandler("TimeSinceCycleStart", "TimeSinceCycleStart", self.TimeSinceCycleStart)) @property def SavableOperationInformation (self) -> str: return self.DebugInformation @property def DebugInformation (self) -> str: return "%s | Section Key: %s | Sim ID: %s | Sim: %s" % \ (self.__class__.__name__, DotSavingKey, self.TargetSimInfo.id, ToolsSims.GetFullName(self.TargetSimInfo)) @property def TargetSimInfo (self) -> typing.Optional[sim_info.SimInfo]: """ The sim that this dot object is handling. """ return self._targetSimPointer.GetSim() @property def TargetSimID (self) -> typing.Optional[int]: """ The id of the sim that this dot object is handling. """ return self._targetSimPointer.SimID @property def Enabled (self) -> bool: """ Whether or not the dot app is installed on the target sim's phone. """ return self._enabled @Enabled.setter def Enabled (self, value: bool) -> None: if not isinstance(value, bool): raise Exceptions.IncorrectTypeException(value, "Enabled", (bool,)) self._enabled = value @property def TrackingMode (self) -> TrackingMode: """ Whether the app is currently tracking the target sim's menstrual cycle or pregnancy. """ return self._trackingMode @TrackingMode.setter def TrackingMode (self, value: TrackingMode) -> None: if not isinstance(value, TrackingMode): raise Exceptions.IncorrectTypeException(value, "TrackingMode", (TrackingMode,)) self._trackingMode = value @property def ShowFertilityNotifications (self) -> bool: """ Whether or not the app should notify the player when the target sim is about to become fertile. """ return self._showFertilityNotifications @ShowFertilityNotifications.setter def ShowFertilityNotifications (self, value: bool) -> None: if not isinstance(value, bool): raise Exceptions.IncorrectTypeException(value, "ShowFertilityNotifications", (bool,)) self._showFertilityNotifications = value @property def TimeSinceCycleStart (self) -> typing.Optional[float]: """ The time in reproductive minutes since the target sim's last known cycle start. """ return self._timeSinceCycleStart @TimeSinceCycleStart.setter def TimeSinceCycleStart (self, value: typing.Optional[float]) -> None: if not isinstance(value, (float, int)) and value is not None: raise Exceptions.IncorrectTypeException(value, "TimeSinceCycleStart", (float, int, None)) self._timeSinceCycleStart = value @property def LastSimulatedTick (self) -> int: """ The tick this dot information was last simulated to. This value isn't saved, when the dot information is first creating it is assumed the it was simulated up to now. All dot information objects should be updated before being saved to prevent missed time. """ return self._lastSimulatedTick @LastSimulatedTick.setter def LastSimulatedTick (self, value: int) -> None: if not isinstance(value, int): raise Exceptions.IncorrectTypeException(value, "LastSimulatedTick", (int,)) self._lastSimulatedTick = value @property def TicksBehind (self) -> int: """ The number of game ticks the information object is behind the current game tick. """ timeService = services.time_service() # type: time_service.TimeService return max(timeService.sim_now.absolute_ticks() - self.LastSimulatedTick, 0) @property def Simulating (self) -> bool: """ Whether or not this reproductive system is currently in the process of simulating time. """ return self._simulating @property def Outdated (self) -> bool: """ Whether or not this dot information has been updated all the way to the latest tick. If the game's time service is not available this will return False. """ if game_services.service_manager is None: return False timeService = services.time_service() # type: time_service.TimeService return self.LastSimulatedTick < timeService.sim_now.absolute_ticks() @property def ShouldUpdate (self) -> bool: """ Whether or not this reproductive system should be updated. This value will be True if this object is outdated and is not already simulating. """ return self.Outdated and not self.Simulating def Load (self, simsSection: SectionBranched.SectionBranched) -> bool: """ Load the reproductive system's data from this saving section. An exception will be raised if no valid sim has been set for this dot object. """ if self.TargetSimID is None or self.TargetSimInfo is None: raise Exception("Cannot load a dot information object with no target sim.") if not isinstance(simsSection, SectionBranched.SectionBranched): raise Exceptions.IncorrectTypeException(simsSection, "simsSection", (SectionBranched.SectionBranched,)) operationInformation = self.SavableOperationInformation # type: str operationSuccessful = True # type: bool try: targetSimSavingKey = Saving.GetSimSimsSectionBranchKey(self.TargetSimInfo) # type: str dotInformationData = simsSection.GetValue(targetSimSavingKey, DotSavingKey, default = None) if dotInformationData is None: Debug.Log("'%s' has had a dot information object created for the first time, or at least, they had no saved data in the loaded save file.\n%s" % (ToolsSims.GetFullName(self.TargetSimInfo), operationInformation), self.HostNamespace, Debug.LogLevels.Info, group = self.HostNamespace, owner = __name__) dotInformationData = dict() if not isinstance(dotInformationData, dict): Debug.Log("Incorrect type in dot information data with the section key.\n" + operationInformation + "\n" + Exceptions.GetIncorrectTypeExceptionText(dotInformationData, "DotInformationData", (dict,)), self.HostNamespace, Debug.LogLevels.Warning, group = self.HostNamespace, owner = __name__) dotInformationData = dict() operationSuccessful = False if simsSection.SavingObject.DataHostVersion is not None: lastVersion = Version.Version(simsSection.SavingObject.DataHostVersion) # type: typing.Optional[Version.Version] else: lastVersion = None # type: typing.Optional[Version.Version] loadSuccessful = self.LoadFromDictionary(dotInformationData, lastVersion = lastVersion) except: Debug.Log("Load operation in a dot information object aborted.\n" + operationInformation, self.HostNamespace, Debug.LogLevels.Exception, group = self.HostNamespace, owner = __name__) self.Reset() return False if not loadSuccessful: return False return operationSuccessful def Save (self, simsSection: SectionBranched.SectionBranched) -> bool: """ Save the reproductive system's data to this saving section. An exception will be raised if no valid sim has been set for this dot object. """ if self.TargetSimID is None or self.TargetSimInfo is None: raise Exception("Cannot save a dot information object with no target sim.") if not isinstance(simsSection, SectionBranched.SectionBranched): raise Exceptions.IncorrectTypeException(simsSection, "simsSection", (SectionBranched.SectionBranched,)) if self.Outdated: self.Update() operationInformation = self.SavableOperationInformation # type: str operationSuccessful = True # type: bool try: targetSimSavingKey = Saving.GetSimSimsSectionBranchKey(self.TargetSimInfo) # type: str saveSuccessful, dotInformationData = self.SaveToDictionary() # type: bool, dict simsSection.Set(targetSimSavingKey, DotSavingKey, dotInformationData) except: Debug.Log("Save operation in a dot information object aborted.\n" + operationInformation, self.HostNamespace, Debug.LogLevels.Exception, group = self.HostNamespace, owner = __name__) return False if not saveSuccessful: return False return operationSuccessful def Update (self) -> None: """ Update this dot app information to the most recent tick. """ if game_services.service_manager is None: return if not self.Outdated: return ticksBehind = self.TicksBehind if ticksBehind <= 0: return self.Simulate(ticksBehind) def Simulate (self, ticks: int) -> None: """ Simulate this many ticks for this dot app information. This method could allow you to simulate time that hasn't actually passed. The dot app information should be simulated every time the target sim's reproductive system simulates. :param ticks: The number of ticks to simulate. :type ticks: int """ if not isinstance(ticks, int): raise Exceptions.IncorrectTypeException(ticks, "ticks", (int,)) self._simulating = True self._SimulateInternal(ticks) self._simulating = False timeService = services.time_service() # type: time_service.TimeService currentTick = timeService.sim_now.absolute_ticks() # type: int self.LastSimulatedTick = min(self.LastSimulatedTick + ticks, currentTick) def GetCurrentCycle (self) -> typing.Optional[DotCycle]: """ Get information about the current cycle, according to the dot app. This will be none if the app is not tracking the cycle, the target sim does not exist, the target sim cannot menstruate, or we don't know when the last menstrual cycle occurred. """ if self.TrackingMode != TrackingMode.Cycle: return None if self.TimeSinceCycleStart is None: return None return self._GetCycle(self.TimeSinceCycleStart) def GetCurrentCycleAge (self) -> typing.Optional[float]: """ Get the age of the target sim's current cycle in game minutes, according to the dot app. This will be none if the app is not tracking the cycle, the target sim does not exist, the target sim cannot menstruate, or we don't know when the last menstrual cycle occurred. """ if self.TrackingMode != TrackingMode.Cycle: return None if self.TimeSinceCycleStart is None: return None return self._GetCycleAge(self.TimeSinceCycleStart) def SetCycleStart (self, minutesSince: float) -> None: """ Set the suspected start of the target's cycle. :param minutesSince: The number of reproductive minutes since the cycle started. :type minutesSince: float """ if not isinstance(minutesSince, (float, int)): raise Exceptions.IncorrectTypeException(minutesSince, "minutesSince", (float, int)) self.TimeSinceCycleStart = minutesSince def _SimulateInternal (self, ticks: int) -> None: if ticks <= 0: return if self.TimeSinceCycleStart is None: return cycleReproductiveTimeMultiplier = _GetCycleReproductiveTimeMultiplier() # type: float lastTimeSinceCycleStart = self.TimeSinceCycleStart # type: float lastTickSinceCycleStart = ReproductionShared.ReproductiveMinutesToTicks(lastTimeSinceCycleStart, cycleReproductiveTimeMultiplier) # type: int nextTickSinceCycleStart = lastTickSinceCycleStart + ticks # type: int nextTimeSinceCycleStart = ReproductionShared.TicksToReproductiveMinutes(nextTickSinceCycleStart, cycleReproductiveTimeMultiplier) # type: float if self.ShowFertilityNotifications and self.Enabled: currentCycleState = self._GetCycle(lastTimeSinceCycleStart) # type: DotCycle currentTicksUntilOvulationStarts = ReproductionShared.GameMinutesToTicks(currentCycleState.GetTimeUntilPhaseStarts(CycleShared.MenstrualCyclePhases.Ovulation)) # type: int ticksBeforeOvulationToNotify = ReproductionShared.GameMinutesToTicks(_GetSpermHalfLifeTime()) # type: int if ticksBeforeOvulationToNotify < self.MinimumTicksBeforeOvulationToNotify: ticksBeforeOvulationToNotify = self.MinimumTicksBeforeOvulationToNotify if currentTicksUntilOvulationStarts > 0 and ticksBeforeOvulationToNotify < currentTicksUntilOvulationStarts: nextCycleState = self._GetCycle(nextTimeSinceCycleStart) # type: DotCycle nextTicksUntilOvulationStarts = ReproductionShared.GameMinutesToTicks(nextCycleState.GetTimeUntilPhaseStarts(CycleShared.MenstrualCyclePhases.Ovulation)) # type: int if ticksBeforeOvulationToNotify > nextTicksUntilOvulationStarts: UIDot.ShowFertilityNotification(self.TargetSimInfo) self.TimeSinceCycleStart = nextTimeSinceCycleStart def _GetCycle (self, reproductiveTimeSinceCycleStart: float) -> DotCycle: cycleInformation = DotCycle() currentCycleAge = self._GetCycleAge(reproductiveTimeSinceCycleStart) # type: typing.Optional[float] assert currentCycleAge is not None cycleInformation.Age = currentCycleAge return cycleInformation def _GetCycleAge (self, reproductiveTimeSinceCycleStart: float) -> float: # Time in game minutes gameTimeSinceCycleStart = ReproductionShared.ReproductiveMinutesToGameMinutes(reproductiveTimeSinceCycleStart, _GetCycleReproductiveTimeMultiplier()) # type: float return gameTimeSinceCycleStart % _GetMenstrualCycleLifetime()
def _GetSpermHalfLifeTime () -> float: reproductiveTimeMultiplier = _GetSpermReproductiveTimeMultiplier() # type: float spermGuide = _GetSpermGuide() # type: CycleGuides.SpermGuide return ReproductionShared.ReproductiveMinutesToGameMinutes(spermGuide.LifetimeDistributionMean.Mean, reproductiveTimeMultiplier)
def _GetMenstrualCycleMenstruationLength () -> float: reproductiveTimeMultiplier = _GetCycleReproductiveTimeMultiplier() # type: float menstrualCycleGuide = _GetMenstrualCycleGuide() # type: CycleGuides.CycleMenstrualGuide return ReproductionShared.ReproductiveMinutesToGameMinutes(menstrualCycleGuide.MenstruationLength.Mean, reproductiveTimeMultiplier)
def _GetCycleAge (self, reproductiveTimeSinceCycleStart: float) -> float: # Time in game minutes gameTimeSinceCycleStart = ReproductionShared.ReproductiveMinutesToGameMinutes(reproductiveTimeSinceCycleStart, _GetCycleReproductiveTimeMultiplier()) # type: float return gameTimeSinceCycleStart % _GetMenstrualCycleLifetime()