Ejemplo n.º 1
0
class DependentExtension:
    # noinspection SpellCheckingInspection
    INSTANCE_TUNABLES = {
        "DependentOnMod":
        tunable.Tunable(
            description=
            "Whether or not the interaction will be disabled it the mod is not loaded.",
            tunable_type=bool,
            default=True),
    }

    DependentMod = None  # type: Mods.Mod

    DependentOnMod: bool

    def __init_subclass__(cls, *args, **kwargs):
        super().__init_subclass__(*args, **kwargs)

        if hasattr(cls, "_additional_tests"):
            if cls._additional_tests is not None:
                for additionalTest in cls._additional_tests:
                    if isinstance(additionalTest, DependentTest):
                        return

        if hasattr(cls, "add_additional_test"):
            cls.add_additional_test(DependentTest())
Ejemplo n.º 2
0
	class PerformanceTypeInfo(tunable.HasTunableSingletonFactory, tunable.AutoFactoryInit):
		FACTORY_TUNABLES = {
			"ArrivingPercentage": tunable.TunableVariant(
				description = "The options to select the percentage of sperm that will arrive for this performance type. These values should be from 0 to 1.",
				default = "fixed",
				fixed = tunable.Tunable(description = "A fixed arriving percentage for this performance type.", tunable_type = float, default = 0.5),
				random = tunable.TunableInterval(description = "An arriving percentage, between the upper and lower bounds, will be randomly selected.", tunable_type = float, default_lower = 0.5, default_upper = 0.5, minimum = 0, maximum = 1),
				normalDistribution = Distribution.TunableNormalDistribution(description = "An arriving percentage will be randomly selected based on a normal distribution.", meanDefault = 0.5, standardDeviationDefault = 0),
			)
		}

		ArrivingPercentage: typing.Union[float, tunable.TunableInterval, Distribution.NormalDistribution]

		def GenerateSpermArrivingPercentage (self, seed: typing.Optional[int] = None) -> float:
			"""
			Get the percentage of sperm that should get past this woohoo device and make it from one sim to another. This will return a number from 0 to 1.
			"""

			if seed is None:
				seed = random.randint(-1000000000, 1000000000)  # type: int

			if not isinstance(seed, int):
				raise Exceptions.IncorrectTypeException(seed, "seed", (int, None))

			if isinstance(self.ArrivingPercentage, tunable.TunedInterval):
				arrivingPercentage = self.ArrivingPercentage.random_float(seed = seed + -712214524)  # type: float
			elif isinstance(self.ArrivingPercentage, Distribution.NormalDistribution):
				arrivingPercentage = self.ArrivingPercentage.GenerateValue(seed = seed + 38190718, minimum = 0, maximum = 1)  # type: float
			else:
				arrivingPercentage = self.ArrivingPercentage

			return arrivingPercentage
Ejemplo n.º 3
0
class LimitedUseWoohooSafetyMethod(WoohooSafety.WoohooSafetyMethod):
	UseReductionBase = UseReductionBase
	UseReductionInventoryStatistic = UseReductionInventoryStatistic
	UseReductionInventoryObject = UseReductionInventoryObject
	UseReductionOptions = UseReductionOptions

	FACTORY_TUNABLES = {
		"UseReduction": tunable.TunableVariant(
			inventoryStatistic = UseReductionInventoryStatistic.TunableFactory(),
			inventoryObject = UseReductionInventoryObject.TunableFactory(),
			options = UseReductionOptions.TunableFactory()
		),
		"PreferInseminatedUses": tunable.Tunable(description = "If true, we will search the inseminated sim for uses to remove before the source sim.", tunable_type = bool, default = True),
		"HandlePostUseReduction": tunable.OptionalTunable(description = "A console command that needs to be called after a use of this safety method has been removed. This should be used to show notifications that a sim has run out of a woohoo safety method. The command should take the woohoo safety method guid, the target sim's id (the sim using the woohoo method), and the amount of uses remaining, in that order. The method GUID, and target sim id parameters may not actually get valid ids.", tunable = tunable.Tunable(tunable_type = str, default = None))

	}

	UseReduction: UseReductionBase
	PreferInseminatedUses: bool
	HandlePostUseReduction: typing.Optional[str]

	def HandlePostUse (self, inseminatedSimInfo: typing.Optional[sim_info.SimInfo], sourceSimInfo: typing.Optional[sim_info.SimInfo], performanceType: str) -> None:
		"""
		Handle the after effects of using this woohoo safety method.
		"""

		if not isinstance(inseminatedSimInfo, sim_info.SimInfo) and inseminatedSimInfo is not None:
			raise Exceptions.IncorrectTypeException(inseminatedSimInfo, "inseminatedSimInfo", (sim_info.SimInfo,))

		if not isinstance(inseminatedSimInfo, sim_info.SimInfo) and inseminatedSimInfo is not None:
			raise Exceptions.IncorrectTypeException(inseminatedSimInfo, "inseminatedSimInfo", (sim_info.SimInfo,))

		if not isinstance(performanceType, str):
			raise Exceptions.IncorrectTypeException(performanceType, "performanceType", (str,))

		primaryReductionSimInfo = inseminatedSimInfo if self.PreferInseminatedUses else sourceSimInfo
		secondaryReductionSimInfo = sourceSimInfo if self.PreferInseminatedUses else inseminatedSimInfo

		reducedSimInfo = None  # type: typing.Optional[sim_info.SimInfo]

		if primaryReductionSimInfo is not None and self.UseReduction.RemoveUse(primaryReductionSimInfo):
			reducedSimInfo = primaryReductionSimInfo
		elif secondaryReductionSimInfo is not None and self.UseReduction.RemoveUse(secondaryReductionSimInfo):
			reducedSimInfo = secondaryReductionSimInfo

		super().HandlePostUse(inseminatedSimInfo, sourceSimInfo, performanceType)

		if reducedSimInfo is not None:
			reducedSimUsesRemaining = self.UseReduction.GetUseCount(reducedSimInfo)  # type: int

			if self.HandlePostUseReduction is not None:
				methodGUID = self.GUID if self.GUID is not None else 0  # type: int
				reducedSimID = reducedSimInfo.id if reducedSimInfo is not None else 0

				try:
					consoleCommand = " ".join((self.HandlePostUseReduction, str(methodGUID), str(reducedSimID), str(reducedSimUsesRemaining)))
					commands.execute(consoleCommand, None)
				except:
					Debug.Log("Failed to call handle post use reduction command for woohoo safety method '%s'." % str(self.GUID), This.Mod.Namespace, Debug.LogLevels.Exception, group = This.Mod.Namespace, owner = __name__)
