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 _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 _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)
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()