Example #1
0
	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
Example #2
0
    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)
Example #3
0
	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)
Example #4
0
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()