Ejemplo n.º 4
0
 def __init__(self,
              description="A single point on a curved line.",
              **kwargs):
     super().__init__(
         description=description,
         x=tunable.Tunable(description="The x value for this point.",
                           tunable_type=float,
                           default=0),
         y=tunable.Tunable(description="The y value for this point.",
                           tunable_type=float,
                           default=0),
         connectionType=ToolsTunable.TunablePythonEnumEntry(
             description=
             "The type of connection this point has to the next point in the chain.",
             enumType=ConnectionType,
             default=ConnectionType.Linear),
         **kwargs)
Ejemplo n.º 5
0
class EmergencyContraceptivePillEffectGuide(tunable.HasTunableSingletonFactory, tunable.AutoFactoryInit, GuidesBase.GuideBase):
	FACTORY_TUNABLES = {
		"StrengthPerReproductiveMinute": tunable.Tunable(description = "The amount the strength of the emergency contraceptive changes per reproductive minute.", tunable_type = float, default = 0),
		"DelayTimeMean": Curve.TunableCurve(description = "The average time in reproductive minutes that an ovum will be delayed by, based on how strong the medication's effect's currently are. The value taken from this curve will be combined with the standard deviation curve's value in a normal distribution, which will be used to randomly generate a delay time. This will not be used if quick mode is enabled."),
		"DelayTimeStandardDeviation": Curve.TunableCurve(description = "The standard deviation time in reproductive minutes that an ovum will be delayed by, based on how strong the medication's effect's currently are. The value taken from this curve will be combined with the mean curve's value in a normal distribution, which will be used to randomly generate a delay time. This will not be used if quick mode is enabled."),
		"DelayTimeMaximum": tunable.Tunable(description = "The maximum amount of time in reproductive minutes emergency contraceptives can delay an ovum by. If the ovum has already been delayed by this amount of time or more, it will not be further delayed.", tunable_type = float, default = 10080),
		"QuickModeSpermBlockChance": Curve.TunableCurve(description = "The chance that a sperm object will be prevented from fertilizing an ovum, based on how strong the medication's effect's currently are. This is only used when quick mode is enabled."),
	}

	StrengthPerReproductiveMinute: float
	DelayTimeMean: Curve.Curve
	DelayTimeStandardDeviation: Curve.Curve
	DelayTimeMaximum: float
	QuickModeSpermBlockChance: Curve.Curve

	@classmethod
	def GetIdentifier (cls):
		"""
		Get an identifier that can be used to pick out a specific guide from a group.
		"""

		return EmergencyContraceptivePillEffectGuideIdentifier

	@classmethod
	def GetDefaultGuide (cls):
		"""
		Get a default instance of this guide.
		"""

		return DefaultEmergencyContraceptivePillEffectGuide

	@classmethod
	def GetGuide (cls, guideGroup):
		"""
		Get this guide group's instance of this guide or the default guide, if the group doesn't have a copy.
		"""

		from NeonOcean.S4.Cycle.GuideGroups import Base as GuidesGroupsBase

		if not isinstance(guideGroup, GuidesGroupsBase.GuideGroup):
			raise Exceptions.IncorrectTypeException(guideGroup, "guideGroup", (GuidesGroupsBase.GuideGroup,))

		guideGroupGuide = guideGroup.GetGuide(cls.GetIdentifier())  # type: typing.Optional[GuidesBase.GuideBase]
		return guideGroupGuide if guideGroupGuide is not None else cls.GetDefaultGuide()
Ejemplo n.º 6
0
    def __init__(
        self,
        description="A normal distribution.",
        meanDefault: float = 0,
        standardDeviationDefault: float = 1,
        **kwargs
    ):  # TODO add the spacings the ea's tunables use to descriptions?

        super().__init__(
            description=description,
            mean=tunable.Tunable(
                description="The mean of the normal distribution.",
                tunable_type=float,
                default=meanDefault),
            standardDeviation=tunable.Tunable(
                description=
                "The standard deviation of the normal distribution.",
                tunable_type=float,
                default=standardDeviationDefault),
            **kwargs)
Ejemplo n.º 7
0
	def __init__ (self, description = "An object to handle the adjustment of probability option weights.", **kwargs):  # TODO add the spacings the ea's tunables use to descriptions?
		super().__init__(
			description = description,
			adjustment = tunable.TunableVariant(
				description = "When a option's weight offset is adjusted by this adjuster, the weight offset value will replaced by either the value or result of the expression.",
				value = tunable.Tunable(tunable_type = float, default = 0),
				expression = TunableOptionAdjustmentExpression(),
				default = "value"
			),
			offsetMaximum = tunable.OptionalTunable(description = "The maximum value that the weight offset may be after the adjustment.", tunable = tunable.Tunable(tunable_type = float, default = 100)),
			offsetMinimum = tunable.OptionalTunable(description = "The minimum value that the weight offset may be after the adjustment.", tunable = tunable.Tunable(tunable_type = float, default = -100)),
			**kwargs)
Ejemplo n.º 8
0
	def __init__ (self, description = "An expression to determine what value an option's weight offset should have.", **kwargs):
		# noinspection SpellCheckingInspection
		super().__init__(
			description = description,
			expression = tunable.Tunable(
				description = "The expression should be a line of python code that is run in order to get an option's next weight offset.\n"
							  "Exponents must be less than 100 or the expression will fail. All math must also be valid, doing things such as dividing by zero will cause the expression to fail. Failures will cause the current weight offset value to not change."
							  "Valid functions are round, abs, max, min, ceil, floor, sqrt, cos, sin, tan.\n"
							  "Valid variables are pi, e, Base, CurrentOffset, and Current. Base refers to the unmodified weight of the option. CurrentOffset is the amount the the option's weight is currently offset by. Current is the option's current weight, the base and the offset combined.",
				tunable_type = str,
				default = "0"),
			**kwargs)
Ejemplo n.º 9
0
class ByObjectInstanceIDRegistrationInformation(RegistrationInformation):
    FACTORY_TUNABLES = {
        "InstanceIDs":
        tunable.TunableList(
            description=
            "The instance id of any game objects that this interaction should be attached to.",
            tunable=tunable.Tunable(tunable_type=int, default=0))
    }

    InstanceIDs: typing.Tuple[int, ...]

    def GetRelevantObjectInstanceIDs(self) -> typing.Set[int]:
        """
		Get a list of instances ids that point to the game objects that the interaction should be attached to.
		"""

        return set(self.InstanceIDs)
Ejemplo n.º 10
0
class ByTypeRegistrationInformation(RegistrationInformation):
    FACTORY_TUNABLES = {
        "Types":
        tunable.TunableList(
            description=
            "The types of game object that the interaction should be attached to. A corresponding type determiner python function needs to also exist and be setup for object registration to occur.",
            tunable=tunable.Tunable(tunable_type=str, default="Everything"))
    }

    Types: typing.Tuple[str, ...]

    def GetRelevantObjectTypes(self) -> typing.Set[str]:
        """
		Get a list of the types of game object that the interaction should be attached to.
		"""

        return set(self.Types)
Ejemplo n.º 11
0
class ByDefinitionInstanceIDRegistrationInformation(RegistrationInformation):
    FACTORY_TUNABLES = {
        "InstanceIDs":
        tunable.TunableList(
            description=
            "The instance id of any game object definition that the interaction should be attached to. The interaction would be attached to the definition's game object, it may be simpler to just designate the game object its self.",
            tunable=tunable.Tunable(tunable_type=int, default=0))
    }

    InstanceIDs: typing.Tuple[int, ...]

    def GetRelevantDefinitionInstanceIDs(self) -> typing.Set[int]:
        """
		Get a list of instances ids that point to the game object definitions that the interaction should be attached to.
		"""

        return set(self.InstanceIDs)
Ejemplo n.º 12
0
class SpermTrackerGuide(tunable.HasTunableSingletonFactory,
                        tunable.AutoFactoryInit, GuidesBase.GuideBase):
    FACTORY_TUNABLES = {
        "FertilizationEqualChanceCount":
        tunable.Tunable(
            int,
            75000000,
            description=
            "The total sperm count at which there is a 50-50 chance that a sperm cell will fertilize the ovum."
        )
    }

    FertilizationEqualChanceCount: int

    @classmethod
    def GetIdentifier(cls):
        """
		Get an identifier that can be used to pick out a specific guide from a group.
		"""

        return SpermTrackerGuideIdentifier

    @classmethod
    def GetDefaultGuide(cls):
        """
		Get a default instance of this guide.
		"""

        return DefaultSpermTrackerGuide

    @classmethod
    def GetGuide(cls, guideGroup):
        """
		Get this guide group's instance of this guide or the default guide, if the group doesn't have a copy.
		"""

        from NeonOcean.S4.Cycle.GuideGroups import Base as GuidesGroupsBase

        if not isinstance(guideGroup, GuidesGroupsBase.GuideGroup):
            raise Exceptions.IncorrectTypeException(
                guideGroup, "guideGroup", (GuidesGroupsBase.GuideGroup, ))

        guideGroupGuide = guideGroup.GetGuide(
            cls.GetIdentifier())  # type: typing.Optional[GuidesBase.GuideBase]
        return guideGroupGuide if guideGroupGuide is not None else cls.GetDefaultGuide(
        )
Ejemplo n.º 13
0
class _DotAppInteraction(Dependent.DependentExtension, Events.EventsExtension,
                         Registration.RegistrationExtension,
                         immediate_interaction.ImmediateSuperInteraction):
    class DotEnabledStateTest(test_base.BaseTest):
        # noinspection SpellCheckingInspection
        def __call__(self, affordance: typing.Type[_DotAppInteraction],
                     actors: typing.Tuple[sim_info.SimInfo, ...]):
            if affordance is None:
                return results.TestResult(False)

            if not issubclass(affordance, _DotAppInteraction):
                return results.TestResult(False)

            if len(actors) == 0:
                Debug.Log(
                    "Dot app state test recived an empty actors parameter.",
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__,
                    lockIdentifier=__name__ + ":" +
                    str(Python.GetLineNumber()))
                return results.TestResult(False)

            targetSimInfo = actors[0]  # type: sim_info.SimInfo

            dotInformation = Dot.GetDotInformation(
                targetSimInfo)  # type: typing.Optional[Dot.DotInformation]

            if dotInformation is None:
                Debug.Log(
                    "Missing dot information for a sim with the id '%s'." %
                    targetSimInfo.id,
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__,
                    lockIdentifier=__name__ + ":" +
                    str(Python.GetLineNumber()),
                    lockReference=targetSimInfo)
                return results.TestResult(False)

            if affordance.RequiredDotEnabledState is None:
                return results.TestResult(True)
            else:
                return results.TestResult(dotInformation.Enabled ==
                                          affordance.RequiredDotEnabledState)

        def get_expected_args(self) -> dict:
            return {
                "affordance": interactions.ParticipantType.Affordance,
                "actors": interactions.ParticipantType.Actor
            }

    class DotTrackerModeTest(test_base.BaseTest):
        # noinspection SpellCheckingInspection
        def __call__(self, affordance: typing.Type[_DotAppInteraction],
                     actors: typing.Tuple[sim_info.SimInfo, ...]):
            if affordance is None:
                return results.TestResult(False)

            if not issubclass(affordance, _DotAppInteraction):
                return results.TestResult(False)

            if len(actors) == 0:
                Debug.Log(
                    "Dot app state test recived an empty actors parameter.",
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__)
                return results.TestResult(False)

            targetSimInfo = actors[0]  # type: sim_info.SimInfo

            dotInformation = Dot.GetDotInformation(
                targetSimInfo)  # type: typing.Optional[Dot.DotInformation]

            if dotInformation is None:
                Debug.Log(
                    "Missing dot information for a sim with the id '%s'." %
                    targetSimInfo.id,
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__,
                    lockIdentifier=__name__ + ":" +
                    str(Python.GetLineNumber()),
                    lockReference=targetSimInfo)
                return results.TestResult(False)

            if affordance.RequiredDotTrackingMode is None:
                return results.TestResult(True)
            else:
                return results.TestResult(dotInformation.TrackingMode ==
                                          affordance.RequiredDotTrackingMode)

        def get_expected_args(self) -> dict:
            return {
                "affordance": interactions.ParticipantType.Affordance,
                "actors": interactions.ParticipantType.Actor
            }

    class DotShowFertilityNotificationsStateTest(test_base.BaseTest):
        # noinspection SpellCheckingInspection
        def __call__(self, affordance: typing.Type[_DotAppInteraction],
                     actors: typing.Tuple[sim_info.SimInfo, ...]):
            if affordance is None:
                return results.TestResult(False)

            if not issubclass(affordance, _DotAppInteraction):
                return results.TestResult(False)

            if len(actors) == 0:
                Debug.Log(
                    "Dot app notify of fertility test recived an empty actors parameter.",
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__)
                return results.TestResult(False)

            targetSimInfo = actors[0]  # type: sim_info.SimInfo

            dotInformation = Dot.GetDotInformation(
                targetSimInfo)  # type: typing.Optional[Dot.DotInformation]

            if dotInformation is None:
                Debug.Log(
                    "Missing dot information for a sim with the id '%s'." %
                    targetSimInfo.id,
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__,
                    lockIdentifier=__name__ + ":" +
                    str(Python.GetLineNumber()),
                    lockReference=targetSimInfo)
                return results.TestResult(False)

            if affordance.RequiredDotShowFertilityNotificationsState is None:
                return results.TestResult(True)
            else:
                return results.TestResult(
                    dotInformation.ShowFertilityNotifications ==
                    affordance.RequiredDotShowFertilityNotificationsState)

        def get_expected_args(self) -> dict:
            return {
                "affordance": interactions.ParticipantType.Affordance,
                "actors": interactions.ParticipantType.Actor
            }

    class HasCycleTrackerTest(test_base.BaseTest):
        # noinspection SpellCheckingInspection
        def __call__(self, actors: typing.Tuple[sim_info.SimInfo, ...]):
            if len(actors) == 0:
                Debug.Log(
                    "Has cycle tracker test recived an empty actors parameter.",
                    This.Mod.Namespace,
                    Debug.LogLevels.Warning,
                    group=This.Mod.Namespace,
                    owner=__name__)
                return results.TestResult(False)

            targetSimInfo = actors[0]  # type: sim_info.SimInfo

            targetSimSystem = Reproduction.GetSimSystem(
                targetSimInfo
            )  # type: typing.Optional[ReproductionShared.ReproductiveSystem]

            if targetSimSystem is None:
                return results.TestResult(False)

            return results.TestResult(
                targetSimSystem.HasTracker(
                    FemalesShared.CycleTrackerIdentifier))

        def get_expected_args(self) -> dict:
            return {"actors": interactions.ParticipantType.Actor}

    INSTANCE_TUNABLES = {
        "RequiredDotEnabledState":
        tunable.OptionalTunable(
            description=
            "If this is true, the sim needs to have the dot app installed for this interaction to appear. If false, it will appear if not installed. This can be left disabled to imply all states are valid.",
            tunable=tunable.Tunable(tunable_type=bool, default=True)),
        "RequiredDotTrackingMode":
        tunable.OptionalTunable(
            description=
            "The tracking mode a sim needs the dot app be in to use this interaction. If this is left disabled we will assume all tracking modes are valid.",
            tunable=ToolsTunable.TunablePythonEnumEntry(
                enumType=Dot.TrackingMode, default=Dot.TrackingMode.Cycle)),
        "RequiredDotShowFertilityNotificationsState":
        tunable.OptionalTunable(
            description=
            "If this is true, the sim needs to be getting fertility notifications for this interaction to appear. If false, it will appear if they are not getting fertility notifications. This can be left disabled to imply all states are valid.",
            tunable=tunable.Tunable(tunable_type=bool, default=False))
    }

    RequiredDotEnabledState: typing.Optional[bool]
    RequiredDotTrackingMode: typing.Optional[Dot.TrackingMode]
    RequiredDotShowFertilityNotificationsState: typing.Optional[bool]

    def __init_subclass__(cls, *args, **kwargs):
        super().__init_subclass__(*args, **kwargs)

        hasDotEnabledStateTest = False  # type: bool
        hasDotTrackerModeTest = False  # type: bool
        hasDotShowFertilityNotificationsStateTest = False  # type: bool
        hasHasCycleTrackerTest = False  # type: bool

        if cls._additional_tests is not None:
            for additionalTest in reversed(cls._additional_tests):
                if not hasDotEnabledStateTest and isinstance(
                        additionalTest, cls.DotEnabledStateTest):
                    hasDotEnabledStateTest = True
                elif not hasDotTrackerModeTest and isinstance(
                        additionalTest, cls.DotTrackerModeTest):
                    hasDotTrackerModeTest = True
                elif not hasDotShowFertilityNotificationsStateTest and isinstance(
                        additionalTest,
                        cls.DotShowFertilityNotificationsStateTest):
                    hasDotTrackerModeTest = True
                elif not hasHasCycleTrackerTest and isinstance(
                        additionalTest, cls.HasCycleTrackerTest):
                    hasHasCycleTrackerTest = True

        if not hasDotEnabledStateTest:
            cls.add_additional_test(cls.DotEnabledStateTest())

        if not hasDotTrackerModeTest:
            cls.add_additional_test(cls.DotTrackerModeTest())

        if not hasDotShowFertilityNotificationsStateTest:
            cls.add_additional_test(
                cls.DotShowFertilityNotificationsStateTest())

        if not hasHasCycleTrackerTest:
            cls.add_additional_test(cls.HasCycleTrackerTest())
Ejemplo n.º 14
0
class WoohooSafetyMethod(tunable.HasTunableSingletonFactory, tunable.AutoFactoryInit):
	class Requirement:
		def RequirementMet (self, targetSim: sim.Sim) -> bool:
			"""
			Whether or not the target sim meets the requirements.
			"""

			raise NotImplementedError()

	class ObjectRequirement(Requirement, tunable.HasTunableSingletonFactory, tunable.AutoFactoryInit):
		FACTORY_TUNABLES = {
			"RequiredObject": tunable.TunableReference(description = "A sim must have this object in their inventory in order to meet this requirement.", manager = services.get_instance_manager(resources.Types.OBJECT), pack_safe = True),
			"RequiredObjectTests": tests.TunableTestSet(description = "A set of tests the object must pass in order to this requirement to be meet.")
		}

		RequiredObject: typing.Optional[definition.Definition]
		RequiredObjectTests: tests.CompoundTestList

		def RequirementMet (self, targetSim: sim.Sim) -> bool:
			"""
			Whether or not the target sim meets the requirements.
			"""

			if not isinstance(targetSim, sim.Sim):
				raise Exceptions.IncorrectTypeException(targetSim, "targetSim", (sim.Sim,))

			if self.RequiredObject is None:
				return False

			inventoryComponent = targetSim.get_component(ComponentsTypes.INVENTORY_COMPONENT)  # type: ComponentsInventory.InventoryComponent

			if not inventoryComponent.has_item_with_definition(self.RequiredObject):
				return False
			else:
				if len(self.RequiredObjectTests) == 0:
					return True

			for matchingObject in inventoryComponent.get_items_with_definition_gen(self.RequiredObject):  # type: game_object.GameObject
				requiredObjectResolver = resolver.SingleObjectResolver(matchingObject)
				testResults = self.RequiredObjectTests.run_tests(requiredObjectResolver)  # type: typing.Optional[TestingUnit.TestResult]

				if not testResults:
					return False

			return True

	class PerformanceTypeInfo(tunable.HasTunableSingletonFactory, tunable.AutoFactoryInit):
		FACTORY_TUNABLES = {
			"ArrivingPercentage": tunable.TunableVariant(
				description = "The options to select the percentage of sperm that will arrive for this performance type. These values should be from 0 to 1.",
				default = "fixed",
				fixed = tunable.Tunable(description = "A fixed arriving percentage for this performance type.", tunable_type = float, default = 0.5),
				random = tunable.TunableInterval(description = "An arriving percentage, between the upper and lower bounds, will be randomly selected.", tunable_type = float, default_lower = 0.5, default_upper = 0.5, minimum = 0, maximum = 1),
				normalDistribution = Distribution.TunableNormalDistribution(description = "An arriving percentage will be randomly selected based on a normal distribution.", meanDefault = 0.5, standardDeviationDefault = 0),
			)
		}

		ArrivingPercentage: typing.Union[float, tunable.TunableInterval, Distribution.NormalDistribution]

		def GenerateSpermArrivingPercentage (self, seed: typing.Optional[int] = None) -> float:
			"""
			Get the percentage of sperm that should get past this woohoo device and make it from one sim to another. This will return a number from 0 to 1.
			"""

			if seed is None:
				seed = random.randint(-1000000000, 1000000000)  # type: int

			if not isinstance(seed, int):
				raise Exceptions.IncorrectTypeException(seed, "seed", (int, None))

			if isinstance(self.ArrivingPercentage, tunable.TunedInterval):
				arrivingPercentage = self.ArrivingPercentage.random_float(seed = seed + -712214524)  # type: float
			elif isinstance(self.ArrivingPercentage, Distribution.NormalDistribution):
				arrivingPercentage = self.ArrivingPercentage.GenerateValue(seed = seed + 38190718, minimum = 0, maximum = 1)  # type: float
			else:
				arrivingPercentage = self.ArrivingPercentage

			return arrivingPercentage

	FACTORY_TUNABLES = {
		"Requirements": tunable.TunableList(
			description = "A list of requirements to determine if a sim can use this method. Requirements may be split into groups, only one group needs to be valid for a sim to met the requirements. If there are no requirements we will assume this method can always be used.",
			tunable = tunable.TunableList(
				description = "An individual group for requirements. For this method to be usable, every criteria in at least one of these groups must be met.",
				tunable = tunable.TunableVariant(
					objectRequirement = ObjectRequirement.TunableFactory(),
				)
			)
		),
		"UsingByDefault": tunable.Tunable(description = "Whether or not sims will use this method if its available by default.", tunable_type = bool, default = True),

		"PerformanceTypes": tunable.TunableMapping(
			description = "A set of performance types that denote how effective this safety method was. A performance type will be randomly selected for a woohoo using the 'PerformanceTypeChances' value.",
			key_type = tunable.Tunable(tunable_type = str, default = "UNKNOWN_PERFORMANCE_TYPE"),
			value_type = PerformanceTypeInfo.TunableFactory()
		),
		"PerformanceTypeChances": Probability.TunableProbability(description = "The chances that each performance type will be selected. Each option's identifier should correspond with a performance type."),

		"CompoundingMethod": tunable.Tunable(description = "Whether or not this method may be used at the same time as another method. If more than one non-compound method are to be used, we will select the first method found.", tunable_type = bool, default = True),

		"HandleUseCommand": tunable.OptionalTunable(description = "A console command that needs to be called after this safety method is used. This command should take the woohoo safety method guid, the inseminated sim id, the source sim id, and the performance type, in that order. The method GUID, inseminated sim id, and source sim id parameters may not actually get valid ids.", tunable = tunable.Tunable(tunable_type = str, default = None))
	}

	Requirements: typing.Tuple[typing.Tuple[Requirement, ...], ...]
	UsingByDefault: bool

	PerformanceTypes: typing.Dict[str, PerformanceTypeInfo]
	PerformanceTypeChances: Probability.Probability

	CompoundingMethod: bool

	HandleUseCommand: typing.Optional[str]

	GUID = None  # type: typing.Optional[int]

	@property
	def HasRequirement (self) -> bool:
		"""
		Whether or not this safety method has requirements.
		"""

		if len(self.Requirements) == 0:
			return False

		for requirementGroup in self.Requirements:  # type: typing.Tuple[WoohooSafetyMethod.Requirement, ...]
			if len(requirementGroup) != 0:
				return True

		return False

	def GetUniqueSeed (self) -> int:
		"""
		Get a unique randomization seed for this woohoo safety method.
		"""

		random.seed(self.GUID)
		return random.randint(-1000000000, 1000000000)  # type: int

	def IsAvailable (self, targetSimInfo: sim_info.SimInfo) -> bool:
		"""
		Get whether or not the target sim can use this safety method. This may incorrectly return false for non-instanced sims; we cannot read the inventory
		of sims not instanced.
		"""

		if not isinstance(targetSimInfo, sim_info.SimInfo):
			raise Exceptions.IncorrectTypeException(targetSimInfo, "targetSimInfo", (sim_info.SimInfo,))

		if not self.HasRequirement:
			return True

		if not targetSimInfo.is_instanced():
			return False

		targetSim = targetSimInfo.get_sim_instance()  # type: sim.Sim

		for requirementGroup in self.Requirements:  # type: typing.Tuple[WoohooSafetyMethod.Requirement, ...]
			if len(requirementGroup) == 0:
				continue

			groupRequirementsMet = True  # type: bool

			for requirement in requirementGroup:  # type: WoohooSafetyMethod.Requirement
				if not requirement.RequirementMet(targetSim):
					groupRequirementsMet = False
					break

			if groupRequirementsMet:
				return True

		return False

	def SelectPerformanceType (self, seed: typing.Optional[int] = None) -> typing.Tuple[str, PerformanceTypeInfo]:
		"""
		Randomly select one of the performance type in this woohoo safety method.
		"""

		if seed is None:
			seed = random.randint(-1000000000, 1000000000)  # type: int

		if not isinstance(seed, int):
			raise Exceptions.IncorrectTypeException(seed, "seed", (int, None))

		if len(self.PerformanceTypes) == 0:
			raise Exception("Could not find any safety method performance type to select.\nGUID: %s" % self.GUID)

		if len(self.PerformanceTypeChances.Options) == 0:
			raise Exception("Could not select a performance type 'PerformanceTypeChances' has no options.\nGUID: %s" % self.GUID)

		performanceType = self.PerformanceTypeChances.ChooseOption(seed = seed + -443757754).Identifier  # type: str
		performanceTypeInfo = self.PerformanceTypes.get(performanceType, None)  # type: WoohooSafetyMethod.PerformanceTypeInfo

		if performanceTypeInfo is None:
			Debug.Log("Randomly selected performance type that doesn't exist.\nGUID: %s, Performance Type: %s" % (str(self.GUID), performanceType), This.Mod.Namespace, Debug.LogLevels.Error, group = This.Mod.Namespace, owner = __name__)
			return random.choice(self.PerformanceTypes)

		return performanceType, performanceTypeInfo

	def GenerateSpermArrivingPercentage (self, seed: typing.Optional[int] = None) -> float:
		"""
		Get the percentage of sperm that should get past this woohoo device and make it from one sim to another. This will return a number from 0 to 1.
		"""

		if seed is None:
			seed = random.randint(-1000000000, 1000000000)  # type: int

		if not isinstance(seed, int):
			raise Exceptions.IncorrectTypeException(seed, "seed", (int, None))

		performanceType, performanceTypeInfo = self.SelectPerformanceType(seed = seed + -443757754)  # type: str, WoohooSafetyMethod.PerformanceTypeInfo
		arrivingPercentage = performanceTypeInfo.GenerateSpermArrivingPercentage(seed + -16160599)  # type: float

		if arrivingPercentage < 0:
			Debug.Log("Safety method performance type generated an arriving percentage is less than 0.\nGUID: %s, Performance Type: %s" % (str(self.GUID), performanceType), This.Mod.Namespace, Debug.LogLevels.Error, group = This.Mod.Namespace, owner = __name__)
			arrivingPercentage = 0

		if arrivingPercentage > 1:
			Debug.Log("Safety method performance type generated an arriving percentage is greater than 1.\nGUID: %s, Performance Type: %s" % (str(self.GUID), performanceType), This.Mod.Namespace, Debug.LogLevels.Error, group = This.Mod.Namespace, owner = __name__)
			arrivingPercentage = 1

		return arrivingPercentage

	def HandlePostUse (self, inseminatedSimInfo: typing.Optional[sim_info.SimInfo], sourceSimInfo: typing.Optional[sim_info.SimInfo], performanceType: str) -> None:
		"""
		Handle the after effects of using this woohoo safety method.
		"""

		if not isinstance(inseminatedSimInfo, sim_info.SimInfo) and inseminatedSimInfo is not None:
			raise Exceptions.IncorrectTypeException(inseminatedSimInfo, "inseminatedSimInfo", (sim_info.SimInfo,))

		if not isinstance(inseminatedSimInfo, sim_info.SimInfo) and inseminatedSimInfo is not None:
			raise Exceptions.IncorrectTypeException(inseminatedSimInfo, "inseminatedSimInfo", (sim_info.SimInfo,))

		if not isinstance(performanceType, str):
			raise Exceptions.IncorrectTypeException(performanceType, "performanceType", (str,))

		if self.HandleUseCommand is not None:
			methodGUID = self.GUID if self.GUID is not None else 0  # type: int
			inseminatedSimID = inseminatedSimInfo.id if inseminatedSimInfo is not None else 0
			sourceSimID = sourceSimInfo.id if sourceSimInfo is not None else 0

			try:
				consoleCommand = " ".join((self.HandleUseCommand, str(methodGUID), str(inseminatedSimID), str(sourceSimID), performanceType))
				commands.execute(consoleCommand, None)
			except:
				Debug.Log("Failed to call handle post use command for woohoo safety method '%s'." % str(self.GUID), This.Mod.Namespace, Debug.LogLevels.Exception, group = This.Mod.Namespace, owner = __name__)
Ejemplo n.º 15
0
class MenstrualEffectGuide(tunable.HasTunableSingletonFactory,
                           tunable.AutoFactoryInit, GuidesBase.GuideBase):
    FACTORY_TUNABLES = {
        "BuffRarity":
        Probability.TunableProbability(
            description=
            "A set of weights used to determine the rarity of the next buff. Adding an option with the identifier 'Abstain' will allow for a chance to not add a buff."
        ),
        "BuffCoolDown":
        tunable.TunableMapping(
            description=
            "A set of cool down distributions to randomly select the time between two buffs being added based on how rare first buff was.",
            key_type=ToolsTunable.TunablePythonEnumEntry(
                description="How rare the first buff was.",
                enumType=BuffsShared.BuffRarity,
                default=BuffsShared.BuffRarity.Common),
            value_type=Distribution.TunableNormalDistribution(
                description=
                "A distribution to select the minimum amount of time in sim minutes between buffs. More strenuous buffs should impose longer cool downs than less strenuous ones.",
                meanDefault=240,
                standardDeviationDefault=60)),
        "AbstainedBuffCoolDown":
        Distribution.TunableNormalDistribution(
            "A distribution to select, if we didn't choose a buff when the opportunity arose, the minimum amount of time in sim minutes until we can try to pick a buff again.",
            meanDefault=120,
            standardDeviationDefault=15),
        "ExperiencesPMSProbability":
        tunable.Tunable(
            description=
            "The probability that a sim should experience PMS symptoms. This should be a number from 0 to 1.",
            tunable_type=float,
            default=0.4)
    }

    BuffRarity: Probability.Probability
    AbstainedBuffCoolDown: Distribution.NormalDistribution
    BuffCoolDown: typing.Dict[BuffsShared.BuffRarity,
                              Distribution.NormalDistribution]
    ExperiencesPMSProbability: float

    @classmethod
    def GetIdentifier(cls):
        """
		Get an identifier that can be used to pick out a specific guide from a group.
		"""

        return MenstrualEffectGuideIdentifier

    @classmethod
    def GetDefaultGuide(cls):
        """
		Get a default instance of this guide.
		"""

        return DefaultMenstrualEffectGuide

    @classmethod
    def GetGuide(cls, guideGroup):
        """
		Get this guide group's instance of this guide or the default guide, if the group doesn't have a copy.
		"""

        from NeonOcean.S4.Cycle.GuideGroups import Base as GuidesGroupsBase

        if not isinstance(guideGroup, GuidesGroupsBase.GuideGroup):
            raise Exceptions.IncorrectTypeException(
                guideGroup, "guideGroup", (GuidesGroupsBase.GuideGroup, ))

        guideGroupGuide = guideGroup.GetGuide(
            cls.GetIdentifier())  # type: typing.Optional[GuidesBase.GuideBase]
        return guideGroupGuide if guideGroupGuide is not None else cls.GetDefaultGuide(
        )
Ejemplo n.º 16
0
import typing

import services
import snippets
import zone
from NeonOcean.S4.Order import Director, Information, This
from NeonOcean.S4.Order.Tools import Exceptions
from sims4 import localization
from sims4.tuning import tunable

IdentifiersTemplate = tunable.TunableMapping(
    description=
    "A dictionary of identifiers and their corresponding localization string keys.",
    key_type=tunable.Tunable(
        description="The identifier of the localization string.",
        tunable_type=str,
        default=None),
    value_type=tunable.Tunable(
        description="The key used to find the localization string.",
        tunable_type=int,
        default=0))  # type: tunable.TunableMapping

IdentifiersSnippetName = Information.GlobalNamespace.replace(
    ".", "_") + "_Language_Identifiers"  # type: str

IdentifiersSnippetReference: snippets.TunableSnippetReference
IdentifiersSnippet: snippets.TunableSnippet

_identifiers = list()  # type: typing.List[_Identifier]

Ejemplo n.º 17
0
	def __init__ (self, description = "An option for a probability object to select.", **kwargs):  # TODO add the spacings the ea's tunables use to descriptions?
		super().__init__(
			description = description,
			identifier = tunable.Tunable(description = "A string that will be returned if the option has been selected. The identifier should be unique to a probability object.", tunable_type = str, default = ""),
			weight = tunable.TunableRange(description = "This option's weight. The probability this option will be chosen is option's weight divided by the sum of every option's weight. This cannot be less than 0.", tunable_type = float, default = 0, minimum = 0),
			**kwargs)
Ejemplo n.º 18
0
class BirthControlPillsEffectGuide(tunable.HasTunableSingletonFactory,
                                   tunable.AutoFactoryInit,
                                   GuidesBase.GuideBase):
    FACTORY_TUNABLES = {
        "NeedPerGameMinute":
        tunable.Tunable(
            description=
            "The amount the birth control pills effect's need value changes per game minute. The need value goes from 0 to 1, with 1 being the most need.",
            tunable_type=float,
            default=0.00046296296),
        "EntrenchmentPerReproductiveMinute":
        Curve.TunableCurve(
            description=
            "A curve that indicates the amount the birth control pills effect's entrenchment value changes per sim minute, based on the need value. X inputs are the need values, this will be a number from 0 to 1. Y values are the rate at which the entrenchment value should change per reproductive minute."
        ),
        "MenstrualBuffRarityOptionAdjusters":
        tunable.TunableMapping(
            description=
            "Probability weight offset adjusters to be applied to the buff rarity probability object when the birth control pills effect is active.\n"
            "Expression adjusters will receive an additional value.\n"
            "The 'Need' value is a number indicating how recently the sim has taken the pill. 0 means the sim has just taken the pill, 1 means hasn't taken the pill recently. Around a value of 1, the 'Entrenched' value will slowly decrease."
            "The 'Entrenchment' value is a number indicating how effective the medication is and slowly increases the long a sim takes the pills. 0 is least effective and 1 is most effective.",
            key_type=tunable.Tunable(
                description=
                "An identifier that corresponds with an existing buff rarity option.",
                tunable_type=str,
                default="Abstain"),
            value_type=Probability.TunableOptionAdjuster()),
        "OvumReleaseSuppressionCurve":
        Curve.TunableCurve(
            description=
            "A curve that indicates the chance that the release of an ovum will be suppressed, based on the birth control's entrenchment value. X inputs are the entrenchment values. Y values are the chance that an ovum will be suppressed. Both x and y values should be limited to between 0 and 1."
        ),
        "GameMinutesBetweenPills":
        tunable.TunableRange(
            description=
            "The amount of game minutes since the last pill was taken until the sim is supposed to take another. This is only used to determine if a pill was missed or not.",
            tunable_type=float,
            default=1440,
            minimum=0),
        "GameMinutesBeforePillMissed":
        tunable.TunableRange(
            description=
            "The amount of game minutes since a pill was suppose to be taken until the sim has officially missed that pill. This is only used to determine if a pill was missed or not.",
            tunable_type=float,
            default=1080,
            minimum=0),
        "GameMinutesUntilPillInteractionAllowed":
        tunable.TunableRange(
            description=
            "The amount of game minute from since the last pill, until the take pill interaction should be unlocked for use.",
            tunable_type=float,
            default=960,
            minimum=0),
        "MissedPillsBeforeIntentional":
        tunable.TunableRange(
            description=
            "The number of missed pills before we start to consider them intentionally missed",
            tunable_type=int,
            default=4,
            minimum=0)
    }

    NeedPerGameMinute: float
    EntrenchmentPerReproductiveMinute: Curve.Curve

    MenstrualBuffRarityOptionAdjusters: typing.Dict[str,
                                                    Probability.OptionAdjuster]
    OvumReleaseSuppressionCurve: Curve.Curve

    GameMinutesBetweenPills: float
    GameMinutesBeforePillMissed: float

    GameMinutesUntilPillInteractionAllowed: float
    MissedPillsBeforeIntentional: int

    @classmethod
    def GetIdentifier(cls):
        """
		Get an identifier that can be used to pick out a specific guide from a group.
		"""

        return BirthControlPillsEffectGuideIdentifier

    @classmethod
    def GetDefaultGuide(cls):
        """
		Get a default instance of this guide.
		"""

        return DefaultBirthControlPillsEffectGuide

    @classmethod
    def GetGuide(cls, guideGroup):
        """
		Get this guide group's instance of this guide or the default guide, if the group doesn't have a copy.
		"""

        from NeonOcean.S4.Cycle.GuideGroups import Base as GuidesGroupsBase

        if not isinstance(guideGroup, GuidesGroupsBase.GuideGroup):
            raise Exceptions.IncorrectTypeException(
                guideGroup, "guideGroup", (GuidesGroupsBase.GuideGroup, ))

        guideGroupGuide = guideGroup.GetGuide(
            cls.GetIdentifier())  # type: typing.Optional[GuidesBase.GuideBase]
        return guideGroupGuide if guideGroupGuide is not None else cls.GetDefaultGuide(
        )
Ejemplo n.º 19
0
class RegistrationInformation(tunable.HasTunableSingletonFactory,
                              tunable.AutoFactoryInit):
    FACTORY_TUNABLES = {
        "InteractionListAttribute":
        tunable.Tunable(
            description=
            "The attribute name of the object's interaction list that the interaction needs to be added to.",
            tunable_type=str,
            default="_super_affordances"),
        "IgnoreMissingAttribute":
        tunable.Tunable(
            description=
            "Whether or not we should ignore an object that is missing the target interaction list or log an error instead.",
            tunable_type=bool,
            default=False)
    }

    InteractionListAttribute: str
    IgnoreMissingAttribute: bool

    def GetRelevantObjectInstanceIDs(self) -> typing.Set[int]:
        """
		Get a list of instances ids that point to the game objects that the interaction should be attached to.
		"""

        return set()

    def GetRelevantDefinitionInstanceIDs(self) -> typing.Set[int]:
        """
		Get a list of instances ids that point to the game object definitions that the interaction should be attached to.
		"""

        return set()

    def GetRelevantObjectTypes(self) -> typing.Set[str]:
        """
		Get a list of the types of game object that the interaction should be attached to.
		"""

        return set()

    def RegisterObject(self,
                       objectType: typing.Type[script_object.ScriptObject],
                       interactionReference: typing.Type) -> bool:
        """
		Add the specified interaction to the specified object. This will return false if the interaction could not be registered and true if it could.
		"""

        objectInteractionList = getattr(objectType,
                                        self.InteractionListAttribute, None)

        if isinstance(objectInteractionList, (tuple, list)):
            objectInteractionList += (interactionReference, )
            setattr(objectType, self.InteractionListAttribute,
                    objectInteractionList)

            return True
        else:
            if not self.IgnoreMissingAttribute:
                Debug.Log(
                    "Failed to find valid interaction list with the attribute name '%s' in an object with a type of '%s'"
                    % (self.InteractionListAttribute,
                       Types.GetFullName(interactionReference)),
                    This.Mod.Namespace,
                    Debug.LogLevels.Error,
                    group=This.Mod.Namespace,
                    owner=__name__,
                    lockIdentifier=__name__ + ":ObjectRegistration",
                    lockReference=interactionReference)

            return False
Ejemplo n.º 20
0
class MenstrualBuffBase(buff.Buff):
    INSTANCE_TUNABLES = {
        # TODO allow persistent buffs.
        "Rarity":
        tunable.OptionalTunable(description="How rare this buff is.",
                                tunable=ToolsTunable.TunablePythonEnumEntry(
                                    enumType=BuffsShared.BuffRarity,
                                    default=BuffsShared.BuffRarity.Common)),
        "ApplyCoolDown":
        tunable.Tunable(
            description=
            "Whether or not adding this buff should temporarily prevent new menstrual buffs from being added.",
            tunable_type=bool,
            default=True),
        "CyclePhaseTest":
        CyclePhaseTest.TunableFactory(
            description=
            "A test to determine if this buff can be added based on their current menstrual cycle."
        ),
    }

    Rarity: typing.Optional[BuffsShared.BuffRarity]
    ApplyCoolDown: bool
    CyclePhaseTest: CyclePhaseTest

    def on_add(self, from_load=False, apply_buff_loot=True) -> None:
        returnValue = super().on_add(from_load=from_load,
                                     apply_buff_loot=apply_buff_loot)

        ownerSimInfo = self._owner  # type: sim_info.SimInfo
        ownerSimSystem = Reproduction.GetSimSystem(
            ownerSimInfo
        )  # type: typing.Optional[ReproductionShared.ReproductiveSystem]

        if ownerSimSystem is None:
            return

        ownerEffectTracker = ownerSimSystem.GetTracker(
            UniversalShared.EffectTrackerIdentifier)  # type: typing.Any

        if ownerEffectTracker is None:
            return

        ownerMenstrualEffect = ownerEffectTracker.GetEffect(
            EffectsShared.MenstrualEffectTypeIdentifier)

        if ownerMenstrualEffect is None:
            return

        ownerMenstrualEffect.NotifyBuffAdded(self, fromLoad=from_load)

        return returnValue

    def on_remove(self, apply_loot_on_remove: bool = True) -> None:
        returnValue = super().on_remove(
            apply_loot_on_remove=apply_loot_on_remove)

        ownerSimInfo = self._owner  # type: sim_info.SimInfo
        ownerSimSystem = Reproduction.GetSimSystem(
            ownerSimInfo
        )  # type: typing.Optional[ReproductionShared.ReproductiveSystem]

        if ownerSimSystem is None:
            return

        ownerEffectTracker = ownerSimSystem.GetTracker(
            UniversalShared.EffectTrackerIdentifier)  # type: typing.Any

        if ownerEffectTracker is None:
            return

        ownerMenstrualEffect = ownerEffectTracker.GetEffect(
            EffectsShared.MenstrualEffectTypeIdentifier)

        if ownerMenstrualEffect is None:
            return

        ownerMenstrualEffect.NotifyBuffRemoved(self)

        return returnValue