class CheatWoohooTuning:
    CHEAT_WOOHOO_BITS = TunableList(
        TunableReference(manager=services.relationship_bit_manager()))
    CHEAT_WOOHOO_TRACK = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions=('RelationshipTrack', ))
    CHEAT_WOOHOO_COMMODITY = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions=('Commodity', ))
    CHEAT_WOOHOO_BUFF = TunableReference(manager=services.buff_manager())
    CHEAT_WOOHOO_SOCIALCONTEXT = TunableReference(
        manager=services.statistic_manager(),
        class_restrictions='RelationshipTrack')
    def _add_skills(cls, sim_info):
        if not cls._skills.explicit and not cls._skills.random:
            return
        statistic_manager = services.statistic_manager()
        available_skills_types = list(
            set([
                stat
                for stat in statistic_manager.types.values() if stat.is_skill
            ]) - cls._skills.blacklist)
        for skill_data in cls._skills.explicit:
            cls._add_skill_type(sim_info, skill_data, available_skills_types)
        if cls._skills.random:
            num_to_add = cls._skills.random.interval.random_int()
            available_random_skill_data = list(cls._skills.random.choices)
            while num_to_add > 0 and available_random_skill_data and available_skills_types:
                random_skill_data = random.choice(available_random_skill_data)
                if random_skill_data.skill is not None:
                    available_random_skill_data.remove(random_skill_data)
                #ERROR: Unexpected statement:   298 POP_BLOCK  |   299 JUMP_FORWARD

                if cls._add_skill_type(sim_info, random_skill_data,
                                       available_skills_types):
                    num_to_add -= 1
                    continue
                    continue
                continue
Beispiel #3
0
 def load(self, load_data):
     super().load(load_data)
     self.ranked_stat = services.statistic_manager().get(
         load_data.ranked_stat_id)
     self.rank_threshold = Threshold(
         value=load_data.threshold_value,
         comparison=Operator(load_data.threshold_comparison).function)
def _get_stat_from_string(stat_str, _connection):
    stat_name = stat_str.lower()
    stat = services.statistic_manager().get(stat_name)
    if stat is None:
        sims4.commands.output("Unable to get stat '{}'.".format(stat_name), _connection)
        return
    return stat
Beispiel #5
0
def _get_stat_from_string(stat_str, _connection):
    stat_name = stat_str.lower()
    stat = services.statistic_manager().get(stat_name)
    if stat is None:
        sims4.commands.output("Unable to get stat '{}'.".format(stat_name),
                              _connection)
        return
    return stat
Beispiel #6
0
class LockRankedStatisticData(LockData):
    REFRESH_EVENTS = (TestEvent.RankedStatisticChange, )
    FACTORY_TUNABLES = {
        'ranked_stat':
        TunableReference(
            description=
            "\n            The ranked statistic we are operating on. Sims won't be allowed to\n            traverse if they don't have this statistic.\n            ",
            manager=services.statistic_manager(),
            class_restrictions=('RankedStatistic', )),
        'rank_threshold':
        TunableThreshold(
            description=
            "\n            Sims that have ranked statistic's value inside the threshold are \n            not locked by the portal.\n            ",
            value=TunableRange(
                description=
                '\n                The number that describes the threshold.\n                ',
                tunable_type=int,
                default=1,
                minimum=0),
            default=sims4.math.Threshold(1, operator.ge))
    }

    def __init__(self, **kwargs):
        super().__init__(lock_type=LockType.LOCK_RANK_STATISTIC, **kwargs)

    def __repr__(self):
        return 'Ranked Stat: {}, Threshold: {}'.format(self.ranked_stat,
                                                       self.rank_threshold)

    def test_lock(self, sim):
        tracker = sim.sim_info.get_tracker(self.ranked_stat)
        if tracker is not None:
            ranked_stat_inst = tracker.get_statistic(self.ranked_stat)
            if ranked_stat_inst is not None and self.rank_threshold.compare(
                    ranked_stat_inst.rank_level):
                return LockResult(False, self.lock_type, self.lock_priority,
                                  self.lock_sides)
        return LockResult(True, self.lock_type, self.lock_priority,
                          self.lock_sides)

    def save(self, save_data):
        super().save(save_data)
        save_data.ranked_stat_id = self.ranked_stat.guid64
        save_data.threshold_value = self.rank_threshold.value
        save_data.threshold_comparison = Operator.from_function(
            self.rank_threshold.comparison)

    def load(self, load_data):
        super().load(load_data)
        self.ranked_stat = services.statistic_manager().get(
            load_data.ranked_stat_id)
        self.rank_threshold = Threshold(
            value=load_data.threshold_value,
            comparison=Operator(load_data.threshold_comparison).function)
class ContinuousStatisticModifier(HasTunableSingletonFactory,
                                  BaseGameEffectModifier):
    @staticmethod
    def _verify_tunable_callback(cls, tunable_name, source, value):
        if value.modifier_value == 0:
            logger.error(
                'Trying to tune a Continuous Statistic Modifier to have a value of 0 which will do nothing on: {}.',
                StackVar(('cls', )))

    FACTORY_TUNABLES = {
        'description':
        "\n        The modifier to add to the current statistic modifier of this continuous statistic,\n        resulting in it's increase or decrease over time. Adding this modifier to something by\n        default doesn't change, i.e. a skill, will start that skill to be added to over time.\n        ",
        'statistic':
        TunablePackSafeReference(
            description='\n        "The statistic we are operating on.',
            manager=services.statistic_manager()),
        'modifier_value':
        Tunable(description=
                '\n        The value to add to the modifier. Can be negative.',
                tunable_type=float,
                default=0),
        'verify_tunable_callback':
        _verify_tunable_callback
    }

    def __init__(self, statistic, modifier_value, **kwargs):
        super().__init__(GameEffectType.CONTINUOUS_STATISTIC_MODIFIER)
        self.statistic = statistic
        self.modifier_value = modifier_value

    def apply_modifier(self, sim_info):
        if self.statistic is None:
            return
        stat = sim_info.get_statistic(self.statistic)
        if stat is None:
            return
        stat.add_statistic_modifier(self.modifier_value)
        if isinstance(stat, Skill):
            sim_info.current_skill_guid = stat.guid64

    def remove_modifier(self, sim_info, handle):
        if self.statistic is None:
            return
        stat = sim_info.get_statistic(self.statistic)
        if stat is None:
            return
        stat.remove_statistic_modifier(self.modifier_value)
        if isinstance(stat, Skill):
            if stat._statistic_modifier <= 0:
                if sim_info.current_skill_guid == stat.guid64:
                    sim_info.current_skill_guid = 0
Beispiel #8
0
 def __init__(self, description=''):
     import statistics.commodity
     (super().__init__(
         description=description,
         key_type=TunableReference(
             services.statistic_manager(),
             class_restrictions=(statistics.commodity.Commodity, ),
             description=
             '\n                    The stat the modifier will apply to.\n                    '
         ),
         value_type=Tunable(
             float,
             0,
             description='Multiply statistic decay by this value.')), )
Beispiel #9
0
class TeamScorePoints(BaseGameLootOperation):
    def __init__(self, score_increase, score_increase_from_stat, **kwargs):
        super().__init__(**kwargs)
        self.score_increase = score_increase
        self.score_increase_from_stat = score_increase_from_stat

    @property
    def loot_type(self):
        return interactions.utils.LootType.TEAM_SCORE

    def _apply_to_subject_and_target(self, subject, target, resolver):
        (game, _) = get_game_references(resolver)
        if game is None:
            return False
        subject_obj = self._get_object_from_recipient(subject)
        if self.score_increase_from_stat is not None:
            stat = subject_obj.get_statistic(self.score_increase_from_stat)
            if stat is None:
                logger.error('Failed to find statistic {} from {}.',
                             self.score_increase_from_stat,
                             subject_obj,
                             owner='mkartika')
                return False
            score_increase = stat.get_value()
        else:
            score_increase = sims4.random.uniform(
                self.score_increase.lower_bound,
                self.score_increase.upper_bound)
        game.increase_score_by_points(subject_obj, score_increase)
        return True

    FACTORY_TUNABLES = {
        'score_increase':
        TunableInterval(
            description=
            '\n            An interval specifying the minimum and maximum score increases\n            from this loot. A random value in this interval will be\n            generated each time this loot is given.\n            ',
            tunable_type=int,
            default_lower=35,
            default_upper=50,
            minimum=0),
        'score_increase_from_stat':
        OptionalTunable(
            description=
            "\n            If enabled, the score will be increased by this statistic value\n            instead of by 'Score Increase' interval value.\n            ",
            tunable=TunableReference(
                description=
                '\n                The stat we are operating on.\n                ',
                manager=services.statistic_manager()))
    }
 def __init__(self, **kwargs):
     super().__init__(
         description=
         '\n            A tunable that increments a specified statistic by a specified\n            amount, runs a sequence, and then decrements the statistic by the\n            same amount.\n            ',
         stat=TunableReference(
             description=
             '\n                The statistic to increment and decrement.\n                ',
             manager=services.statistic_manager(),
             class_restrictions=(Statistic, Skill)),
         subject=TunableEnumFlags(
             description=
             '\n                The participant of the interaction on which the statistic will\n                be incremented and decremented.\n                ',
             enum_type=ParticipantType,
             default=ParticipantType.Object),
         amount=Tunable(
             description=
             '\n                The amount that will be incremented and decremented from the\n                specified statistic.\n                ',
             tunable_type=float,
             default=1),
         **kwargs)
    def _add_skills(cls, sim_info):
        if not cls._skills.explicit and not cls._skills.random:
            return
        statistic_manager = services.statistic_manager()
        available_skills_types = list(set([stat for stat in statistic_manager.types.values() if stat.is_skill]) - cls._skills.blacklist)
        for skill_data in cls._skills.explicit:
            cls._add_skill_type(sim_info, skill_data, available_skills_types)
        if cls._skills.random:
            num_to_add = cls._skills.random.interval.random_int()
            available_random_skill_data = list(cls._skills.random.choices)
            while num_to_add > 0 and available_random_skill_data and available_skills_types:
                random_skill_data = random.choice(available_random_skill_data)
                if random_skill_data.skill is not None:
                    available_random_skill_data.remove(random_skill_data)
                #ERROR: Unexpected statement:   298 POP_BLOCK  |   299 JUMP_FORWARD 

                if cls._add_skill_type(sim_info, random_skill_data, available_skills_types):
                    num_to_add -= 1
                    continue
                    continue
                continue
class BroadcasterEffectStatisticModifier(_BroadcasterEffectTested):
    FACTORY_TUNABLES = {
        'statistic':
        TunableReference(
            description=
            '\n            The statistic to be affected by the modifier.\n            ',
            manager=services.statistic_manager()),
        'modifier':
        Tunable(
            description=
            '\n            The modifier to apply to the tuned statistic.\n            ',
            tunable_type=float,
            default=0)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._modifier_handles = {}

    @classproperty
    def apply_when_linked(cls):
        return True

    def _apply_broadcaster_effect(self, broadcaster, affected_object):
        key = (affected_object.id, broadcaster.broadcaster_id)
        if key not in self._modifier_handles:
            autonomy_modifier = AutonomyModifier(
                statistic_modifiers={self.statistic: self.modifier})
            handle_id = affected_object.add_statistic_modifier(
                autonomy_modifier)
            if handle_id:
                self._modifier_handles[key] = handle_id

    def remove_broadcaster_effect(self, broadcaster, affected_object):
        key = (affected_object.id, broadcaster.broadcaster_id)
        if key in self._modifier_handles:
            affected_object.remove_statistic_modifier(
                self._modifier_handles[key])
            del self._modifier_handles[key]
Beispiel #13
0
class RelationshipTrackDecayLocker(HasTunableSingletonFactory, BaseGameEffectModifier):
    __qualname__ = 'RelationshipTrackDecayLocker'
    FACTORY_TUNABLES = {'description': '\n        A modifier for locking the decay of a relationship track.\n        ', 'relationship_track': TunableReference(description='\n        The relationship track to lock.\n        ', manager=services.statistic_manager(), class_restrictions=('RelationshipTrack',))}
    tunable = (TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',)),)

    def __init__(self, relationship_track, **kwargs):
        BaseGameEffectModifier.__init__(self, GameEffectType.RELATIONSHIP_TRACK_DECAY_LOCKER, **kwargs)
        self._track_type = relationship_track
        self._owner = None

    def apply_modifier(self, owner):
        self._owner = owner
        zone = services.current_zone()
        if not zone.is_households_and_sim_infos_loaded and not zone.is_zone_running:
            zone.register_callback(zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED, self._all_sim_infos_loaded_callback)
            return
        self._set_decay_lock_all_relationships(lock=True)
        self._initialize_create_relationship_callback()

    def _initialize_create_relationship_callback(self):
        tracker = self._owner.relationship_tracker
        tracker.add_create_relationship_listener(self._relationship_added_callback)

    def _all_sim_infos_loaded_callback(self):
        zone = services.current_zone()
        zone.unregister_callback(zone_types.ZoneState.HOUSEHOLDS_AND_SIM_INFOS_LOADED, self._all_sim_infos_loaded_callback)
        self._set_decay_lock_all_relationships(lock=True)
        self._initialize_create_relationship_callback()

    def _set_decay_lock_all_relationships(self, lock=True):
        tracker = self._owner.relationship_tracker
        sim_info_manager = services.sim_info_manager()
        for other_sim_id in tracker.target_sim_gen():
            other_sim_info = sim_info_manager.get(other_sim_id)
            track = tracker.get_relationship_track(other_sim_id, self._track_type, add=True)
            other_tracker = other_sim_info.relationship_tracker
            other_track = other_tracker.get_relationship_track(self._owner.id, self._track_type, add=True)
            if lock:
                track.add_decay_rate_modifier(0)
                other_track.add_decay_rate_modifier(0)
            else:
                track.remove_decay_rate_modifier(0)
                other_track.remove_decay_rate_modifier(0)

    def _relationship_added_callback(self, relationship):
        sim_a_track = relationship.get_track(self._track_type, add=True)
        sim_b_track = relationship.find_target_sim_info().relationship_tracker.get_relationship_track(relationship.sim_id, self._track_type, add=True)
        sim_a_track.add_decay_rate_modifier(0)
        sim_b_track.add_decay_rate_modifier(0)

    def remove_modifier(self, owner):
        tracker = owner.relationship_tracker
        tracker.remove_create_relationship_listener(self._relationship_added_callback)
        self._set_decay_lock_all_relationships(lock=False)
Beispiel #14
0
class ContinuousStatisticModifier(HasTunableSingletonFactory, BaseGameEffectModifier):
    __qualname__ = 'ContinuousStatisticModifier'
    FACTORY_TUNABLES = {'description': "\n        The modifier to add to the current statistic modifier of this continuous statistic,\n        resulting in it's increase or decrease over time. Adding this modifier to something by\n        default doesn't change, i.e. a skill, will start that skill to be added to over time.\n        ", 'statistic': TunableReference(description='\n        "The statistic we are operating on.', manager=services.statistic_manager()), 'modifier_value': Tunable(description='\n        The value to add to the modifier. Can be negative.', tunable_type=float, default=0)}

    def __init__(self, statistic, modifier_value, **kwargs):
        BaseGameEffectModifier.__init__(self, GameEffectType.CONTINUOUS_STATISTIC_MODIFIER)
        self.statistic = statistic
        self.modifier_value = modifier_value

    def apply_modifier(self, owner):
        stat = owner.get_statistic(self.statistic)
        if stat is None:
            stat = owner.add_statistic(self.statistic)
        stat.add_statistic_modifier(self.modifier_value)
        if isinstance(stat, Skill):
            owner.current_skill_guid = stat.guid64

    def remove_modifier(self, owner):
        stat = owner.get_statistic(self.statistic)
        if stat is None:
            return
        stat.remove_statistic_modifier(self.modifier_value)
        if isinstance(stat, Skill) and stat._statistic_modifier <= 0 and owner.current_skill_guid == stat.guid64:
            owner.current_skill_guid = 0
Beispiel #15
0
class Fish(objects.game_object.GameObject):
    __qualname__ = 'Fish'
    INSTANCE_TUNABLES = {
        'fishbowl_vfx':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            The name of the VFX to use when this fish is dropped in a fish bowl.\n            ',
            tunable_type=str,
            default=None,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'fishing_hole_vfx':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            The name of the VFX to use at the fishing hole (pond) where this\n            fish can be caught.\n            ',
            tunable_type=str,
            default=None,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'fishing_spot_vfx':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            The name of the VFX to use at the fishing spot (sign) where this\n            fish can be caught.\n            ',
            tunable_type=str,
            default=None,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'wall_mounted_object':
        sims4.tuning.tunable.TunableReference(
            description=
            '\n            When this fish is mounted to the wall, this is the object it will turn in to.\n            ',
            manager=services.definition_manager(),
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'fish_weight':
        sims4.tuning.tunable.TunableInterval(
            description=
            '\n            The weight range of this fish. Each fish caught will get a random\n            weight that lies between these numbers, inclusively.\n            ',
            tunable_type=float,
            default_lower=0.0,
            default_upper=1.0,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'catchable_tests':
        event_testing.tests.TunableTestSet(
            description=
            "\n            If these tests pass, the Sim can catch this fish.\n            If these tests fail, the Sim can not catch this fish.\n            This doesn't stop the Sim from trying to catch these fish, but it\n            will never happen.\n            \n            DO NOT add bait buffs here. Those should be added to the Required Bait tunable field.\n            \n            When testing on fishing skill be sure to enable 'Use Effective\n            Skill Level' since baits can change it.\n            ",
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'required_bait_buff':
        sims4.tuning.tunable.OptionalTunable(
            description=
            '\n            The bait buff that is required to catch this fish.\n            \n            If this is tuned, this fish can not be caught without the required bait.\n            If this is not tuned, this fish can be caught with or without bait.\n            \n            Note: Bait buffs are the only buffs that should be tuned here.\n            If you want to gate this fish on a non-bait buff, use the Catchable Tests.\n            ',
            tunable=sims4.tuning.tunable.TunableReference(
                manager=services.buff_manager()),
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'fish_type':
        sims4.tuning.tunable.Tunable(
            description=
            "\n            The asm parameter for the size of the fish. If you're unsure what\n            this should be set to, talk to the animator or modeler and ask what\n            fish type this fish should be.\n            ",
            tunable_type=str,
            default=None,
            source_query=sims4.tuning.tunable_base.SourceQueries.
            SwingEnumNamePattern.format('fishType'),
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'skill_weight_curve':
        sims4.tuning.geometric.TunableCurve(
            description=
            "\n            This curve represents the mean weight in kg of the fish based on the Sims's fishing skill level.\n            The X axis is the Sim's effective fishing skill level.\n            The Y axis is the mean weight, in kg, of the fish.\n            The mean weight will be modified by the Mean Weight Deviation field.\n            ",
            x_axis_name='Effective Fishing Skill Level',
            y_axis_name='Mean Weight (kg)',
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'mean_weight_deviation':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            This is the amount of deviation from the mean the weight can be.\n            The mean weight is first decided then multiplied by this number.\n            The result is both added and subtracted from the mean weight to get\n            the min/max possible weight of the fish. We then pick a random\n            number between the min and max to determine the final weight of the\n            fish.\n            \n            Example: Assume Mean Weight = 2 and Mean Weight Deviation = 0.2\n            2 x 0.2 = 0.4\n            min = 2 - 0.4 = 1.6\n            max = 2 + 0.4 = 2.4\n            A random number is chosen between 1.6 and 2.4, inclusively.\n            ',
            tunable_type=float,
            default=1,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'weight_money_multiplier':
        sims4.tuning.tunable.Tunable(
            description=
            '\n            The weight of the fish will be multiplied by this number then the\n            result of that multiplication will be added to the base value of\n            the fish.\n            ',
            tunable_type=float,
            default=1,
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING),
        'buffs_on_catch':
        sims4.tuning.tunable.TunableList(
            description=
            '\n            A list of buffs to award the Sim when they catch this fish.\n            ',
            tunable=buffs.tunable.TunableBuffReference(),
            tuning_group=sims4.tuning.tunable_base.GroupNames.FISHING)
    }
    FISHING_SKILL_STATISTIC = sims4.tuning.tunable.TunableReference(
        description=
        '\n        The fishing skill stat. This just makes lookups on the fishing skill easier.\n        ',
        manager=services.statistic_manager())
    FISH_FRESHNESS_STATE = objects.components.state.ObjectState.TunableReference(
        description='\n        The statistic used for fish freshness.\n        '
    )
    WEIGHT_STATISTIC = sims4.tuning.tunable.TunableReference(
        description=
        '\n        The weight statistic that will be added to the fish and set as they\n        are caught.\n        ',
        manager=services.statistic_manager())
    LOCALIZED_WEIGHT = sims4.localization.TunableLocalizedStringFactory(
        description=
        "\n        How the weight should appear when used in other strings, like the\n        'catch fish' notification. i.e. '2.2 kg'\n        {0.Number} = weight value\n        "
    )
    MINIMUM_FISH_WEIGHT = 0.1

    @sims4.utils.flexmethod
    def can_catch(cls, inst, resolver, require_bait=False):
        inst_or_cls = inst if inst is not None else cls
        if require_bait:
            sim = resolver.get_participant(interactions.ParticipantType.Actor)
            if inst_or_cls.required_bait_buff and not sim.has_buff(
                    inst_or_cls.required_bait_buff):
                return False
        return inst_or_cls.catchable_tests.run_tests(resolver)

    def on_add(self):
        super().on_add()
        self.add_state_changed_callback(self._on_state_or_name_changed)
        self.add_name_changed_callback(self._on_state_or_name_changed)

    def get_object_property(self, property_type):
        if property_type == objects.game_object_properties.GameObjectProperty.FISH_FRESHNESS:
            return self.get_state(self.FISH_FRESHNESS_STATE).display_name
        return super().get_object_property(property_type)

    def initialize_fish(self, sim):
        fishing_stat = sim.get_statistic(self.FISHING_SKILL_STATISTIC)
        skill_level = 1 if fishing_stat is None else sim.get_effective_skill_level(
            fishing_stat)
        mean_weight = self.skill_weight_curve.get(skill_level)
        deviation = mean_weight * self.mean_weight_deviation
        weight_min = max(mean_weight - deviation, self.MINIMUM_FISH_WEIGHT)
        weight_max = mean_weight + deviation
        actual_weight = sims4.random.uniform(weight_min, weight_max)
        fish_stat_tracker = self.get_tracker(self.WEIGHT_STATISTIC)
        fish_stat_tracker.set_value(self.WEIGHT_STATISTIC, actual_weight)
        self.update_ownership(sim)
        self.update_object_tooltip()

    def get_catch_buffs_gen(self):
        yield self.buffs_on_catch

    def get_localized_weight(self):
        stat_tracker = self.get_tracker(self.WEIGHT_STATISTIC)
        return self.LOCALIZED_WEIGHT(
            stat_tracker.get_user_value(self.WEIGHT_STATISTIC))

    def _on_state_or_name_changed(self, *_, **__):
        fishbowl = self._try_get_fishbowl()
        if fishbowl is not None:
            fishbowl.update_object_tooltip()

    def on_remove(self):
        self.remove_state_changed_callback(self._on_state_or_name_changed)
        self.remove_name_changed_callback(self._on_state_or_name_changed)
        super().on_remove()

    def _ui_metadata_gen(self):
        tooltip_component = self.get_component(
            objects.components.types.TOOLTIP_COMPONENT)
        yield tooltip_component._ui_metadata_gen()

    def _try_get_fishbowl(self):
        inventory_owner = self.inventoryitem_component._last_inventory_owner
        if isinstance(inventory_owner, fishing.fish_bowl_object.FishBowl):
            return inventory_owner
 def __init__(self, **kwargs):
     super().__init__(description='\n            A tunable that increments a specified statistic by a specified\n            amount, runs a sequence, and then decrements the statistic by the\n            same amount.\n            ', stat=TunableReference(description='\n                The statistic to increment and decrement.\n                ', manager=services.statistic_manager(), class_restrictions=(Statistic, Skill)), subject=TunableEnumFlags(description='\n                The participant of the interaction on which the statistic will\n                be incremented and decremented.\n                ', enum_type=ParticipantType, default=ParticipantType.Object), amount=Tunable(description='\n                The amount that will be incremented and decremented from the\n                specified statistic.\n                ', tunable_type=float, default=1), **kwargs)
class OwningHouseholdComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=OWNING_HOUSEOLD_COMPONENT):

    @staticmethod
    def _verify_tunable_callback(cls, tunable_name, source, commodity_to_add, **kwargs):
        for commodity in commodity_to_add:
            if commodity.persisted_tuning:
                logger.error('Commodity {} is set to persist and therefore cannot be added by the Owning Household Component.', commodity)

    FACTORY_TUNABLES = {'commodity_to_add': TunableList(description='\n            A list of commodities to add to the Sims of the owning household.\n            ', tunable=TunableReference(description='\n                A commodity to add to the Sim.  Commodities must not persist.\n                ', manager=services.statistic_manager(), class_restrictions=('Commodity',)), unique_entries=True), 'verify_tunable_callback': _verify_tunable_callback}

    def _add_commodities_to_sims(self, sim):
        commodity_tracker = sim.commodity_tracker
        for commodity in self.commodity_to_add:
            commodity_tracker.add_statistic(commodity)

    def _on_sim_spawned(self, sim):
        if services.owning_household_id_of_active_lot() != sim.household_id:
            return
        self._add_commodities_to_sims(sim)

    def on_add(self, *_, **__):
        services.sim_spawner_service().register_sim_spawned_callback(self._on_sim_spawned)
        owning_household = services.owning_household_of_active_lot()
        if owning_household is None:
            return
        for sim in owning_household.instanced_sims_gen(allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED):
            self._add_commodities_to_sims(sim)

    def on_remove(self, *_, **__):
        services.sim_spawner_service().unregister_sim_spawned_callback(self._on_sim_spawned)
Beispiel #18
0
 def __init__(self):
     super().__init__(
         commodities=TunableSet(
             TunableReference(
                 services.statistic_manager(),
                 description='The type of commodity to search for.'),
             description=
             'List of commodities to run parameterized autonomy against after running this interaction.'
         ),
         static_commodities=TunableSet(
             TunableReference(
                 services.static_commodity_manager(),
                 description='The type of static commodity to search for.'),
             description=
             'List of static commodities to run parameterized autonomy against after running this interaction.'
         ),
         same_target_only=Tunable(
             bool,
             False,
             description=
             'If checked, only interactions on the same target as this interaction will be considered.'
         ),
         retain_priority=Tunable(
             bool,
             True,
             description=
             'If checked, this autonomy request is run at the same priority level as the interaction creating it.  If unchecked, the interaction chosen will run at low priority.'
         ),
         consider_same_target=Tunable(
             bool,
             True,
             description=
             'If checked, parameterized autonomy will consider interactions on the current Target.'
         ),
         retain_carry_target=Tunable(
             bool,
             True,
             description=
             "If checked, the interactions considered for autonomy will retain this interaction's carry target. It is useful to uncheck this if the desired autonomous interactions need not to consider carry, e.g. the Grim Reaper finding arbitrary interactions while in an interaction holding his scythe as a carry target."
         ),
         randomization_override=OptionalTunable(
             description=
             '\n                    If enabled then the parameterized autonomy will run with\n                    an overwritten autonomy randomization settings.\n                    ',
             tunable=TunableEnumEntry(
                 description=
                 '\n                        The autonomy randomization setting that will be used.\n                        ',
                 tunable_type=AutonomyRandomization,
                 default=AutonomyRandomization.UNDEFINED)),
         radius_to_consider=Tunable(
             description=
             '\n                    The radius around the sim that targets must be in to be valid for Parameterized \n                    Autonomy.  Anything outside this radius will be ignored.  A radius of 0 is considered\n                    infinite.\n                    ',
             tunable_type=float,
             default=0),
         consider_scores_of_zero=Tunable(
             description=
             '\n                    The autonomy request will consider scores of zero.  This allows sims to to choose things they \n                    might not desire.\n                    ',
             tunable_type=bool,
             default=False),
         test_connectivity_to_target=Tunable(
             description=
             '\n                    If checked, this test will ensure the Sim can pass a pt to\n                    pt connectivity check to the advertising object.\n                    ',
             tunable_type=bool,
             default=True),
         retain_context_source=Tunable(
             description=
             '\n                    If True, any interactions that run as a result of\n                    this request will run with the same context source as the creating\n                    interaction. If False, it will default to InteractionContext.SOURCE_AUTONOMY.\n                    ',
             tunable_type=bool,
             default=False),
         ignore_user_directed_and_autonomous=Tunable(
             description=
             '\n                    If True, parametrized request will ignore autonomous and\n                    user directed checks.  This means, that the request may\n                    push a user directed or autonomous interaction without\n                    restriction.\n                    A use case for this is when a vampire runs pre run autonomy\n                    to enable its dark form, we want to keep the context as \n                    user directed (to keep the high priority of the\n                    interaction), but the interaction being run can normally\n                    not be user directed (since we dont want it on the pie\n                    menu). \n                    ',
             tunable_type=bool,
             default=False),
         description=
         'Commodities and StaticCommodities will be combined, so interactions must support at least one commodity from both lists.'
     )
class LocalizationTokens(HasTunableSingletonFactory, AutoFactoryInit):
    TOKEN_PARTICIPANT = 0
    TOKEN_MONEY = 1
    TOKEN_STATISTIC = 2
    TOKEN_OBJECT_PROPERTY = 3
    TOKEN_INTERACTION_COST = 4
    TOKEN_DEFINITION = 5
    TOKEN_CAREER_DATA = 6
    TOKEN_ASSOCIATED_CLUB = 7
    TOKEN_GAME_COMPONENT = 8
    TOKEN_SICKNESS = 9
    TOKEN_PARTICIPANT_COUNT = 10
    TOKEN_INTERACTION_PAYOUT = 11
    TOKEN_HOLIDAY = 12
    TOKEN_CURRENT_TRENDS = 13
    TOKEN_LIFESTYLE_BRAND = 14
    TOKEN_GLOBAL_POLICY = 15
    TOKEN_PICKED_PART = 16
    TOKEN_SCHOLARSHIP_LETTER = 17
    TOKEN_BUCK = 18
    TOKEN_CIVIC_POLICY = 19
    TOKEN_STREET = 20
    TOKEN_VENUE = 21
    TOKEN_CAREER_DATA_CURRENT_LEVEL_NAME = 1
    TOKEN_CAREER_DATA_CURRENT_LEVEL_SALARY = 2
    TOKEN_CAREER_DATA_NEXT_LEVEL_NAME = 3
    TOKEN_CAREER_DATA_NEXT_LEVEL_SALARY = 4
    TOKEN_CAREER_DATA_PREVIOUS_LEVEL_NAME = 5
    TOKEN_CAREER_DATA_PREVIOUS_LEVEL_SALARY = 6
    TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE = 1
    TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE_SIM = 2
    TOKEN_SCHOLARSHIP_LETTER_APPLICANT_NAME = 1
    TOKEN_SCHOLARSHIP_LETTER_DATA_AMOUNT = 2
    TOKEN_SCHOLARSHIP_LETTER_DATA_NAME = 3
    TOKEN_SCHOLARSHIP_LETTER_DATA_DESCRIPTION = 4
    TOKEN_CIVIC_POLICY_VOTING_START_TIME = 1
    TOKEN_CIVIC_POLICY_VOTING_END_TIME = 2
    TOKEN_STREET_POLICY_NAME_UP_FOR_REPEAL = 1
    TOKEN_STREET_POLICY_NAME_BALLOTED_RANDOM_LOSING = 2
    TOKEN_STREET_POLICY_NAME_BALLOTED_WINNING = 3
    TOKEN_VENUE_ACTIVE_VENUE_NAME = 1
    TOKEN_VENUE_SOURCE_VENUE_NAME = 2
    _DatalessToken = collections.namedtuple('_DatalessToken', 'token_type')
    FACTORY_TUNABLES = {
        'tokens':
        TunableList(
            description=
            "\n            A list of tokens that will be returned by this factory. Any string\n            that uses this token will have token '0' be set to the first\n            element, '1' to the second element, and so on. Do not let the list\n            inheritance values confuse you; regardless of what the list element\n            index is, the first element will always be 0, the second element 1,\n            and so on.\n            ",
            tunable=TunableVariant(
                description=
                '\n                Define what the token at the specified index is.\n                ',
                participant_type=TunableTuple(
                    description=
                    '\n                    The token is a Sim or object participant from the\n                    interaction.\n                    ',
                    locked_args={'token_type': TOKEN_PARTICIPANT},
                    objects=TunableObjectGeneratorVariant(
                        participant_default=ParticipantType.Actor),
                    formatter=TunableObjectLocalizationTokenFormatterVariant(
                        description=
                        '\n                        Define the format for this token.\n                        '
                    )),
                participant_count=TunableTuple(
                    description=
                    '\n                    The number of participants of the specified type.\n                    ',
                    locked_args={'token_type': TOKEN_PARTICIPANT_COUNT},
                    objects=TunableObjectGeneratorVariant(
                        participant_default=ParticipantType.ObjectChildren)),
                definition=TunableTuple(
                    description=
                    "\n                    A catalog definition to use as a token. This is useful if\n                    you want to properly localize an object's name or\n                    description.\n                    ",
                    locked_args={'token_type': TOKEN_DEFINITION},
                    definition=TunableReference(
                        manager=services.definition_manager())),
                money_amount=TunableTuple(
                    description=
                    '\n                    The token is a number representing the amount of Simoleons\n                    that were awarded in loot to the specified participant.\n                    ',
                    locked_args={'token_type': TOKEN_MONEY},
                    participant=TunableEnumEntry(
                        description=
                        '\n                        The participant for whom we fetch the earned amount of\n                        money.\n                        ',
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor)),
                buck_amount=TunableTuple(
                    description=
                    '\n                    The token is a number repesenting the amount of the \n                    specified buck type the specified participant has.\n                    ',
                    locked_args={'token_type': TOKEN_BUCK},
                    participant=TunableEnumEntry(
                        description=
                        "\n                        The participant from whom we will fetch the specified\n                        statistic's value.\n                        ",
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor),
                    bucks_type=TunableEnumEntry(
                        tunable_type=BucksType,
                        default=BucksType.INVALID,
                        invalid_enums=BucksType.INVALID)),
                statistic_value=TunableTuple(
                    description=
                    '\n                    The token is a number representing the value of a specific\n                    statistic from the selected participant.\n                    ',
                    locked_args={'token_type': TOKEN_STATISTIC},
                    participant=TunableEnumEntry(
                        description=
                        "\n                        The participant from whom we will fetch the specified\n                        statistic's value.\n                        ",
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor),
                    statistic=TunableReference(
                        description=
                        "\n                        The statistic's whose value we want to fetch.\n                        ",
                        manager=services.statistic_manager())),
                object_property=TunableTuple(
                    description=
                    '\n                    The token is a property of a game object.  This could be \n                    catalog properties like its price or its rarity which is a \n                    property given by a component.\n                    ',
                    locked_args={'token_type': TOKEN_OBJECT_PROPERTY},
                    obj_property=TunableEnumEntry(
                        description=
                        '\n                        The property of the object that we will request.\n                        ',
                        tunable_type=GameObjectProperty,
                        default=GameObjectProperty.CATALOG_PRICE)),
                career_data=TunableTuple(
                    description=
                    '\n                    The token is a localized string, number, or Sim,\n                    representing the specified career data for the specified\n                    participant.\n                    ',
                    locked_args={'token_type': TOKEN_CAREER_DATA},
                    participant=TunableEnumEntry(
                        description=
                        "\n                        The participant's whose career data we care about.\n                        ",
                        tunable_type=ParticipantType,
                        default=ParticipantType.Actor),
                    career_type=TunableReference(
                        description=
                        '\n                        The career we care about.\n                        ',
                        manager=services.get_instance_manager(Types.CAREER)),
                    career_data=TunableVariant(
                        description=
                        '\n                        The piece of data to fetch.\n                        ',
                        locked_args={
                            'current_level_name':
                            TOKEN_CAREER_DATA_CURRENT_LEVEL_NAME,
                            'current_level_salary':
                            TOKEN_CAREER_DATA_CURRENT_LEVEL_SALARY,
                            'next_level_name':
                            TOKEN_CAREER_DATA_NEXT_LEVEL_NAME,
                            'next_level_salary':
                            TOKEN_CAREER_DATA_NEXT_LEVEL_SALARY,
                            'previous_level_name':
                            TOKEN_CAREER_DATA_PREVIOUS_LEVEL_NAME,
                            'previous_level_salary':
                            TOKEN_CAREER_DATA_PREVIOUS_LEVEL_SALARY
                        },
                        default='current_level_name')),
                associated_club=TunableTuple(
                    description=
                    '\n                    The token is a stored "associated_club" on this\n                    interaction. Only works with ClubMixerInteractions or\n                    ClubSuperInteractions.\n                    ',
                    locked_args={'token_type': TOKEN_ASSOCIATED_CLUB}),
                game_component_data=TunableTuple(
                    description=
                    '\n                    The token is a localized number or Sim representing \n                    the specified game component data from game component.\n                    ',
                    locked_args={'token_type': TOKEN_GAME_COMPONENT},
                    participant=TunableEnumEntry(
                        description=
                        "\n                        The participant's from whom the game component data \n                        we want to fetch.\n                        ",
                        tunable_type=ParticipantType,
                        default=ParticipantType.Object),
                    game_component_data=TunableVariant(
                        description=
                        '\n                        The piece of data to fetch.\n                        ',
                        locked_args={
                            'high_score':
                            TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE,
                            'high_score_sim':
                            TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE_SIM
                        },
                        default='high_score')),
                scholarship_letter_component_data=TunableTuple(
                    description=
                    '\n                    The token can be used to get strings representing scholarship\n                    information from the scholarship letter component of an object.\n                    ',
                    locked_args={'token_type': TOKEN_SCHOLARSHIP_LETTER},
                    participant=TunableEnumEntry(
                        description=
                        '\n                        The participant from whom to get the scholarship letter\n                        component data.\n                        ',
                        tunable_type=ParticipantTypeObject,
                        default=ParticipantTypeObject.Object),
                    scholarship_letter_component_data=TunableVariant(
                        description=
                        '\n                        The piece of data to fetch.\n                        ',
                        locked_args={
                            'applicant_name':
                            TOKEN_SCHOLARSHIP_LETTER_APPLICANT_NAME,
                            'scholarship_amount':
                            TOKEN_SCHOLARSHIP_LETTER_DATA_AMOUNT,
                            'scholarship_name':
                            TOKEN_SCHOLARSHIP_LETTER_DATA_NAME,
                            'scholarship_description':
                            TOKEN_SCHOLARSHIP_LETTER_DATA_DESCRIPTION
                        },
                        default='applicant_name')),
                sickness=TunableTuple(
                    description=
                    '\n                    The token is the name of the sickness on the specified Sim.\n                    ',
                    locked_args={'token_type': TOKEN_SICKNESS},
                    participant=TunableEnumEntry(
                        description=
                        '\n                        The participant who is sick.\n                        ',
                        tunable_type=ParticipantType,
                        default=ParticipantType.TargetSim)),
                lifestyle_brand=TunableTuple(
                    description=
                    '\n                    The token used to display the name of a Lifestyle Brand \n                    owned by a Sim.\n                    ',
                    locked_args={'token_type': TOKEN_LIFESTYLE_BRAND},
                    participant=TunableEnumEntry(
                        description=
                        '\n                        The participant who owns the lifestyle brand.\n                        ',
                        tunable_type=ParticipantTypeSingle,
                        default=ParticipantType.TargetSim)),
                global_policy=TunableTuple(
                    description=
                    '\n                    The token used to display data from the tuned global policy.\n                    ',
                    locked_args={'token_type': TOKEN_GLOBAL_POLICY},
                    global_policy=TunableReference(
                        description=
                        '\n                        The global policy from which data is displayed.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.SNIPPET),
                        class_restrictions=('GlobalPolicy', )),
                    token_property=TunableEnumEntry(
                        description=
                        "\n                        Which property from the Global Policy Token to use. NAME\n                        will use the policy's display name, PROGRESS will use\n                        the progress made over the max progress value. \n                        ",
                        tunable_type=GlobalPolicyTokenType,
                        default=GlobalPolicyTokenType.NAME)),
                picked_part=TunableTuple(
                    description=
                    '\n                    The token used to display the name of a picked part.\n                    ',
                    locked_args={'token_type': TOKEN_PICKED_PART}),
                civic_policy=TunableTuple(
                    description=
                    '\n                    Tokens for the Civic Policy system.\n                    ',
                    locked_args={'token_type': TOKEN_CIVIC_POLICY},
                    civic_policy_data=TunableVariant(
                        description=
                        '\n                        The specific value to display.\n                        ',
                        locked_args={
                            'voting_start_time':
                            TOKEN_CIVIC_POLICY_VOTING_START_TIME,
                            'voting_end_time':
                            TOKEN_CIVIC_POLICY_VOTING_END_TIME
                        },
                        default='voting_start_time')),
                street=TunableTuple(
                    description='\n                    ',
                    locked_args={'token_type': TOKEN_STREET},
                    street=TunableLocalizationStreetSelector.TunableFactory(),
                    street_data=TunableVariant(
                        description=
                        '\n                        The piece of data to fetch.\n                        ',
                        locked_args={
                            'policy_up_for_repeal':
                            TOKEN_STREET_POLICY_NAME_UP_FOR_REPEAL,
                            'random_losing_balloted_policy':
                            TOKEN_STREET_POLICY_NAME_BALLOTED_RANDOM_LOSING,
                            'winning_balloted_policy':
                            TOKEN_STREET_POLICY_NAME_BALLOTED_WINNING
                        },
                        default='policy_up_for_repeal')),
                venue=TunableTuple(
                    description='\n                    ',
                    locked_args={'token_type': TOKEN_VENUE},
                    venue_data=TunableVariant(
                        description='\n                        ',
                        locked_args={
                            'active_venue': TOKEN_VENUE_ACTIVE_VENUE_NAME,
                            'source_venue': TOKEN_VENUE_SOURCE_VENUE_NAME
                        },
                        default='active_venue')),
                locked_args={
                    'interaction_cost':
                    _DatalessToken(token_type=TOKEN_INTERACTION_COST),
                    'interaction_payout':
                    _DatalessToken(token_type=TOKEN_INTERACTION_PAYOUT),
                    'active_holiday':
                    _DatalessToken(token_type=TOKEN_HOLIDAY),
                    'current_trends':
                    _DatalessToken(token_type=TOKEN_CURRENT_TRENDS)
                },
                default='participant_type'))
    }

    def _get_token(self, resolver, token_data):
        if token_data.token_type == self.TOKEN_PARTICIPANT:
            participants = token_data.objects.get_objects(resolver)
            return token_data.formatter(participants)
        if token_data.token_type == self.TOKEN_PARTICIPANT_COUNT:
            participants = token_data.objects.get_objects(resolver)
            if not participants:
                return 0
            return len(participants)
        if token_data.token_type == self.TOKEN_DEFINITION:
            return token_data.definition
        elif token_data.token_type == self.TOKEN_MONEY:
            interaction = getattr(resolver, 'interaction', None)
            if interaction is not None:
                from interactions.money_payout import MoneyLiability
                money_liability = interaction.get_liability(
                    MoneyLiability.LIABILITY_TOKEN)
                if money_liability is not None:
                    return money_liability.amounts[token_data.participant]
                return 0
        return 0
        if token_data.token_type == self.TOKEN_BUCK:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            tracker = BucksUtils.get_tracker_for_bucks_type(
                token_data.bucks_type, owner_id=participant.id)
            if not tracker:
                return 0
            return tracker.get_bucks_amount_for_type(token_data.bucks_type)
        if token_data.token_type == self.TOKEN_STATISTIC:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            if participant is not None:
                tracker = participant.get_tracker(token_data.statistic)
                if tracker is not None:
                    return tracker.get_value(token_data.statistic)
        if token_data.token_type == self.TOKEN_OBJECT_PROPERTY:
            participant = resolver.get_participant(ParticipantType.Object)
            if participant is None:
                return
            return participant.get_object_property(token_data.obj_property)
        if token_data.token_type == self.TOKEN_INTERACTION_COST:
            interaction = getattr(resolver, 'interaction', None)
            if interaction is not None:
                return interaction.get_simoleon_cost()
            affordance = getattr(resolver, 'affordance', None)
            if affordance is not None:
                return affordance.get_simoleon_cost(target=resolver.target,
                                                    context=resolver.context)
        if token_data.token_type == self.TOKEN_INTERACTION_PAYOUT:
            interaction = getattr(resolver, 'interaction', None)
            if interaction is not None:
                return interaction.get_simoleon_payout()
            affordance = getattr(resolver, 'affordance', None)
            if affordance is not None:
                return affordance.get_simoleon_payout(target=resolver.target,
                                                      context=resolver.context)
        if token_data.token_type == self.TOKEN_ASSOCIATED_CLUB:
            if resolver.interaction is not None:
                club = getattr(resolver.interaction, 'associated_club')
            else:
                club = resolver.interaction_parameters.get('associated_club')
            if club is not None:
                return club.name
        if token_data.token_type == self.TOKEN_CAREER_DATA:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            if participant is not None:
                if participant.career_tracker is None:
                    return
                career = participant.career_tracker.get_career_by_uid(
                    token_data.career_type.guid64)
                if career is not None:
                    if token_data.career_data == self.TOKEN_CAREER_DATA_CURRENT_LEVEL_NAME:
                        current_level = career.current_level_tuning
                        return current_level.get_title(participant)
                    if token_data.career_data == self.TOKEN_CAREER_DATA_CURRENT_LEVEL_SALARY:
                        current_level = career.current_level_tuning
                        return current_level.simoleons_per_hour
                    if token_data.career_data == self.TOKEN_CAREER_DATA_NEXT_LEVEL_NAME:
                        next_level = career.next_level_tuning
                        if next_level is not None:
                            return next_level.get_title(participant)
                    elif token_data.career_data == self.TOKEN_CAREER_DATA_NEXT_LEVEL_SALARY:
                        next_level = career.next_level_tuning
                        if next_level is not None:
                            return next_level.simoleons_per_hour
                    elif token_data.career_data == self.TOKEN_CAREER_DATA_PREVIOUS_LEVEL_NAME:
                        previous_level = career.previous_level_tuning
                        if previous_level is not None:
                            return previous_level.get_title(participant)
                    elif token_data.career_data == self.TOKEN_CAREER_DATA_PREVIOUS_LEVEL_SALARY:
                        previous_level = career.previous_level_tuning
                        if previous_level is not None:
                            return previous_level.simoleons_per_hour
        if token_data.token_type == self.TOKEN_GAME_COMPONENT:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            if participant is not None:
                game = participant.game_component
                if game is None:
                    return
                if token_data.game_component_data == self.TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE and game.high_score is not None:
                    return game.high_score
                if token_data.game_component_data == self.TOKEN_GAME_COMPONENT_DATA_HIGH_SCORE_SIM and game.high_score_sim_ids:
                    high_score_sim_id = game.high_score_sim_ids[0]
                    return services.sim_info_manager().get(high_score_sim_id)
        if token_data.token_type == self.TOKEN_SCHOLARSHIP_LETTER:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            if participant is not None:
                scholarship_letter_component = participant.scholarship_letter_component
                if scholarship_letter_component is None:
                    return
                if token_data.scholarship_letter_component_data == self.TOKEN_SCHOLARSHIP_LETTER_APPLICANT_NAME:
                    return scholarship_letter_component.get_applicant_name()
                if token_data.scholarship_letter_component_data == self.TOKEN_SCHOLARSHIP_LETTER_DATA_AMOUNT:
                    return scholarship_letter_component.get_scholarship_amount(
                    )
                if token_data.scholarship_letter_component_data == self.TOKEN_SCHOLARSHIP_LETTER_DATA_NAME:
                    return scholarship_letter_component.get_scholarship_name()
                if token_data.scholarship_letter_component_data == self.TOKEN_SCHOLARSHIP_LETTER_DATA_DESCRIPTION:
                    return scholarship_letter_component.get_scholarship_description(
                    )
        if token_data.token_type == self.TOKEN_SICKNESS:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            if participant is None or not participant.is_sim:
                return
            current_sickness = participant.current_sickness
            if current_sickness is None:
                return
            return current_sickness.display_name(participant)
        if token_data.token_type == self.TOKEN_GLOBAL_POLICY:
            global_policy = services.global_policy_service().get_global_policy(
                token_data.global_policy, create=False)
            if global_policy is None:
                return token_data.global_policy.get_non_active_display(
                    token_data)
            return global_policy.get_active_policy_display(token_data)
        if token_data.token_type == self.TOKEN_HOLIDAY:
            active_household = services.active_household()
            if active_household.holiday_tracker is None:
                return
            holiday_id = active_household.holiday_tracker.get_active_or_upcoming_holiday(
            )
            if holiday_id is None:
                return
            return services.holiday_service().get_holiday_display_name(
                holiday_id)
        if token_data.token_type == self.TOKEN_CURRENT_TRENDS:
            trend_service = services.trend_service()
            if trend_service is None:
                return
            return trend_service.get_current_trends_loc_string()
        if token_data.token_type == self.TOKEN_LIFESTYLE_BRAND:
            participant = resolver.get_participant(
                participant_type=token_data.participant)
            lifestyle_brand_tracker = participant.lifestyle_brand_tracker
            if lifestyle_brand_tracker is None:
                return
            return lifestyle_brand_tracker.brand_name
        if token_data.token_type == self.TOKEN_PICKED_PART:
            participant = resolver.get_participant(ParticipantType.Object)
            if participant is None:
                return
            context = getattr(resolver, 'context', None)
            if context is not None and context.pick is not None:
                target_objects = participant.get_closest_parts_to_position(
                    context.pick.location, has_name=True)
                if target_objects:
                    part = target_objects.pop()
                    return part.part_name
        if token_data.token_type == self.TOKEN_CIVIC_POLICY:
            if token_data.civic_policy_data == self.TOKEN_CIVIC_POLICY_VOTING_START_TIME:
                return services.street_service().voting_open_time
            if token_data.civic_policy_data == self.TOKEN_CIVIC_POLICY_VOTING_END_TIME:
                return services.street_service().voting_close_time
        if token_data.token_type == self.TOKEN_STREET:
            if token_data.street_data == self.TOKEN_STREET_POLICY_NAME_UP_FOR_REPEAL:
                provider = token_data.street.get_street_provider()
                if provider is not None:
                    policies = provider.get_up_for_repeal_policies()
                    if policies:
                        return list(policies)[0].display_name()
            if token_data.street_data == self.TOKEN_STREET_POLICY_NAME_BALLOTED_RANDOM_LOSING:
                provider = token_data.street.get_street_provider()
                if provider is None:
                    return
                policies = list(provider.get_balloted_policies())
                if policies and len(policies) > 1:
                    winning_policy = max(
                        policies,
                        key=lambda policy: provider.get_stat_value(
                            policy.vote_count_statistic))
                    policies.remove(winning_policy)
                    random_losing_policy = random.choice(policies)
                    return random_losing_policy.display_name()
            if token_data.street_data == self.TOKEN_STREET_POLICY_NAME_BALLOTED_WINNING:
                provider = token_data.street.get_street_provider()
                if provider is None:
                    return
                policies = list(provider.get_balloted_policies())
                if policies:
                    winning_policy = max(
                        policies,
                        key=lambda policy: provider.get_stat_value(
                            policy.vote_count_statistic))
                    return winning_policy.display_name()
        if token_data.token_type == self.TOKEN_VENUE:
            if token_data.venue_data == self.TOKEN_VENUE_ACTIVE_VENUE_NAME:
                raw_active_venue_tuning_id = build_buy.get_current_venue(
                    services.current_zone_id(), allow_ineligible=True)
                raw_active_venue_tuning = services.venue_manager().get(
                    raw_active_venue_tuning_id)
                if raw_active_venue_tuning is None:
                    return
                source_venue_tuning = type(
                    services.venue_service().source_venue)
                if source_venue_tuning is raw_active_venue_tuning and source_venue_tuning.variable_venues is not None:
                    return raw_active_venue_tuning.variable_venues.variable_venue_display_name(
                    )
                return raw_active_venue_tuning.display_name
            elif token_data.venue_data == self.TOKEN_VENUE_SOURCE_VENUE_NAME:
                source_venue_tuning = type(
                    services.venue_service().source_venue)
                if source_venue_tuning is not None:
                    return source_venue_tuning.display_name

    def get_tokens(self, resolver):
        return tuple(
            self._get_token(resolver, token_data)
            for token_data in self.tokens)
Beispiel #20
0
 def __init__(self, **kwargs):
     super().__init__(reservation_stat=TunableReference(services.statistic_manager(), description='The stat driving the available number of reservations.'), **kwargs)
Beispiel #21
0
class FestivalContestDramaNodeMixin:
    SIM_ID_SAVE_TOKEN = 'sim_ids'
    OBJECT_ID_SAVE_TOKEN = 'object_ids'
    SCORE_SAVE_TOKEN = 'scores'
    WINNERS_AWARDED_TOKEN = 'awarded'
    USER_SUBMITTED_TOKEN = 'submitted'
    INSTANCE_TUNABLES = {
        'festival_contest_tuning':
        OptionalTunable(
            description='\n            Optional contest tuning\n            ',
            tunable=TunableTuple(
                _score_update_frequency=TunableSimMinute(
                    description=
                    '\n                    How often a fake new score should be submitted to the tournament.\n                    ',
                    default=30,
                    minimum=0,
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _score_update_value_interval=TunableInterval(
                    description=
                    '\n                    When a fake new score is submitted, the interval determining what the value should be.\n                    ',
                    tunable_type=float,
                    default_lower=0,
                    default_upper=10,
                    minimum=0,
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _win_rewards=TunableList(
                    description=
                    '\n                    List of Loots applied to the winners of the contest. Index refers to the \n                    winner who receives that loot. 1st, 2nd, 3rd, etc.\n                    ',
                    tunable=TunableReference(
                        description=
                        '\n                        A reference to a loot that will be applied to one of the winners.\n                        ',
                        manager=(services.get_instance_manager(
                            sims4.resources.Types.ACTION)),
                        class_restrictions=('LootActions', )),
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _win_notifications=TunableList(
                    description=
                    '\n                    List of notifications applied to the winners of the contest. These display regardless of whether\n                    the rewards have already been given out. Index refers to the winners of that rank. 1st, 2nd, 3rd, etc.\n                    ',
                    tunable=(UiDialogNotification.TunableFactory()),
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _lose_notification=TunableUiDialogNotificationSnippet(
                    description=
                    '\n                    Notification displayed if there are no player sim winners of the contest. Only displayed if the \n                    winners are requested, not at the end of the festival.\n                    ',
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _contest_duration=TunableSimMinute(
                    description=
                    '\n                    The amount of time in sim minutes that we should allow scores to be\n                    submitted to the contest and that we should submit fake scores.\n                    ',
                    default=60,
                    minimum=0,
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _weight_statistic=TunableReference(
                    description=
                    "\n                    Statistic that describes the weight of the object for the contest. The \n                    value of the statistic is used as the sim's score in the contest\n                    ",
                    manager=(services.statistic_manager())),
                _allow_multiple_entries_per_sim=Tunable(
                    description=
                    '\n                    If checked, the same sim can have more than one object in\n                    the scores list. If false (default) only the highest scoring\n                    submission per sim is maintained.\n                    ',
                    tunable_type=bool,
                    default=False,
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _destroy_object_on_submit=Tunable(
                    description=
                    '\n                    If checked, the submitted object will be destroyed when it\n                    is submitted for the contest.\n                    ',
                    tunable_type=bool,
                    default=True,
                    tuning_group=(GroupNames.FESTIVAL_CONTEST)),
                _winner_selection_method=TunableVariant(
                    description=
                    '\n                    Which method to use for choosing a winner (or winners) of the contest.\n                    ',
                    ranked=(_FestivalContestWinnerSelectionMethod_Ranked.
                            TunableFactory()),
                    weighted_random=(
                        _FestivalContestWinnerSelectionMethod_WeightedRandom.
                        TunableFactory()),
                    default='ranked',
                    tuning_group=(GroupNames.FESTIVAL_CONTEST))))
    }

    def __init__(self, *args, **kwargs):
        (super().__init__)(*args, **kwargs)
        if self.festival_contest_tuning is None:
            return
        self._scores = []
        self._score_alarm = None
        self._has_awarded_winners = False

    def _try_and_start_festival(self):
        super()._try_and_start_festival()
        if self.festival_contest_tuning is None:
            return
        self._setup_score_add_alarm()

    def _setup_score_add_alarm(self):
        if self.festival_contest_tuning._score_update_frequency > 0:
            duration = create_time_span(
                minutes=(self.festival_contest_tuning._score_update_frequency))
            self._score_alarm = alarms.add_alarm(self, duration,
                                                 self._score_add_callback,
                                                 True)
        elif self.festival_contest_tuning._score_update_value_interval.upper_bound > 0:
            self._add_fake_score()

    def _score_add_callback(self, _):
        if self._get_remaining_contest_time().in_minutes() <= 0:
            if self._score_alarm is not None:
                alarms.cancel_alarm(self._score_alarm)
                self._score_alarm = None
            return
        self._add_fake_score()

    def _get_remaining_contest_time(self):
        now = services.time_service().sim_now
        time_since_started = now - self._selected_time
        duration = create_time_span(
            minutes=(self.festival_contest_tuning._contest_duration +
                     self.pre_festival_duration))
        time_left_to_go = duration - time_since_started
        return time_left_to_go

    def _add_fake_score(self):
        score = self.festival_contest_tuning._score_update_value_interval.random_float(
        )
        self.add_score(sim_id=0, object_id=0, score=score)

    def add_score(self, sim_id, object_id, score):
        scores = self._scores
        if sim_id == 0:
            scores.append(
                ContestScore(score, sim_id=sim_id, object_id=object_id))
        else:
            found_score = False
            for current_score_obj in scores:
                if current_score_obj.sim_id == sim_id:
                    if self.festival_contest_tuning._allow_multiple_entries_per_sim:
                        if current_score_obj.object_id != object_id:
                            continue
                        current_score = current_score_obj.score
                        if current_score >= score:
                            return
                        current_score_obj.score = score
                        found_score = True
                        break

            if not found_score:
                scores.append(
                    ContestScore(score, sim_id=sim_id, object_id=object_id))
            scores.sort(key=(lambda item: item.score), reverse=True)
            scores_to_consider = self.festival_contest_tuning._winner_selection_method.max_scores_to_consider(
            )
            if scores_to_consider is not None:
                if len(scores) > scores_to_consider:
                    scores = scores[:scores_to_consider]
                    self._scores = scores
                if not self.festival_contest_tuning._winner_selection_method.uses_ranking(
                ):
                    return
                for rank, obj in enumerate(scores):
                    if obj.sim_id == sim_id:
                        return rank
                else:
                    return

    def get_scores_gen(self):
        self._scores.sort(key=(lambda item: item.score), reverse=False)
        yield from self._scores

    def is_during_contest(self):
        if self.is_during_pre_festival():
            return False
        else:
            remaining_time = self._get_remaining_contest_time()
            return remaining_time.in_minutes() > 0

    def award_winners(self, show_fallback_dialog=False):
        valid_sim_won = False
        winners = self.festival_contest_tuning._winner_selection_method.get_winners(
            self)
        sim_info_manager = services.sim_info_manager()
        for contest_score, award in zip(
                winners, self.festival_contest_tuning._win_rewards):
            if not contest_score.sim_id is None:
                if contest_score.sim_id is 0:
                    pass
                elif not sim_info_manager.is_sim_id_valid(
                        contest_score.sim_id):
                    pass
                else:
                    valid_sim_won = True
                    sim = sim_info_manager.get(contest_score.sim_id)
                    resolver = SingleSimResolver(sim)
                    if not self._has_awarded_winners:
                        award.apply_to_resolver(resolver)
                    rank = winners.index(contest_score)
                    if rank >= len(
                            self.festival_contest_tuning._win_notifications):
                        pass
                    else:
                        notification = self.festival_contest_tuning._win_notifications[
                            rank]
                        dialog = notification(
                            sim,
                            target_sim_id=(contest_score.sim_id),
                            resolver=resolver)
                        dialog.show_dialog()

        if show_fallback_dialog:
            if not valid_sim_won:
                active_sim = services.get_active_sim()
                resolver = SingleSimResolver(active_sim)
                dialog = self.festival_contest_tuning._lose_notification(
                    active_sim,
                    target_sim_id=(active_sim.id
                                   if active_sim is not None else None),
                    resolver=resolver)
                dialog.show_dialog()
            self._has_awarded_winners = True

    def cleanup(self, from_service_stop=False):
        super().cleanup(from_service_stop=from_service_stop)
        if self.festival_contest_tuning is None:
            return
        if self._score_alarm is not None:
            alarms.cancel_alarm(self._score_alarm)
            self._score_alarm = None

    def complete(self, **kwargs):
        (super().complete)(**kwargs)
        if self.festival_contest_tuning is None:
            return
        if self.is_during_pre_festival():
            return
        if self.is_during_contest():
            return
        if self._has_awarded_winners:
            return
        self.award_winners(
            show_fallback_dialog=(self.has_user_submitted_entry()))

    def _save_custom_data(self, writer):
        super()._save_custom_data(writer)
        if self.festival_contest_tuning is None:
            return
        if self._scores and len(self._scores) == 0:
            return
        scores = []
        sim_ids = []
        object_ids = []
        for score in self.get_scores_gen():
            scores.append(score.score)
            sim_ids.append(score.sim_id)
            object_ids.append(score.object_id)

        writer.write_floats(self.SCORE_SAVE_TOKEN, scores)
        writer.write_uint64s(self.SIM_ID_SAVE_TOKEN, sim_ids)
        writer.write_uint64s(self.OBJECT_ID_SAVE_TOKEN, object_ids)
        writer.write_bool(self.WINNERS_AWARDED_TOKEN,
                          self._has_awarded_winners)

    def _load_custom_data(self, reader):
        super_success = super()._load_custom_data(reader)
        if self.festival_contest_tuning is None:
            return
        elif not super_success:
            return False
        else:
            self._scores = []
            scores = reader.read_floats(self.SCORE_SAVE_TOKEN, ())
            sim_ids = reader.read_uint64s(self.SIM_ID_SAVE_TOKEN, ())
            object_ids = reader.read_uint64s(self.OBJECT_ID_SAVE_TOKEN, None)
            if object_ids is None:
                object_ids = (0, ) * len(sim_ids)
            for score, sim_id, object_id in zip(scores, sim_ids, object_ids):
                self._scores.append(ContestScore(score, sim_id, object_id))

            self._has_awarded_winners = reader.read_bool(
                self.WINNERS_AWARDED_TOKEN, False)
            return True

    def has_user_submitted_entry(self):
        if self._scores:
            active_sim = services.get_active_sim()
            if active_sim is not None:
                sim_id = active_sim.sim_id
                for current_score_obj in self._scores:
                    if current_score_obj.sim_id == sim_id:
                        return True

            return False
Beispiel #22
0
class RelationshipTrack(TunedContinuousStatistic,
                        HasTunableReference,
                        metaclass=HashedTunedInstanceMetaclass,
                        manager=services.statistic_manager()):
    __qualname__ = 'RelationshipTrack'
    FRIENDSHIP_TRACK = TunableReference(
        description=
        '\n        A reference to the friendship track so that the client knows which\n        track is the friendship one.\n        ',
        manager=services.statistic_manager(),
        class_restrictions='RelationshipTrack',
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    FRIENDSHIP_TRACK_FILTER_THRESHOLD = Tunable(
        description=
        '\n        Value that the client will use when filtering friendship on the Sim\n        Picker.  Sims that have a track value equal to or above this value will\n        be shown with the friendship filter.\n        ',
        tunable_type=int,
        default=0,
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    ROMANCE_TRACK = TunableReference(
        description=
        '\n        A reference to the romance track so that the client knows which\n        track is the romance one.\n        ',
        manager=services.statistic_manager(),
        class_restrictions='RelationshipTrack',
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    ROMANCE_TRACK_FILTER_THRESHOLD = Tunable(
        description=
        '\n        Value that the client will use when filtering romance on the Sim\n        Picker.  Sims that have a track value equal to or above this value will\n        be shown with the romance filter.\n        ',
        tunable_type=int,
        default=0,
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    ROMANCE_TRACK_FILTER_BITS = TunableSet(
        description=
        '\n        A set of relationship bits that will be used in the Sim Picker for\n        filtering based on romance.  If a Sim has any of these bits then they\n        will be displayed in the Sim Picker when filtering for romance.\n        ',
        tunable=TunableReference(
            description=
            '\n                A specific bit used for filtering romance in the Sim Picker.\n                ',
            manager=services.get_instance_manager(
                sims4.resources.Types.RELATIONSHIP_BIT)),
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    REMOVE_INSTANCE_TUNABLES = ('stat_asm_param', 'persisted_tuning')
    INSTANCE_TUNABLES = {
        'bit_data_tuning':
        TunableVariant(bit_set=TunableRelationshipBitData(),
                       _2dMatrix=TunableRelationshipTrack2dLink()),
        'ad_data':
        TunableList(
            TunableVector2(sims4.math.Vector2(0, 0),
                           description='Point on a Curve'),
            description=
            'A list of Vector2 points that define the desire curve for this relationship track.'
        ),
        'relationship_obj_prefence_curve':
        TunableWeightedUtilityCurveAndWeight(
            description=
            "A curve that maps desire of a sim to interact with an object made by a sim of this relation to this relationship's actual score."
        ),
        '_add_bit_on_threshold':
        OptionalTunable(
            description=
            '\n                If enabled, the referenced bit will be added this track reaches the threshold.\n                ',
            tunable=TunableTuple(
                description=
                '\n                    The bit & threshold pair.\n                    ',
                bit=TunableReference(
                    description=
                    '\n                        The bit to add.\n                        ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.RELATIONSHIP_BIT)),
                threshold=TunableThreshold(
                    description=
                    '\n                        The threshold at which to add this bit.\n                        '
                ))),
        'display_priority':
        TunableRange(
            description=
            '\n                The display priority of this relationship track.  Tracks with a\n                display priority greater than zero will be displayed in\n                ascending order in the UI.  So a relationship track with a\n                display priority of 1 will show above a relationship track\n                with a display priority of 2.  Relationship tracks with the\n                same display priority will show up in potentially\n                non-deterministic ways.  Relationship tracks with display\n                priorities of 0 will not be shown.\n                ',
            tunable_type=int,
            default=0,
            minimum=0,
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        'display_popup_priority':
        TunableRange(
            description=
            '\n                The display popup priority.  This is the priority that the\n                relationship score increases will display. If there are\n                multiple relationship changes at the same time.\n                ',
            tunable_type=int,
            default=0,
            minimum=0,
            export_modes=sims4.tuning.tunable_base.ExportModes.All),
        '_neutral_bit':
        TunableReference(
            description=
            "\n                The neutral bit for this relationship track.  This is the bit\n                that is displayed when there are holes in the relationship\n                track's bit data.\n                ",
            manager=services.get_instance_manager(
                sims4.resources.Types.RELATIONSHIP_BIT)),
        'decay_only_affects_selectable_sims':
        Tunable(
            description=
            '\n                If this is True, the decay is only enabled if one or both of \n                the sims in the relationship are selectable. \n                ',
            tunable_type=bool,
            default=False),
        'delay_until_decay_is_applied':
        OptionalTunable(
            description=
            '\n                If enabled, the decay for this track will be disabled whenever\n                the value changes by any means other than decay.  It will then \n                be re-enabled after this amount of time (in sim minutes) passes.\n                ',
            tunable=TunableRange(
                description=
                '\n                    The amount of time, in sim minutes, that it takes before \n                    decay is enabled.\n                    ',
                tunable_type=int,
                default=10,
                minimum=1)),
        'causes_delayed_removal_on_convergence':
        Tunable(
            description=
            '\n                If True, this track may cause the relationship to get culled when \n                it reaches convergence.  This is not guaranteed, based on the \n                culling rules.  Sim relationships will NOT be culled if any of \n                the folling conditions are met:\n                - The sim has any relationship bits that are tuned to prevent this.\n                - The Sims are in the same household\n                ',
            tunable_type=bool,
            default=False),
        'visible_test_set':
        OptionalTunable(event_testing.tests.TunableTestSet(
            description=
            '\n                If set , tests whether relationship should be sent to client.\n                If no test given, then as soon as track is added to relationship\n                it will be visible to client.\n                '
        ),
                        disabled_value=DEFAULT,
                        disabled_name='always_visible',
                        enabled_name='run_test')
    }
    bit_data = None

    def __init__(self, tracker):
        super().__init__(tracker, self.initial_value)
        self._per_instance_data = self.bit_data.get_track_instance_data(self)
        self.visible_to_client = True if self.visible_test_set is DEFAULT else False
        self._decay_alarm_handle = None
        self._convergence_callback_data = None
        self._first_same_sex_relationship_callback_data = None
        self._set_initial_decay()

    @classproperty
    def is_short_term_context(cls):
        return False

    def on_add(self):
        if not self.tracker.suppress_callback_setup_during_load:
            self._per_instance_data.setup_callbacks()
            self.update_instance_data()
        if self._add_bit_on_threshold is not None:
            self.add_callback(self._add_bit_on_threshold.threshold,
                              self._on_add_bit_from_threshold_callback)
        if self._should_initialize_first_same_sex_relationship_callback():
            self._first_same_sex_relationship_callback_data = self.add_callback(
                Threshold(
                    sims.global_gender_preference_tuning.
                    GlobalGenderPreferenceTuning.
                    ENABLE_AUTOGENERATION_SAME_SEX_PREFERENCE_THRESHOLD,
                    operator.ge), self._first_same_sex_relationship_callback)

    def on_remove(self, on_destroy=False):
        self.remove_callback(self._first_same_sex_relationship_callback_data)
        super().on_remove(on_destroy=on_destroy)
        self._destroy_decay_alarm()

    def get_statistic_multiplier_increase(self):
        sim_info_manager = services.sim_info_manager()
        target_sim_info = sim_info_manager.get(
            self.tracker.relationship.target_sim_id)
        target_sim_multiplier = 1
        if target_sim_info is not None:
            target_sim_stat = target_sim_info.relationship_tracker.get_relationship_track(
                self.tracker.relationship.sim_id, track=self.stat_type)
            if target_sim_stat is not None:
                target_sim_multiplier = target_sim_stat._statistic_multiplier_increase
        return self._statistic_multiplier_increase * target_sim_multiplier

    def get_statistic_multiplier_decrease(self):
        sim_info_manager = services.sim_info_manager()
        target_sim_info = sim_info_manager.get(
            self.tracker.relationship.target_sim_id)
        target_sim_multiplier = 1
        if target_sim_info is not None:
            target_sim_stat = target_sim_info.relationship_tracker.get_relationship_track(
                self.tracker.relationship.sim_id, track=self.stat_type)
            if target_sim_stat is not None:
                target_sim_multiplier = target_sim_stat._statistic_multiplier_decrease
        return self._statistic_multiplier_decrease * target_sim_multiplier

    def set_value(self, value, *args, **kwargs):
        self._update_value()
        old_value = self._value
        delta = value - old_value
        sim_info = self.tracker.relationship.find_sim_info()
        self.tracker.trigger_test_event(
            sim_info,
            event_testing.test_events.TestEvent.PrerelationshipChanged)
        super().set_value(value, *args, **kwargs)
        self._update_visiblity()
        self._reset_decay_alarm()
        self.tracker.relationship.send_relationship_info(deltas={self: delta})
        self.tracker.trigger_test_event(
            sim_info, event_testing.test_events.TestEvent.RelationshipChanged)

    @property
    def is_visible(self):
        return self.visible_to_client

    def fixup_callbacks_during_load(self):
        super().fixup_callbacks_during_load()
        self._per_instance_data.setup_callbacks()

    def update_instance_data(self):
        self._per_instance_data.request_full_update()

    def apply_social_group_decay(self):
        pass

    def remove_social_group_decay(self):
        pass

    def _on_statistic_modifier_changed(self, notify_watcher=True):
        super()._on_statistic_modifier_changed(notify_watcher=notify_watcher)
        if self._statistic_modifier == 0:
            self._reset_decay_alarm()
        self.tracker.relationship.send_relationship_info()

    def _update_visiblity(self):
        if not self.visible_to_client:
            sim_info_manager = services.sim_info_manager()
            actor_sim_info = sim_info_manager.get(
                self.tracker.relationship.sim_id)
            if actor_sim_info is None:
                return
            target_sim_info = sim_info_manager.get(
                self.tracker.relationship.target_sim_id)
            if target_sim_info is None:
                return
            resolver = DoubleSimResolver(actor_sim_info, target_sim_info)
            self.visible_to_client = True if self.visible_test_set.run_tests(
                resolver) else False

    @classmethod
    def _tuning_loaded_callback(cls):
        super()._tuning_loaded_callback()
        cls.bit_data = cls.bit_data_tuning()
        cls.bit_data.build_track_data()
        cls._build_utility_curve_from_tuning_data(cls.ad_data)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._neutral_bit is None:
            logger.error('No Neutral Bit tuned for Relationship Track: {}',
                         cls)

    @staticmethod
    def check_relationship_track_display_priorities(statistic_manager):
        if not __debug__:
            return
        relationship_track_display_priority = collections.defaultdict(list)
        for statistic in statistic_manager.types.values():
            if not issubclass(statistic, RelationshipTrack):
                pass
            if statistic.display_priority == 0:
                pass
            relationship_track_display_priority[
                statistic.display_priority].append(statistic)
        for relationship_priority_level in relationship_track_display_priority.values(
        ):
            if len(relationship_priority_level) <= 1:
                pass
            logger.warn(
                'Multiple Relationship Tracks have the same display priority: {}',
                relationship_priority_level)

    @classmethod
    def type_id(cls):
        return cls.guid64

    @classmethod
    def get_bit_track_node_for_bit(cls, relationship_bit):
        for node in cls.bit_data.bit_track_node_gen():
            while node.bit is relationship_bit:
                return node

    @classmethod
    def bit_track_node_gen(cls):
        for node in cls.bit_data.bit_track_node_gen():
            yield node

    @classmethod
    def get_bit_at_relationship_value(cls, value):
        for bit_node in reversed(tuple(cls.bit_track_node_gen())):
            while bit_node.min_rel <= value <= bit_node.max_rel:
                return bit_node.bit or cls._neutral_bit
        return cls._neutral_bit

    @classproperty
    def persisted(cls):
        return True

    def get_active_bit(self):
        return self._per_instance_data.get_active_bit()

    def get_bit_for_client(self):
        active_bit = self.get_active_bit()
        if active_bit is None:
            return self._neutral_bit
        return active_bit

    def _set_initial_decay(self):
        if self._should_decay():
            self.decay_enabled = True
        self._convergence_callback_data = self.add_callback(
            Threshold(self.convergence_value, operator.eq),
            self._on_convergence_callback)
        logger.debug('Setting decay on track {} to {} for {}', self,
                     self.decay_enabled, self.tracker.relationship)

    def _should_decay(self):
        if self.decay_rate == 0:
            return False
        if self.decay_only_affects_selectable_sims:
            sim_info = self.tracker.relationship.find_sim_info()
            target_sim_info = self.tracker.relationship.find_target_sim_info()
            if sim_info is None or target_sim_info is None:
                return False
            if sim_info.is_selectable or target_sim_info.is_selectable:
                return True
        else:
            return True
        return False

    def _reset_decay_alarm(self):
        self._destroy_decay_alarm()
        if self._should_decay(
        ) and self.delay_until_decay_is_applied is not None:
            logger.debug('Resetting decay alarm for track {} for {}.', self,
                         self.tracker.relationship)
            delay_time_span = date_and_time.create_time_span(
                minutes=self.delay_until_decay_is_applied)
            self._decay_alarm_handle = alarms.add_alarm(
                self, delay_time_span, self._decay_alarm_callback)
            self.decay_enabled = False

    def _decay_alarm_callback(self, handle):
        logger.debug('Decay alarm triggered on track {} for {}.', self,
                     self.tracker.relationship)
        self._destroy_decay_alarm()
        self.decay_enabled = True

    def _destroy_decay_alarm(self):
        if self._decay_alarm_handle is not None:
            alarms.cancel_alarm(self._decay_alarm_handle)
            self._decay_alarm_handle = None

    def _on_convergence_callback(self, _):
        logger.debug(
            'Track {} reached convergence; rel might get culled for {}', self,
            self.tracker.relationship)
        self.tracker.relationship.track_reached_convergence(self)

    def _on_add_bit_from_threshold_callback(self, _):
        logger.debug('Track {} is adding its extra bit: {}'.format(
            self, self._add_bit_on_threshold.bit))
        self.tracker.relationship.add_bit(self._add_bit_on_threshold.bit)

    def _should_initialize_first_same_sex_relationship_callback(self):
        if self.stat_type is not self.ROMANCE_TRACK:
            return False
        if sims.global_gender_preference_tuning.GlobalGenderPreferenceTuning.enable_autogeneration_same_sex_preference:
            return False
        sim_info_a = self.tracker.relationship.find_sim_info()
        sim_info_b = self.tracker.relationship.find_target_sim_info()
        if sim_info_a is None or sim_info_b is None:
            return False
        if sim_info_a.gender is not sim_info_b.gender:
            return False
        if sim_info_a.is_npc and sim_info_b.is_npc:
            return False
        return True

    def _first_same_sex_relationship_callback(self, _):
        sims.global_gender_preference_tuning.GlobalGenderPreferenceTuning.enable_autogeneration_same_sex_preference = True
        self.remove_callback(self._first_same_sex_relationship_callback_data)
class RelationshipBit(HasTunableReference,
                      metaclass=HashedTunedInstanceMetaclass,
                      manager=services.relationship_bit_manager()):
    __qualname__ = 'RelationshipBit'
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedString(
            description=
            '\n            Localized name of this bit\n            ',
            export_modes=ExportModes.All),
        'bit_description':
        TunableLocalizedString(
            description=
            '\n            Localized description of this bit\n            ',
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the relationship bit.\n            ',
            default='PNG:missing_image',
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'bit_added_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is added.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'bit_removed_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is removed.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'depth':
        Tunable(
            description=
            '\n            The amount of depth provided by the bit.\n            ',
            tunable_type=int,
            default=0),
        'priority':
        Tunable(
            description=
            '\n            Priority of the bit.  This is used when a bit turns on while a\n            mutually exclusive bit is already on.\n            ',
            tunable_type=float,
            default=0),
        'display_priority':
        Tunable(
            description=
            '\n            The priority of this bit with regards to UI.  Only the highest\n            priority bits are displayed.\n            ',
            tunable_type=int,
            default=0,
            export_modes=ExportModes.All),
        'visible':
        Tunable(
            description=
            "\n            If True, this bit has the potential to be visible when applied,\n            depending on display_priority and the other active bits.  If False,\n            the bit will not be displayed unless it's part of the\n            REL_INSPECTOR_TRACK bit track.\n            ",
            tunable_type=bool,
            default=True),
        'group_id':
        TunableEnumEntry(
            description=
            '\n            The group this bit belongs to.  Two bits of the same group cannot\n            belong in the same set of bits for a given relationship.\n            ',
            tunable_type=RelationshipBitType,
            default=RelationshipBitType.NoGroup),
        'triggered_track':
        TunableReference(
            description=
            '\n            If set, the track that is triggered when this bit is set\n            ',
            manager=services.statistic_manager(),
            class_restrictions='RelationshipTrack'),
        'required_bits':
        TunableList(
            description=
            '\n            List of all bits that are required to be on in order to allow this\n            bit to turn on.\n            ',
            tunable=TunableReference(services.relationship_bit_manager())),
        'timeout':
        TunableSimMinute(
            description=
            '\n            The length of time this bit will last in sim minutes.  0 means the\n            bit will never timeout.\n            ',
            default=0),
        'remove_on_threshold':
        OptionalTunable(tunable=TunableTuple(
            description=
            '\n                If enabled, this bit will be removed when the referenced track\n                reaches the appropriate threshold.\n                ',
            track=TunableReference(
                description=
                '\n                    The track to be tested.\n                    ',
                manager=services.statistic_manager(),
                class_restrictions='RelationshipTrack'),
            threshold=TunableThreshold(
                description=
                '\n                    The threshold at which to remove this bit.\n                    '
            ))),
        'historical_bits':
        OptionalTunable(tunable=TunableList(tunable=TunableTuple(
            age_trans_from=
            TunableEnumEntry(description=
                             '\n                        Age we are transitioning out of.\n                        ',
                             tunable_type=sims.sim_info_types.Age,
                             default=sims.sim_info_types.Age.CHILD),
            new_historical_bit=TunableReference(
                description=
                '\n                        New historical bit the sim obtains\n                        ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT))))),
        'collection_ids':
        TunableList(tunable=TunableEnumEntry(
            description=
            '\n                The bit collection id this bit belongs to, like family,\n                friends, romance. Default to be All.\n                ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.All,
            export_modes=ExportModes.All)),
        'buffs_on_add_bit':
        TunableList(tunable=TunableTuple(
            buff_ref=buffs.tunable.
            TunableBuffReference(description=
                                 '\n                    Buff that gets added to sim when bit is added.\n                    '
                                 ),
            amount=Tunable(
                description=
                '\n                    If buff is tied to commodity the amount to add to the\n                    commodity.\n                    ',
                tunable_type=float,
                default=1),
            only_add_once=Tunable(
                description=
                '\n                    If True, the buff should only get added once no matter how\n                    many times this bit is being applied.\n                    ',
                tunable_type=bool,
                default=False))),
        'buffs_to_add_if_on_active_lot':
        TunableList(
            description=
            "\n            List of buffs to add when a sim that I share this relationship with\n            is in the household that owns the lot that I'm on.\n            ",
            tunable=buffs.tunable.TunableBuffReference(
                description=
                '\n                Buff that gets added to sim when bit is added.\n                '
            )),
        'permission_requirements':
        TunableList(
            tunable=TunableTuple(
                permission=TunableEnumEntry(
                    description=
                    '\n                    The Sim Permission to test to allow setting of the\n                    relationship bit.\n                    ',
                    tunable_type=SimPermissions.Settings,
                    default=SimPermissions.Settings.VisitationAllowed),
                required_enabled=
                Tunable(
                    description=
                    '\n                    If True, the chosen Sim Permission must be enabled for\n                    relationship bit to be set.  If False, the permission must\n                    be disabled.\n                    ',
                    tunable_type=bool,
                    default=True))),
        'autonomy_multiplier':
        Tunable(
            description=
            '\n            This value is multiplied to the autonomy score of any interaction\n            performed between the two Sims.  For example, when the Sim decides\n            to socialize, she will start looking at targets to socialize with.\n            If there is a Sim who she shares this bit with, her final score for\n            socializing with that Sim will be multiplied by this value.\n            ',
            tunable_type=float,
            default=1),
        'prevents_relationship_culling':
        Tunable(
            description=
            '\n            If checked, any relationship with this bit applied will never be\n            culled.\n            ',
            tunable_type=bool,
            default=False),
        'persisted_tuning':
        Tunable(
            description=
            '\n            Whether this bit will persist when saving a Sim. \n            \n            For example, a Sims is good_friends should be set to true, but\n            romantic_gettingMarried should not be saved.\n            ',
            tunable_type=bool,
            default=True)
    }
    is_track_bit = False
    is_rel_bit = True
    track_min_score = 0
    track_max_score = 0
    track_mean_score = 0
    trait_replacement_bits = None

    @sims4.utils.classproperty
    def persisted(cls):
        return cls.persisted_tuning

    def __init__(self):
        self._buff_handles = []
        self._conditional_removal_listener = None
        self._appropriate_buffs_handles = []

    def on_add_to_relationship(self, sim, target_sim_info, relationship):
        for buff_data in self.buffs_on_add_bit:
            buff_type = buff_data.buff_ref.buff_type
            if buff_data.only_add_once:
                if buff_type.guid64 in relationship.bit_added_buffs[
                        buff_type.guid64]:
                    pass
                else:
                    relationship.bit_added_buffs[buff_type.guid64].append(
                        buff_type.guid64)
            if buff_type.commodity:
                tracker = sim.get_tracker(buff_type.commodity)
                tracker.add_value(buff_type.commodity, buff_data.amount)
                sim.set_buff_reason(buff_type, buff_data.buff_ref.buff_reason)
            else:
                buff_handle = sim.add_buff(
                    buff_type, buff_reason=buff_data.buff_ref.buff_reason)
                self._buff_handles.append(buff_handle)
        if self.bit_added_notification is not None and sim.is_selectable:
            target_sim = target_sim_info.get_sim_instance()
            if not target_sim or not target_sim.is_selectable:
                self._show_bit_added_dialog(sim, target_sim_info)
            elif not target_sim_info.relationship_tracker.has_bit(
                    sim.id, type(self)):
                self._show_bit_added_dialog(sim, target_sim_info)

    def on_remove_from_relationship(self, sim, target_sim_info):
        for buff_handle in self._buff_handles:
            sim.remove_buff(buff_handle)
        if self.bit_removed_notification is not None and sim.is_selectable:
            target_sim = target_sim_info.get_sim_instance()
            if not target_sim or not target_sim.is_selectable:
                self._show_bit_removed_dialog(sim, target_sim_info)
            elif not target_sim_info.relationship_tracker.has_bit(
                    sim.id, type(self)):
                self._show_bit_removed_dialog(sim, target_sim_info)

    def add_appropriateness_buffs(self, sim_info):
        if not self._appropriate_buffs_handles:
            for buff in self.buffs_to_add_if_on_active_lot:
                handle = sim_info.add_buff(buff.buff_type,
                                           buff_reason=buff.buff_reason)
                self._appropriate_buffs_handles.append(handle)

    def remove_appropriateness_buffs(self, sim_info):
        for buff in self._appropriate_buffs_handles:
            sim_info.remove_buff(buff)
        self._appropriate_buffs_handles.clear()

    def add_conditional_removal_listener(self, listener):
        if self._conditional_removal_listener is not None:
            logger.error(
                'Attempting to add a conditional removal listener when one already exists; old one will be overwritten.',
                owner='rez')
        self._conditional_removal_listener = listener

    def remove_conditional_removal_listener(self):
        listener = self._conditional_removal_listener
        self._conditional_removal_listener = None
        return listener

    def __repr__(self):
        return '<({}) Type: {}.{}>'.format(self.__name__,
                                           self.__mro__[1].__module__,
                                           self.__mro__[1].__name__)

    @classmethod
    def _cls_repr(cls):
        return '<({}) Type: {}.{}>'.format(cls.__name__,
                                           cls.__mro__[1].__module__,
                                           cls.__mro__[1].__name__)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.historical_bits is not None:
            for bit in cls.historical_bits:
                pass
        if cls.remove_on_threshold and cls.remove_on_threshold.track is None:
            logger.error(
                'Tuning Error: Remove On Threshold was tuned without a corresponding relationship track.'
            )

    def _show_bit_added_dialog(self, sim, target_sim_info):
        dialog = self.bit_added_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    def _show_bit_removed_dialog(self, sim, target_sim_info):
        dialog = self.bit_removed_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    @classmethod
    def matches_bit(cls, bit_type):
        return cls is bit_type
Beispiel #24
0
 def __init__(self, **kwargs):
     super().__init__(stat_to_check=TunableReference(services.statistic_manager()), threshold=TunableThreshold(description='Stat should be greater than this value for object creation to score.'), multiplier=Tunable(float, 1, description='Multiplier to be applied to score if object is created with this quality'))
Beispiel #25
0
class Skill(HasTunableReference, ProgressiveStatisticCallbackMixin, statistics.continuous_statistic_tuning.TunedContinuousStatistic, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)):
    SKILL_LEVEL_LIST = TunableMapping(description='\n        A mapping defining the level boundaries for each skill type.\n        ', key_type=SkillLevelType, value_type=TunableList(description='\n            The level boundaries for skill type, specified as a delta from the\n            previous value.\n            ', tunable=Tunable(tunable_type=int, default=0)), tuple_name='SkillLevelListMappingTuple', export_modes=ExportModes.All)
    SKILL_EFFECTIVENESS_GAIN = TunableMapping(description='\n        Skill gain points based on skill effectiveness.\n        ', key_type=SkillEffectiveness, value_type=TunableCurve())
    DYNAMIC_SKILL_INTERVAL = TunableRange(description='\n        Interval used when dynamic loot is used in a\n        PeriodicStatisticChangeElement.\n        ', tunable_type=float, default=1, minimum=1)
    INSTANCE_TUNABLES = {'stat_name': TunableLocalizedString(description='\n            The name of this skill.\n            ', export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'skill_description': TunableLocalizedString(description="\n            The skill's normal description.\n            ", export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'locked_description': TunableLocalizedString(description="\n            The skill description when it's locked.\n            ", allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'icon': TunableIcon(description='\n            Icon to be displayed for the Skill.\n            ', export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'tooltip_icon_list': TunableList(description='\n            A list of icons to show in the tooltip of this\n            skill.\n            ', tunable=TunableIcon(description='\n                Icon that is displayed what types of objects help\n                improve this skill.\n                '), export_modes=(ExportModes.ClientBinary,), tuning_group=GroupNames.UI), 'tutorial': TunableReference(description='\n            Tutorial instance for this skill. This will be used to bring up the\n            skill lesson from the first notification for Sim to know this skill.\n            ', manager=services.get_instance_manager(sims4.resources.Types.TUTORIAL), allow_none=True, class_restrictions=('Tutorial',), tuning_group=GroupNames.UI), 'priority': Tunable(description="\n            Skill priority.  Higher priority skills will trump other skills when\n            being displayed on the UI. When a Sim gains multiple skills at the\n            same time, only the highest priority one will display a progress bar\n            over the Sim's head.\n            ", tunable_type=int, default=1, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'next_level_teaser': TunableList(description='\n            Tooltip which describes what the next level entails.\n            ', tunable=TunableLocalizedString(), export_modes=(ExportModes.ClientBinary,), tuning_group=GroupNames.UI), 'mood_id': TunableReference(description='\n            When this mood is set and active sim matches mood, the UI will\n            display a special effect on the skill bar to represent that this\n            skill is getting a bonus because of the mood.\n            ', manager=services.mood_manager(), allow_none=True, export_modes=ExportModes.All, tuning_group=GroupNames.UI), 'stat_asm_param': TunableStatAsmParam.TunableFactory(tuning_group=GroupNames.ANIMATION), 'hidden': Tunable(description='\n            If checked, this skill will be hidden.\n            ', tunable_type=bool, default=False, export_modes=ExportModes.All, tuning_group=GroupNames.AVAILABILITY), 'update_client_for_npcs': Tunable(description="\n            Whether this skill will send update messages to the client\n            for non-active household sims (NPCs).\n            \n            e.g. A toddler's communication skill determines the VOX they use, so\n            the client needs to know the skill level for all toddlers in order\n            for this work properly.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'is_default': Tunable(description='\n            Whether Sim will default has this skill.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.AVAILABILITY), 'ages': TunableSet(description='\n            Allowed ages for this skill.\n            ', tunable=TunableEnumEntry(tunable_type=Age, default=Age.ADULT, export_modes=ExportModes.All), tuning_group=GroupNames.AVAILABILITY), 'ad_data': TunableList(description='\n            A list of Vector2 points that define the desire curve for this\n            commodity.\n            ', tunable=TunableVector2(description='\n                Point on a Curve\n                ', default=sims4.math.Vector2(0, 0)), tuning_group=GroupNames.AUTONOMY), 'weight': Tunable(description="\n            The weight of the Skill with regards to autonomy.  It's ignored for\n            the purposes of sorting stats, but it's applied when scoring the\n            actual statistic operation for the SI.\n            ", tunable_type=float, default=0.5, tuning_group=GroupNames.AUTONOMY), 'statistic_multipliers': TunableMapping(description='\n            Multipliers this skill applies to other statistics based on its\n            value.\n            ', key_type=TunableReference(description='\n                The statistic this multiplier will be applied to.\n                ', manager=services.statistic_manager(), reload_dependent=True), value_type=TunableTuple(curve=TunableCurve(description='\n                    Tunable curve where the X-axis defines the skill level, and\n                    the Y-axis defines the associated multiplier.\n                    ', x_axis_name='Skill Level', y_axis_name='Multiplier'), direction=TunableEnumEntry(description="\n                    Direction where the multiplier should work on the\n                    statistic.  For example, a tuned decrease for an object's\n                    brokenness rate will not also increase the time it takes to\n                    repair it.\n                    ", tunable_type=StatisticChangeDirection, default=StatisticChangeDirection.INCREASE), use_effective_skill=Tunable(description='\n                    If checked, this modifier will look at the current\n                    effective skill value.  If unchecked, this modifier will\n                    look at the actual skill value.\n                    ', tunable_type=bool, needs_tuning=True, default=True)), tuning_group=GroupNames.MULTIPLIERS), 'success_chance_multipliers': TunableList(description='\n            Multipliers this skill applies to the success chance of\n            affordances.\n            ', tunable=TunableSkillMultiplier(), tuning_group=GroupNames.MULTIPLIERS), 'monetary_payout_multipliers': TunableList(description='\n            Multipliers this skill applies to the monetary payout amount of\n            affordances.\n            ', tunable=TunableSkillMultiplier(), tuning_group=GroupNames.MULTIPLIERS), 'tags': TunableList(description='\n            The associated categories of the skill\n            ', tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID, pack_safe=True), tuning_group=GroupNames.CORE), 'skill_level_type': TunableEnumEntry(description='\n            Skill level list to use.\n            ', tunable_type=SkillLevelType, default=SkillLevelType.MAJOR, export_modes=ExportModes.All, tuning_group=GroupNames.CORE), 'level_data': TunableMapping(description='\n            Level-specific information, such as notifications to be displayed to\n            level up.\n            ', key_type=int, value_type=TunableTuple(level_up_notification=UiDialogNotification.TunableFactory(description='\n                    The notification to display when the Sim obtains this level.\n                    The text will be provided two tokens: the Sim owning the\n                    skill and a number representing the 1-based skill level\n                    ', locked_args={'text_tokens': DEFAULT, 'icon': None, 'primary_icon_response': UiDialogResponse(text=None, ui_request=UiDialogResponse.UiDialogUiRequest.SHOW_SKILL_PANEL), 'secondary_icon': None}), level_up_screen_slam=OptionalTunable(description='\n                    Screen slam to show when reaches this skill level.\n                    Localization Tokens: Sim - {0.SimFirstName}, Skill Name - \n                    {1.String}, Skill Number - {2.Number}\n                    ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), skill_level_buff=OptionalTunable(tunable=TunableReference(description='\n                        The buff to place on a Sim when they reach this specific\n                        level of skill.\n                        ', manager=services.buff_manager())), rewards=TunableList(description='\n                    A reward to give for achieving this level.\n                    ', tunable=rewards.reward_tuning.TunableSpecificReward(pack_safe=True)), loot=TunableList(description='\n                    A loot to apply for achieving this level.\n                    ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.ACTION), class_restrictions=('LootActions',))), super_affordances=TunableSet(description='\n                    Super affordances this adds to the Sim.\n                    ', tunable=TunableReference(description='\n                        A super affordance added to this Sim.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), class_restrictions=('SuperInteraction',), pack_safe=True)), target_super_affordances=TunableProvidedAffordances(description='\n                    Super affordances this adds to the target.\n                    ', locked_args={'target': ParticipantType.Object, 'carry_target': ParticipantType.Invalid, 'is_linked': False, 'unlink_if_running': False}), actor_mixers=TunableMapping(description='\n                    Mixers this adds to an associated actor object. (When targeting\n                    something else.)\n                    ', key_type=TunableReference(description='\n                        The super affordance these mixers are associated with.\n                        ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), class_restrictions=('SuperInteraction',), pack_safe=True), value_type=TunableSet(description='\n                        Set of mixer affordances associated with the super affordance.\n                        ', tunable=TunableReference(description='\n                            Linked mixer affordance.\n                            ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), category='asm', class_restrictions=('MixerInteraction',), pack_safe=True)))), tuning_group=GroupNames.CORE), 'age_up_skill_transition_data': OptionalTunable(description='\n            Data used to modify the value of a new skill based on the level\n            of this skill.\n            \n            e.g. Toddler Communication skill transfers into Child Social skill.\n            ', tunable=TunableTuple(new_skill=TunablePackSafeReference(description='\n                    The new skill.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC)), skill_data=TunableMapping(description="\n                    A mapping between this skill's levels and the\n                    new skill's internal value.\n                    \n                    The keys are user facing skill levels.\n                    \n                    The values are the internal statistic value, not the user\n                    facing skill level.\n                    ", key_type=Tunable(description="\n                        This skill's level.\n                        \n                        This is the actual user facing skill level.\n                        ", tunable_type=int, default=0), value_type=Tunable(description='\n                        The new skill\'s value.\n                        \n                        This is the internal statistic\n                        value, not the user facing skill level."\n                        ', tunable_type=int, default=0))), tuning_group=GroupNames.SPECIAL_CASES), 'skill_unlocks_on_max': TunableList(description='\n            A list of skills that become unlocked when this skill is maxed.\n            ', tunable=TunableReference(description='\n                A skill to unlock.\n                ', manager=services.get_instance_manager(sims4.resources.Types.STATISTIC), class_restrictions=('Skill',), pack_safe=True), tuning_group=GroupNames.SPECIAL_CASES), 'trend_tag': OptionalTunable(description='\n            If enabled, we associate this skill with a particular trend via tag\n            which you can find in trend_tuning.\n            ', tunable=TunableTag(description='\n                The trend tag we associate with this skill\n                ', filter_prefixes=('func_trend',)))}
    REMOVE_INSTANCE_TUNABLES = ('min_value_tuning', 'max_value_tuning', 'decay_rate', '_default_convergence_value')

    def __init__(self, tracker):
        self._skill_level_buff = None
        super().__init__(tracker, self.initial_value)
        self._delta_enabled = True
        self._max_level_update_sent = False

    @classmethod
    def _tuning_loaded_callback(cls):
        super()._tuning_loaded_callback()
        level_list = cls.get_level_list()
        cls.max_level = len(level_list)
        cls.min_value_tuning = 0
        cls.max_value_tuning = sum(level_list)
        cls._default_convergence_value = cls.min_value_tuning
        cls._build_utility_curve_from_tuning_data(cls.ad_data)
        for stat in cls.statistic_multipliers:
            multiplier = cls.statistic_multipliers[stat]
            curve = multiplier.curve
            direction = multiplier.direction
            use_effective_skill = multiplier.use_effective_skill
            stat.add_skill_based_statistic_multiplier(cls, curve, direction, use_effective_skill)
        for multiplier in cls.success_chance_multipliers:
            curve = multiplier.curve
            use_effective_skill = multiplier.use_effective_skill
            for affordance in multiplier.affordance_list:
                affordance.add_skill_multiplier(affordance.success_chance_multipliers, cls, curve, use_effective_skill)
        for multiplier in cls.monetary_payout_multipliers:
            curve = multiplier.curve
            use_effective_skill = multiplier.use_effective_skill
            for affordance in multiplier.affordance_list:
                affordance.add_skill_multiplier(affordance.monetary_payout_multipliers, cls, curve, use_effective_skill)

    @classmethod
    def _verify_tuning_callback(cls):
        success_multiplier_affordances = []
        for multiplier in cls.success_chance_multipliers:
            success_multiplier_affordances.extend(multiplier.affordance_list)
        if len(success_multiplier_affordances) != len(set(success_multiplier_affordances)):
            logger.error("The same affordance has been tuned more than once under {}'s success multipliers, and they will overwrite each other. Please fix in tuning.", cls, owner='tastle')
        monetary_payout_multiplier_affordances = []
        for multiplier in cls.monetary_payout_multipliers:
            monetary_payout_multiplier_affordances.extend(multiplier.affordance_list)
        if len(monetary_payout_multiplier_affordances) != len(set(monetary_payout_multiplier_affordances)):
            logger.error("The same affordance has been tuned more than once under {}'s monetary payout multipliers, and they will overwrite each other. Please fix in tuning.", cls, owner='tastle')

    @classproperty
    def skill_type(cls):
        return cls

    @constproperty
    def is_skill():
        return True

    @classproperty
    def autonomy_weight(cls):
        return cls.weight

    @constproperty
    def remove_on_convergence():
        return False

    @classproperty
    def valid_for_stat_testing(cls):
        return True

    @classmethod
    def can_add(cls, owner, force_add=False, **kwargs):
        if force_add:
            return True
        if owner.age not in cls.ages:
            return False
        return super().can_add(owner, **kwargs)

    @classmethod
    def convert_to_user_value(cls, value):
        level_list = cls.get_level_list()
        if not level_list:
            return 0
        current_value = value
        for (level, level_threshold) in enumerate(level_list):
            current_value -= level_threshold
            if current_value < 0:
                return level
        return level + 1

    @classmethod
    def convert_from_user_value(cls, user_value):
        (level_min, _) = cls._get_level_bounds(user_value)
        return level_min

    @classmethod
    def create_skill_update_msg(cls, sim_id, stat_value):
        skill_msg = Commodities_pb2.Skill_Update()
        skill_msg.skill_id = cls.guid64
        skill_msg.curr_points = int(stat_value)
        skill_msg.sim_id = sim_id
        return skill_msg

    @classmethod
    def get_level_list(cls):
        return cls.SKILL_LEVEL_LIST.get(cls.skill_level_type)

    @classmethod
    def get_skill_effectiveness_points_gain(cls, effectiveness_level, level):
        skill_gain_curve = cls.SKILL_EFFECTIVENESS_GAIN.get(effectiveness_level)
        if skill_gain_curve is not None:
            return skill_gain_curve.get(level)
        logger.error('{} does not exist in SKILL_EFFECTIVENESS_GAIN mapping', effectiveness_level)
        return 0

    def _get_level_data_for_skill_level(self, skill_level):
        level_data = self.level_data.get(skill_level)
        if level_data is None:
            logger.debug('No level data found for skill [{}] at level [{}].', self, skill_level)
        return level_data

    @property
    def is_initial_value(self):
        return self.initial_value == self.get_value()

    def should_send_update(self, sim_info, stat_value):
        if sim_info.is_npc and not self.update_client_for_npcs:
            return False
        if self.hidden:
            return False
        if Skill.convert_to_user_value(stat_value) == 0:
            return False
        if self.reached_max_level:
            if self._max_level_update_sent:
                return False
            self._max_level_update_sent = True
        return True

    def on_initial_startup(self):
        super().on_initial_startup()
        skill_level = self.get_user_value()
        self._update_skill_level_buff(skill_level)

    def on_add(self):
        super().on_add()
        self._tracker.owner.add_modifiers_for_skill(self)
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is not None:
            provided_affordances = []
            for provided_affordance in level_data.target_super_affordances:
                provided_affordance_data = ProvidedAffordanceData(provided_affordance.affordance, provided_affordance.object_filter, provided_affordance.allow_self)
                provided_affordances.append(provided_affordance_data)
            self._tracker.add_to_affordance_caches(level_data.super_affordances, provided_affordances)
            self._tracker.add_to_actor_mixer_cache(level_data.actor_mixers)
            sim = self._tracker._owner.get_sim_instance()
            apply_super_affordance_commodity_flags(sim, self, level_data.super_affordances)

    def on_remove(self, on_destroy=False):
        super().on_remove(on_destroy=on_destroy)
        self._destory_callback_handle()
        if not on_destroy:
            self._send_skill_delete_message()
        if self._skill_level_buff is not None:
            self._tracker.owner.remove_buff(self._skill_level_buff)
            self._skill_level_buff = None
        if not on_destroy:
            self._tracker.update_affordance_caches()
        sim = self._tracker._owner.get_sim_instance()
        remove_super_affordance_commodity_flags(sim, self)

    def on_zone_load(self):
        self._max_level_update_sent = False

    def _apply_multipliers_to_continuous_statistics(self):
        for stat in self.statistic_multipliers:
            if stat.continuous:
                owner_stat = self.tracker.get_statistic(stat)
                if owner_stat is not None:
                    owner_stat._recalculate_modified_decay_rate()

    @classproperty
    def default_value(cls):
        return cls.initial_value

    @flexmethod
    @caches.cached
    def get_user_value(cls, inst):
        inst_or_cls = inst if inst is not None else cls
        return super(__class__, inst_or_cls).get_user_value()

    def _clear_user_value_cache(self):
        self.get_user_value.func.cache.clear()

    def set_value(self, value, *args, from_load=False, interaction=None, **kwargs):
        old_value = self.get_value()
        super().set_value(value, *args, **kwargs)
        if not caches.skip_cache:
            self._clear_user_value_cache()
        if from_load:
            return
        event_manager = services.get_event_manager()
        sim_info = self._tracker._owner
        new_value = self.get_value()
        new_level = self.convert_to_user_value(value)
        if old_value == self.initial_value or old_value != new_value:
            event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(self.stat_type,))
        old_level = self.convert_to_user_value(old_value)
        if old_level < new_level or old_value == self.initial_value:
            self._apply_multipliers_to_continuous_statistics()
            event_manager.process_event(test_events.TestEvent.SkillLevelChange, sim_info=sim_info, skill=self, new_level=new_level, custom_keys=(self.stat_type,))

    def add_value(self, add_amount, interaction=None, **kwargs):
        old_value = self.get_value()
        if old_value == self.initial_value:
            telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME
        else:
            telemhook = TELEMETRY_HOOK_SKILL_INTERACTION
        super().add_value(add_amount, interaction=interaction)
        if not caches.skip_cache:
            self._clear_user_value_cache()
        if interaction is not None:
            interaction_name = interaction.affordance.__name__
        else:
            interaction_name = TELEMETRY_INTERACTION_NOT_AVAILABLE
        self.on_skill_updated(telemhook, old_value, self.get_value(), interaction_name)

    def _update_value(self):
        old_value = self._value
        if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled:
            last_update = self._last_update
        time_delta = super()._update_value()
        if not caches.skip_cache:
            self._clear_user_value_cache()
        new_value = self._value
        if old_value < new_value:
            event_manager = services.get_event_manager()
            sim_info = self._tracker._owner if self._tracker is not None else None
            if old_value == self.initial_value:
                telemhook = TELEMETRY_HOOK_SKILL_INTERACTION_FIRST_TIME
                self.on_skill_updated(telemhook, old_value, new_value, TELEMETRY_INTERACTION_NOT_AVAILABLE)
            event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(self.stat_type,))
            old_level = self.convert_to_user_value(old_value)
            new_level = self.convert_to_user_value(new_value)
            if gsi_handlers.sim_handlers_log.skill_change_archiver.enabled and self.tracker.owner.is_sim:
                gsi_handlers.sim_handlers_log.archive_skill_change(self.tracker.owner, self, time_delta, old_value, new_value, new_level, last_update)
            if old_level < new_level or old_value == self.initial_value:
                if self._tracker is not None:
                    self._tracker.notify_watchers(self.stat_type, self._value, self._value)
                event_manager.process_event(test_events.TestEvent.SkillLevelChange, sim_info=sim_info, skill=self, new_level=new_level, custom_keys=(self.stat_type,))

    def _on_statistic_modifier_changed(self, notify_watcher=True):
        super()._on_statistic_modifier_changed(notify_watcher=notify_watcher)
        if not self.reached_max_level:
            return
        event_manager = services.get_event_manager()
        sim_info = self._tracker._owner if self._tracker is not None else None
        event_manager.process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, skill=self, statistic=self.stat_type, custom_keys=(self.stat_type,))

    def on_skill_updated(self, telemhook, old_value, new_value, affordance_name):
        owner_sim_info = self._tracker._owner
        if owner_sim_info.is_selectable:
            with telemetry_helper.begin_hook(skill_telemetry_writer, telemhook, sim_info=owner_sim_info) as hook:
                hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64)
                hook.write_string(TELEMETRY_FIELD_SKILL_AFFORDANCE, affordance_name)
                hook.write_bool(TELEMETRY_FIELD_SKILL_AFFORDANCE_SUCCESS, True)
                hook.write_int(TELEMETRY_FIELD_SKILL_AFFORDANCE_VALUE_ADD, new_value - old_value)
        if old_value == self.initial_value:
            skill_level = self.convert_to_user_value(old_value)
            self._handle_skill_up(skill_level)

    def _send_skill_delete_message(self):
        if self.tracker.owner.is_npc:
            return
        skill_msg = Commodities_pb2.SkillDelete()
        skill_msg.skill_id = self.guid64
        op = GenericProtocolBufferOp(Operation.SIM_SKILL_DELETE, skill_msg)
        Distributor.instance().add_op(self.tracker.owner, op)

    @staticmethod
    def _callback_handler(stat_inst):
        new_level = stat_inst.get_user_value()
        old_level = new_level - 1
        stat_inst.on_skill_level_up(old_level, new_level)
        stat_inst.refresh_threshold_callback()

    def _handle_skill_up(self, skill_level):
        self._show_level_notification(skill_level)
        self._update_skill_level_buff(skill_level)
        self._try_give_skill_up_payout(skill_level)
        self._tracker.update_affordance_caches()
        sim = self._tracker._owner.get_sim_instance()
        remove_super_affordance_commodity_flags(sim, self)
        super_affordances = tuple(self._tracker.get_cached_super_affordances_gen())
        apply_super_affordance_commodity_flags(sim, self, super_affordances)

    def _recalculate_modified_decay_rate(self):
        pass

    def refresh_level_up_callback(self):
        self._destory_callback_handle()

        def _on_level_up_callback(stat_inst):
            new_level = stat_inst.get_user_value()
            old_level = new_level - 1
            stat_inst.on_skill_level_up(old_level, new_level)
            stat_inst.refresh_level_up_callback()

        self._callback_handle = self.create_and_add_callback_listener(Threshold(self._get_next_level_bound(), operator.ge), _on_level_up_callback)

    def on_skill_level_up(self, old_level, new_level):
        tracker = self.tracker
        sim_info = tracker._owner
        if self.reached_max_level:
            for skill in self.skill_unlocks_on_max:
                skill_instance = tracker.add_statistic(skill, force_add=True)
                skill_instance.set_value(skill.initial_value)
        with telemetry_helper.begin_hook(skill_telemetry_writer, TELEMETRY_HOOK_SKILL_LEVEL_UP, sim_info=sim_info) as hook:
            hook.write_guid(TELEMETRY_FIELD_SKILL_ID, self.guid64)
            hook.write_int(TELEMETRY_FIELD_SKILL_LEVEL, new_level)
        self._handle_skill_up(new_level)
        services.get_event_manager().process_event(test_events.TestEvent.SkillValueChange, sim_info=sim_info, statistic=self.stat_type, custom_keys=(self.stat_type,))

    def _show_level_notification(self, skill_level, ignore_npc_check=False):
        sim_info = self._tracker._owner
        if not (ignore_npc_check or not sim_info.is_npc):
            if skill_level == 1:
                tutorial_service = services.get_tutorial_service()
                if tutorial_service is not None and tutorial_service.is_tutorial_running():
                    return
            level_data = self._get_level_data_for_skill_level(skill_level)
            if level_data is not None:
                tutorial_id = None
                if self.tutorial is not None:
                    if skill_level == 1:
                        tutorial_id = self.tutorial.guid64
                notification = level_data.level_up_notification(sim_info, resolver=SingleSimResolver(sim_info))
                notification.show_dialog(icon_override=IconInfoData(icon_resource=self.icon), secondary_icon_override=IconInfoData(obj_instance=sim_info), additional_tokens=(skill_level,), tutorial_id=tutorial_id)
                if level_data.level_up_screen_slam is not None:
                    level_data.level_up_screen_slam.send_screen_slam_message(sim_info, sim_info, self.stat_name, skill_level)

    def _update_skill_level_buff(self, skill_level):
        level_data = self._get_level_data_for_skill_level(skill_level)
        new_buff = level_data.skill_level_buff if level_data is not None else None
        if self._skill_level_buff is not None:
            self._tracker.owner.remove_buff(self._skill_level_buff)
            self._skill_level_buff = None
        if new_buff is not None:
            self._skill_level_buff = self._tracker.owner.add_buff(new_buff)

    def _try_give_skill_up_payout(self, skill_level):
        level_data = self._get_level_data_for_skill_level(skill_level)
        if level_data is None:
            return
        if level_data.rewards:
            for reward in level_data.rewards:
                reward().open_reward(self._tracker.owner, reward_destination=RewardDestination.SIM, reward_source=self)
        if level_data.loot:
            resolver = SingleSimResolver(self._tracker.owner)
            for loot in level_data.loot:
                loot.apply_to_resolver(resolver)

    def force_show_level_notification(self, skill_level):
        self._show_level_notification(skill_level, ignore_npc_check=True)

    @classmethod
    def send_commodity_update_message(cls, sim_info, old_value, new_value):
        stat_instance = sim_info.get_statistic(cls.stat_type, add=False)
        if stat_instance is None or not stat_instance.should_send_update(sim_info, new_value):
            return
        msg = cls.create_skill_update_msg(sim_info.id, new_value)
        add_object_message(sim_info, MSG_SIM_SKILL_UPDATE, msg, False)
        change_rate = stat_instance.get_change_rate()
        hide_progress_bar = False
        if sim_info.is_npc or sim_info.is_skill_bar_suppressed():
            hide_progress_bar = True
        op = distributor.ops.SkillProgressUpdate(cls.guid64, change_rate, new_value, hide_progress_bar)
        distributor.ops.record(sim_info, op)

    def save_statistic(self, commodities, skills, ranked_stats, tracker):
        current_value = self.get_saved_value()
        if current_value == self.initial_value:
            return
        message = protocols.Skill()
        message.name_hash = self.guid64
        message.value = current_value
        if self._time_of_last_value_change:
            message.time_of_last_value_change = self._time_of_last_value_change.absolute_ticks()
        skills.append(message)

    def unlocks_skills_on_max(self):
        return True

    def can_decay(self):
        return False

    def get_skill_provided_affordances(self):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return ((), ())
        return (level_data.super_affordances, level_data.target_super_affordances)

    def get_skill_provided_actor_mixers(self):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return
        return level_data.actor_mixers

    def get_actor_mixers(self, super_interaction):
        level_data = self._get_level_data_for_skill_level(self.get_user_value())
        if level_data is None:
            return []
        mixers = level_data.actor_mixers.get(super_interaction, tuple()) if level_data is not None else []
        return mixers

    @flexmethod
    def populate_localization_token(cls, inst, token):
        inst_or_cls = inst if inst is not None else cls
        token.type = LocalizedStringToken.STRING
        token.text_string = inst_or_cls.stat_name
Beispiel #26
0
 def __init__(self):
     super().__init__(commodities=TunableSet(TunableReference(services.statistic_manager(), description='The type of commodity to search for.'), description='List of commodities to run parameterized autonomy against after running this interaction.'), static_commodities=TunableSet(TunableReference(services.static_commodity_manager(), description='The type of static commodity to search for.'), description='List of static commodities to run parameterized autonomy against after running this interaction.'), same_target_only=Tunable(bool, False, description='If checked, only interactions on the same target as this interaction will be considered.'), retain_priority=Tunable(bool, True, needs_tuning=True, description='If checked, this autonomy request is run at the same priority level as the interaction creating it.  If unchecked, the interaction chosen will run at low priority.'), consider_same_target=Tunable(bool, True, description='If checked, parameterized autonomy will consider interactions on the current Target.'), retain_carry_target=Tunable(bool, True, description="If checked, the interactions considered for autonomy will retain this interaction's carry target. It is useful to uncheck this if the desired autonomous interactions need not to consider carry, e.g. the Grim Reaper finding arbitrary interactions while in an interaction holding his scythe as a carry target."), randomization_override=OptionalTunable(description='\n                    If enabled then the parameterized autonomy will run with\n                    an overwritten autonomy randomization settings.\n                    ', tunable=TunableEnumEntry(description='\n                        The autonomy randomization setting that will be used.\n                        ', tunable_type=AutonomyRandomization, default=AutonomyRandomization.UNDEFINED)), radius_to_consider=Tunable(description='\n                    The radius around the sim that targets must be in to be valid for Parameterized \n                    Autonomy.  Anything outside this radius will be ignored.  A radius of 0 is considered\n                    infinite.\n                    ', tunable_type=float, default=0), consider_scores_of_zero=Tunable(description='\n                    The autonomy request will consider scores of zero.  This allows sims to to choose things they \n                    might not desire.\n                    ', tunable_type=bool, default=False), description='Commodities and StaticCommodities will be combined, so interactions must support at least one commodity from both lists.')
Beispiel #27
0
class TutorialTip(
        metaclass=sims4.tuning.instances.HashedTunedInstanceMetaclass,
        manager=services.get_instance_manager(
            sims4.resources.Types.TUTORIAL_TIP)):
    INSTANCE_TUNABLES = {
        'required_tip_groups':
        TunableList(
            description=
            '\n            The Tip Groups that must be complete for this tip to be valid.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
                                     class_restrictions='TutorialTipGroup'),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_ui_list':
        TunableList(
            description=
            '\n            The UI elements that are required to be present in order for this\n            tutorial tip to be valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=TutorialTipUiElement,
                                     default=TutorialTipUiElement.UI_INVALID),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_ui_hidden_list':
        TunableList(
            description=
            '\n            The UI elements that are required to NOT be present in order for this\n            tutorial tip to be valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=TutorialTipUiElement,
                                     default=TutorialTipUiElement.UI_INVALID),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_game_state':
        TunableEnumEntry(
            description=
            '\n            The state the game must be in for this tutorial tip to be valid.\n            ',
            tunable_type=TutorialTipGameState,
            default=TutorialTipGameState.GAMESTATE_NONE,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_tips_not_satisfied':
        TunableList(
            description=
            '\n            This is a list of tips that must be un-satisfied in order for this\n            tip to activate. If any tip in this list is satisfied, this tip will\n            not activate.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
                                     class_restrictions='TutorialTip'),
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'platform_filter':
        TunableEnumEntry(
            description=
            '\n            The platforms on which this tutorial tip is shown.\n            ',
            tunable_type=tutorials.tutorial.TutorialPlatformFilter,
            default=tutorials.tutorial.TutorialPlatformFilter.ALL_PLATFORMS,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'required_tutorial_mode':
        TunableEnumEntry(
            description=
            '\n            What mode this tutorial tip should be restricted to.\n            STANDARD allows this tip to be in the original / standard tutorial mode.\n            FTUE allows this tip to be in the FTUE tutorial mode.\n            DISABLED means this tip is valid in any mode.\n            ',
            tunable_type=TutorialMode,
            default=TutorialMode.STANDARD,
            tuning_group=GROUP_NAME_DISPLAY_CRITERIA,
            export_modes=ExportModes.ClientBinary),
        'display':
        TunableTutorialTipDisplay(
            description=
            '\n            This display information for this tutorial tip.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'display_narration':
        OptionalTunable(
            description=
            '\n            Optionally play narration voice-over and display subtitles.\n            ',
            tunable=TunableTuple(
                voiceover_audio=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                voiceover_audio_ps4=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play specific to PS4.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                voiceover_audio_xb1=TunableResourceKey(
                    description=
                    '\n                    Narration audio to play specific to XB1.\n                    ',
                    default=None,
                    allow_none=True,
                    resource_types=(sims4.resources.Types.PROPX, )),
                subtitle_text=TunableLocalizedString(
                    description=
                    '\n                    Subtitles to display while audio narration is playing.\n                    '
                ),
                subtitle_display_location=TunableVariant(
                    description=
                    '\n                    What area on the screen the subtitles should appear.\n                    Top    - Use the generic top-of-screen position.\n                    Bottom - Use the generic bottom-of-screen position.\n                    Custom - Specify a custom position in terms of % vertically.\n                    ',
                    location=TunableEnumEntry(
                        description=
                        '\n                        Semantic location (UX-defined) for where the subtitles should appear.\n                        ',
                        tunable_type=TutorialTipSubtitleDisplayLocation,
                        default=TutorialTipSubtitleDisplayLocation.BOTTOM),
                    custom=TunablePercent(
                        description=
                        '\n                        Vertical position for the subtitles, expressed as a\n                        percentage of the height of the screen.\n                        ',
                        default=90),
                    default='location'),
                satisfy_when_voiceover_finished=Tunable(
                    description=
                    '\n                    If set, the tutorial tip will be marked as satisfied when the\n                    voiceover completes or is interrupted.\n                    ',
                    tunable_type=bool,
                    default=False),
                delay_satisfaction_until_voiceover_finished=Tunable(
                    description=
                    '\n                    If set, the tutorial tip will not be marked satisfied until after\n                    the voiceover completes, preventing the voiceover from being\n                    interrupted by external satisfaction.\n                    ',
                    tunable_type=bool,
                    default=False),
                keep_subtitle_visible_until_satisfaction=Tunable(
                    description=
                    '\n                    If set, the subtitle will remain visible until the tutorial tip is\n                    marked as satisfied, even though the voiceover may have finished.\n                    ',
                    tunable_type=bool,
                    default=False),
                export_class_name='TutorialTipNarrationDisplay'),
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'activation_ui_message':
        TunableTutorialTipUiMessage(
            description=
            '\n            Sends a message to the UI when this tip is activated.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'deactivation_ui_message':
        TunableTutorialTipUiMessage(
            description=
            '\n            Sends a message to the UI when this tip is deactivated.\n            ',
            tuning_group=GROUP_NAME_ACTIONS,
            export_modes=ExportModes.ClientBinary),
        'buffs':
        TunableList(
            description=
            '\n            Buffs that will be applied at the start of this tutorial tip.\n            ',
            tunable=TunableBuffReference(),
            tuning_group=GROUP_NAME_ACTIONS),
        'buffs_removed_on_deactivate':
        Tunable(
            description=
            '\n            If enabled, this tip will remove those buffs on deactivate.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'commodities_to_solve':
        TunableSet(
            description=
            "\n            A set of commodities we will attempt to solve. This will result in\n            the Sim's interaction queue being filled with various interactions.\n            ",
            tunable=TunableReference(services.statistic_manager()),
            tuning_group=GROUP_NAME_ACTIONS),
        'gameplay_loots':
        OptionalTunable(
            description=
            '\n            Loots that will be given at the start of this tip.\n            Actor is is the sim specified by Sim Actor.\n            Target is the sim specified by Sim Target.\n            ',
            tunable=TunableList(
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION),
                                         class_restrictions=('LootActions', ),
                                         pack_safe=True)),
            tuning_group=GROUP_NAME_ACTIONS),
        'restricted_affordances':
        OptionalTunable(
            description=
            '\n            If enabled, use the filter to determine which affordances are allowed.\n            ',
            tunable=TunableTuple(
                visible_affordances=TunableAffordanceFilterSnippet(
                    description=
                    '\n                    The filter of affordances that are visible.\n                    '
                ),
                tooltip=OptionalTunable(
                    description=
                    '\n                    Tooltip when interaction is disabled by tutorial restrictions\n                    If not specified, will use the default in the tutorial service\n                    tuning.\n                    ',
                    tunable=sims4.localization.TunableLocalizedStringFactory(
                    )),
                enabled_affordances=TunableAffordanceFilterSnippet(
                    description=
                    '\n                    The filter of visible affordances that are enabled.\n                    '
                )),
            tuning_group=GROUP_NAME_ACTIONS),
        'call_to_actions':
        OptionalTunable(
            description=
            '\n            Call to actions that should persist for the duration of this tip.\n            ',
            tunable=TunableList(
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.CALL_TO_ACTION),
                                         pack_safe=True)),
            tuning_group=GROUP_NAME_ACTIONS),
        'end_drama_node':
        Tunable(
            description=
            '\n            If enabled, this tip will end the tutorial drama node.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'sim_actor':
        TunableEnumEntry(
            description=
            "\n            The entity who will be the actor sim for loot, and will\n            receive the items that aren't specified via loots.\n            \n            If there is no Tutorial Drama Node active, actor will be active\n            sim\n            ",
            tunable_type=TutorialTipActorOption,
            default=TutorialTipActorOption.ACTIVE_SIM,
            tuning_group=GROUP_NAME_ACTIONS),
        'sim_target':
        TunableEnumEntry(
            description=
            '\n            The entity who will be the target sim for loot\n            \n            If there is no Tutorial Drama Node active, target sim will be active\n            sim.\n            ',
            tunable_type=TutorialTipActorOption,
            default=TutorialTipActorOption.ACTIVE_SIM,
            tuning_group=GROUP_NAME_ACTIONS),
        'add_target_to_actor_household':
        Tunable(
            description=
            '\n            If enabled, target sim will be added to active sim household.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'make_housemate_unselectable':
        Tunable(
            description=
            '\n            If enabled, housemate will be unselectable for the duration of the\n            tooltip.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_ACTIONS),
        'timeout_satisfies':
        Tunable(
            description=
            '\n            If enabled, this tip is satisfied when the timeout is reached.\n            If disabled, this tip will not satisfy when the timeout is reached.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.ClientBinary),
        'gameplay_test':
        OptionalTunable(
            description=
            '\n            Tests that, if passed, will satisfy this tutorial tip.\n            Only one test needs to pass to satisfy. These are intended for tips\n            where the satisfy message should be tested and sent at a later time.\n            ',
            tunable=tutorials.tutorial.TunableTutorialTestVariant(),
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'sim_tested':
        TunableEnumEntry(
            description=
            '\n            The entity who must fulfill the test events.\n            \n            If there is no Tutorial Drama Node, player sim and housemate sim will be active\n            sim.\n            ',
            tunable_type=TutorialTipTestSpecificityOption,
            default=TutorialTipTestSpecificityOption.UNSPECIFIED,
            tuning_group=GROUP_NAME_SATISFY),
        'time_of_day':
        OptionalTunable(
            description=
            '\n            If specified, tutorialtip will be satisfied once the time passes \n            the specified time.\n            ',
            tunable=TunableTimeOfDay(),
            tuning_group=GROUP_NAME_SATISFY),
        'gameplay_immediate_test':
        OptionalTunable(
            description=
            '\n            Tests that, if passed, will satisfy this tutorial tip.\n            Only one test needs to pass to satisfy. These are intended for tips\n            where the satisfy message should be tested and sent back immediately.\n            ',
            tunable=tutorials.tutorial.TunableTutorialTestVariant(),
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'satisfy_on_active_sim_change':
        Tunable(
            description=
            '\n            If enabled, this tip is satisfied when the active sim changes\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.All),
        'satisfy_on_activate':
        Tunable(
            description=
            "\n            If enabled, this tip is satisfied immediately when all of it's\n            preconditions have been met.\n            ",
            tunable_type=bool,
            default=False,
            tuning_group=GROUP_NAME_SATISFY,
            export_modes=ExportModes.ClientBinary),
        'tutorial_group_to_complete_on_skip':
        TunableReference(
            description=
            '\n            The tutorial group who will have all tutorial tips within it\n            completed when the button to skip all is pressed from this tip.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.TUTORIAL_TIP),
            class_restrictions='TutorialTipGroup',
            export_modes=ExportModes.ClientBinary)
    }

    def __init__(self):
        raise NotImplementedError

    @classmethod
    def activate(cls):
        tutorial_service = services.get_tutorial_service()
        client = services.client_manager().get_first_client()
        actor_sim_info = client.active_sim.sim_info
        target_sim_info = actor_sim_info
        housemate_sim_info = None
        tutorial_drama_node = None
        drama_scheduler = services.drama_scheduler_service()
        if drama_scheduler is not None:
            drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                DramaNodeType.TUTORIAL)
            if drama_nodes:
                tutorial_drama_node = drama_nodes[0]
                housemate_sim_info = tutorial_drama_node.get_housemate_sim_info(
                )
                player_sim_info = tutorial_drama_node.get_player_sim_info()
                if cls.sim_actor == TutorialTipActorOption.PLAYER_SIM:
                    actor_sim_info = player_sim_info
                elif cls.sim_actor == TutorialTipActorOption.HOUSEMATE_SIM:
                    actor_sim_info = housemate_sim_info
                if cls.sim_target == TutorialTipActorOption.PLAYER_SIM:
                    target_sim_info = player_sim_info
                elif cls.sim_target == TutorialTipActorOption.HOUSEMATE_SIM:
                    target_sim_info = housemate_sim_info
        if cls.gameplay_immediate_test is not None:
            resolver = event_testing.resolver.SingleSimResolver(actor_sim_info)
            if resolver(cls.gameplay_immediate_test):
                cls.satisfy()
            else:
                return
        for buff_ref in cls.buffs:
            actor_sim_info.add_buff_from_op(buff_ref.buff_type,
                                            buff_reason=buff_ref.buff_reason)
        if cls.gameplay_test is not None:
            services.get_event_manager().register_tests(
                cls, [cls.gameplay_test])
        if cls.satisfy_on_active_sim_change:
            client = services.client_manager().get_first_client()
            if client is not None:
                client.register_active_sim_changed(cls._on_active_sim_change)
        if cls.commodities_to_solve:
            actor_sim = actor_sim_info.get_sim_instance()
            if actor_sim is not None:
                context = InteractionContext(
                    actor_sim,
                    InteractionContext.SOURCE_SCRIPT_WITH_USER_INTENT,
                    priority.Priority.High,
                    bucket=InteractionBucketType.DEFAULT)
                for commodity in cls.commodities_to_solve:
                    if not actor_sim.queue.can_queue_visible_interaction():
                        break
                    autonomy_request = autonomy.autonomy_request.AutonomyRequest(
                        actor_sim,
                        autonomy_mode=autonomy.autonomy_modes.FullAutonomy,
                        commodity_list=(commodity, ),
                        context=context,
                        consider_scores_of_zero=True,
                        posture_behavior=AutonomyPostureBehavior.
                        IGNORE_SI_STATE,
                        distance_estimation_behavior=
                        AutonomyDistanceEstimationBehavior.
                        ALLOW_UNREACHABLE_LOCATIONS,
                        allow_opportunity_cost=False,
                        autonomy_mode_label_override='Tutorial')
                    selected_interaction = services.autonomy_service(
                    ).find_best_action(autonomy_request)
                    AffordanceObjectPair.execute_interaction(
                        selected_interaction)
        if cls.gameplay_loots:
            resolver = DoubleSimResolver(actor_sim_info, target_sim_info)
            for loot_action in cls.gameplay_loots:
                loot_action.apply_to_resolver(resolver)
        if cls.restricted_affordances is not None and tutorial_service is not None:
            tutorial_service.set_restricted_affordances(
                cls.restricted_affordances.visible_affordances,
                cls.restricted_affordances.tooltip,
                cls.restricted_affordances.enabled_affordances)
        if cls.call_to_actions is not None:
            call_to_action_service = services.call_to_action_service()
            for call_to_action_fact in cls.call_to_actions:
                call_to_action_service.begin(call_to_action_fact, None)
        if cls.add_target_to_actor_household:
            household_manager = services.household_manager()
            household_manager.switch_sim_household(target_sim_info)
        if cls.make_housemate_unselectable and tutorial_service is not None:
            tutorial_service.set_unselectable_sim(housemate_sim_info)
        if cls.end_drama_node and tutorial_drama_node is not None:
            tutorial_drama_node.end()
        if cls.time_of_day is not None and tutorial_service is not None:
            tutorial_service.add_tutorial_alarm(cls, lambda _: cls.satisfy(),
                                                cls.time_of_day)

    @classmethod
    def _on_active_sim_change(cls, old_sim, new_sim):
        cls.satisfy()

    @classmethod
    def handle_event(cls, sim_info, event, resolver):
        if cls.gameplay_test is not None and resolver(cls.gameplay_test):
            if cls.sim_tested != TutorialTipTestSpecificityOption.UNSPECIFIED:
                client = services.client_manager().get_first_client()
                test_sim_info = client.active_sim.sim_info
                drama_scheduler = services.drama_scheduler_service()
                if drama_scheduler is not None:
                    drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                        DramaNodeType.TUTORIAL)
                    if drama_nodes:
                        drama_node = drama_nodes[0]
                        if cls.sim_tested == TutorialTipTestSpecificityOption.PLAYER_SIM:
                            test_sim_info = drama_node.get_player_sim_info()
                        elif cls.sim_tested == TutorialTipTestSpecificityOption.HOUSEMATE_SIM:
                            test_sim_info = drama_node.get_housemate_sim_info()
                if test_sim_info is not sim_info:
                    return
            cls.satisfy()

    @classmethod
    def satisfy(cls):
        op = distributor.ops.SetTutorialTipSatisfy(cls.guid64)
        distributor_instance = Distributor.instance()
        distributor_instance.add_op_with_no_owner(op)

    @classmethod
    def deactivate(cls):
        tutorial_service = services.get_tutorial_service()
        client = services.client_manager().get_first_client()
        if cls.gameplay_test is not None:
            services.get_event_manager().unregister_tests(
                cls, (cls.gameplay_test, ))
        if cls.satisfy_on_active_sim_change and client is not None:
            client.unregister_active_sim_changed(cls._on_active_sim_change)
        if cls.restricted_affordances is not None and tutorial_service is not None:
            tutorial_service.clear_restricted_affordances()
        if cls.call_to_actions is not None:
            call_to_action_service = services.call_to_action_service()
            for call_to_action_fact in cls.call_to_actions:
                call_to_action_service.end(call_to_action_fact)
        if cls.buffs_removed_on_deactivate:
            actor_sim_info = None
            if client is not None:
                actor_sim_info = client.active_sim.sim_info
            drama_scheduler = services.drama_scheduler_service()
            if drama_scheduler is not None:
                drama_nodes = drama_scheduler.get_running_nodes_by_drama_node_type(
                    DramaNodeType.TUTORIAL)
                if drama_nodes:
                    tutorial_drama_node = drama_nodes[0]
                    if cls.sim_actor == TutorialTipActorOption.PLAYER_SIM:
                        actor_sim_info = tutorial_drama_node.get_player_sim_info(
                        )
                    elif cls.sim_actor == TutorialTipActorOption.HOUSEMATE_SIM:
                        actor_sim_info = tutorial_drama_node.get_housemate_sim_info(
                        )
            if actor_sim_info is not None:
                for buff_ref in cls.buffs:
                    actor_sim_info.remove_buff_by_type(buff_ref.buff_type)
        if cls.time_of_day is not None and tutorial_service is not None:
            tutorial_service.remove_tutorial_alarm(cls)
        if cls.make_housemate_unselectable and tutorial_service is not None:
            tutorial_service.set_unselectable_sim(None)
class RotateTargetPhotoLoot(HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'photographer_loot':
        TunableReference(
            description=
            '\n            Loot to apply to the Photographer.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
            class_restrictions=('LootActions', ),
            pack_safe=True),
        'target_loot':
        TunableReference(
            description=
            '\n            Loot to give to the Rotate Selfie Target.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
            class_restrictions=('LootActions', ),
            pack_safe=True),
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled, this notification will show after the photo is\n            taken.\n            ',
            tunable=UiDialogNotification.TunableFactory()),
        'statistic_info':
        OptionalTunable(
            description=
            "\n            Statistic to be passed in as an additional token available. The\n            token will be the difference between the tuned statistics value\n            before the interaction and after the loot is applied.\n            \n            Use Case: Simstagram Pet interaction gives to the pet's simstagram\n            account, and the player sees a notification with the amount of followers\n            gained.\n            \n            IMPORTANT: The tuned statistic is expected to have a default value of\n            0. If not, the resulting difference token will not be accurate. \n            ",
            tunable=TunableTuple(
                description=
                ' \n                Specify the value of a specific statistic from the selected participant.\n                ',
                participant=TunableEnumEntry(
                    description=
                    "\n                    The participant from whom we will fetch the specified\n                    statistic's value.\n                    ",
                    tunable_type=ParticipantType,
                    default=ParticipantType.Actor),
                statistic=TunableReference(
                    description=
                    "\n                    The statistic's whose value we want to fetch.\n                    ",
                    manager=services.statistic_manager())))
    }

    def __init__(self, photographer, interaction, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.photographer = photographer
        self.stored_statistic_value = 0
        if self.statistic_info is None:
            return
        participant = self.statistic_info.participant
        sim = interaction.get_participant(participant)
        if participant is not None:
            tracker = sim.get_tracker(self.statistic_info.statistic)
            if tracker is not None:
                self.stored_statistic_value = tracker.get_value(
                    self.statistic_info.statistic)

    def apply_loot(self, sim):
        target_info = services.sim_info_manager().get(sim.id)
        target_resolver = SingleSimResolver(target_info)
        self.target_loot.apply_to_resolver(target_resolver)
        photographer_info = services.sim_info_manager().get(
            self.photographer.id)
        photographer_resolver = SingleSimResolver(photographer_info)
        self.photographer_loot.apply_to_resolver(photographer_resolver)
        tracker = target_info.get_tracker(self.statistic_info.statistic)
        current_statvalue = tracker.get_value(self.statistic_info.statistic)
        change_amount = abs(current_statvalue - self.stored_statistic_value)
        if self.photographer is None:
            logger.error(
                'Got a None Sim {} while applying loot to the photographer.',
                self.photographer,
                owner='shipark')
        if self.notification is None:
            return
        notification = self.notification(self.photographer,
                                         resolver=DoubleSimResolver(
                                             photographer_info, target_info),
                                         target_sim_id=sim.id)
        if change_amount > 0:
            notification.show_dialog(additional_tokens=(change_amount, ))
        else:
            notification.show_dialog()
class RelationshipBit(HasTunableReference,
                      SuperAffordanceProviderMixin,
                      MixerProviderMixin,
                      metaclass=HashedTunedInstanceMetaclass,
                      manager=services.relationship_bit_manager()):
    INSTANCE_TUNABLES = {
        'display_name':
        TunableLocalizedStringFactory(
            description=
            '\n            Localized name of this bit\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'bit_description':
        TunableLocalizedStringFactory(
            description=
            '\n            Localized description of this bit\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed for the relationship bit.\n            ',
            allow_none=True,
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All),
        'bit_added_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is added.\n            ',
            tunable=TunableTuple(
                notification=TunableUiDialogNotificationSnippet(),
                show_if_unselectable=Tunable(
                    description=
                    '\n                    If this is checked, then the notification is displayed if\n                    the owning Sim is not selectable, but the target is.\n                    Normally, notifications are only displayed if the owning Sim\n                    is selectable.\n                    ',
                    tunable_type=bool,
                    default=False))),
        'bit_removed_notification':
        OptionalTunable(
            description=
            '\n            If enabled, a notification will be displayed when this bit is removed.\n            ',
            tunable=TunableUiDialogNotificationSnippet()),
        'depth':
        Tunable(
            description=
            '\n            The amount of depth provided by the bit.\n            ',
            tunable_type=int,
            default=0),
        'priority':
        Tunable(
            description=
            '\n            Priority of the bit.  This is used when a bit turns on while a\n            mutually exclusive bit is already on.\n            ',
            tunable_type=float,
            default=0),
        'display_priority':
        Tunable(
            description=
            '\n            The priority of this bit with regards to UI.  Only the highest\n            priority bits are displayed.\n            ',
            tunable_type=int,
            default=0,
            export_modes=ExportModes.All),
        'exclusive':
        Tunable(
            description=
            "\n            Whether or not the bit is exclusive. This means that a sim can only have \n            this bit with one other sim.  If you attempt to add an exclusive bit to \n            a sim that already has the same one with another sim, it will remove the \n            old bit.\n            \n            Example: A sim can only be BFF's with one other sim.  If the sim asks \n            another sim to be their BFF, the old bit is removed.\n            ",
            tunable_type=bool,
            default=False),
        'visible':
        Tunable(
            description=
            "\n            If True, this bit has the potential to be visible when applied,\n            depending on display_priority and the other active bits.  If False,\n            the bit will not be displayed unless it's part of the\n            REL_INSPECTOR_TRACK bit track.\n            ",
            tunable_type=bool,
            default=True),
        'group_id':
        TunableEnumEntry(
            description=
            '\n            The group this bit belongs to.  Two bits of the same group cannot\n            belong in the same set of bits for a given relationship.\n            ',
            tunable_type=RelationshipBitType,
            default=RelationshipBitType.NoGroup),
        'triggered_track':
        TunableReference(
            description=
            '\n            If set, the track that is triggered when this bit is set\n            ',
            manager=services.statistic_manager(),
            allow_none=True,
            class_restrictions='RelationshipTrack'),
        'required_bits':
        TunableList(
            description=
            '\n            List of all bits that are required to be on in order to allow this\n            bit to turn on.\n            ',
            tunable=TunableReference(services.relationship_bit_manager())),
        'timeout':
        TunableSimMinute(
            description=
            '\n            The length of time this bit will last in sim minutes.  0 means the\n            bit will never timeout.\n            ',
            default=0),
        'remove_on_threshold':
        OptionalTunable(tunable=TunableTuple(
            description=
            '\n                If enabled, this bit will be removed when the referenced track\n                reaches the appropriate threshold.\n                ',
            track=TunableReference(
                description=
                '\n                    The track to be tested.\n                    ',
                manager=services.statistic_manager(),
                class_restrictions='RelationshipTrack'),
            threshold=TunableThreshold(
                description=
                '\n                    The threshold at which to remove this bit.\n                    '
            ))),
        'historical_bits':
        OptionalTunable(tunable=TunableList(tunable=TunableTuple(
            age_trans_from=
            TunableEnumEntry(description=
                             '\n                        Age we are transitioning out of.\n                        ',
                             tunable_type=sims.sim_info_types.Age,
                             default=sims.sim_info_types.Age.CHILD),
            new_historical_bit=TunableReference(
                description=
                '\n                        New historical bit the sim obtains\n                        ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.RELATIONSHIP_BIT))))),
        'collection_ids':
        TunableList(tunable=TunableEnumEntry(
            description=
            '\n                The bit collection id this bit belongs to, like family,\n                friends, romance. Default to be All.\n                ',
            tunable_type=RelationshipBitCollectionUid,
            default=RelationshipBitCollectionUid.All,
            export_modes=ExportModes.All)),
        'buffs_on_add_bit':
        TunableList(tunable=TunableTuple(
            buff_ref=buffs.tunable.
            TunableBuffReference(description=
                                 '\n                    Buff that gets added to sim when bit is added.\n                    '
                                 ),
            amount=Tunable(
                description=
                '\n                    If buff is tied to commodity the amount to add to the\n                    commodity.\n                    ',
                tunable_type=float,
                default=1),
            only_add_once=Tunable(
                description=
                '\n                    If True, the buff should only get added once no matter how\n                    many times this bit is being applied.\n                    ',
                tunable_type=bool,
                default=False))),
        'buffs_to_add_if_on_active_lot':
        TunableList(
            description=
            "\n            List of buffs to add when a sim that I share this relationship with\n            is in the household that owns the lot that I'm on.\n            ",
            tunable=buffs.tunable.TunableBuffReference(
                description=
                '\n                Buff that gets added to sim when bit is added.\n                '
            )),
        'autonomy_multiplier':
        Tunable(
            description=
            '\n            This value is multiplied to the autonomy score of any interaction\n            performed between the two Sims.  For example, when the Sim decides\n            to socialize, she will start looking at targets to socialize with.\n            If there is a Sim who she shares this bit with, her final score for\n            socializing with that Sim will be multiplied by this value.\n            ',
            tunable_type=float,
            default=1),
        'relationship_culling_prevention':
        TunableEnumEntry(
            description=
            '\n            Determine if bit should prevent relationship culling.  \n            \n            ALLOW_ALL = all culling\n            PLAYED_ONLY = only cull if not a played household\n            PLAYED_AND_UNPLAYED = disallow culling for played and unplayed sims. (e.g. family bits)\n            ',
            tunable_type=RelationshipBitCullingPrevention,
            default=RelationshipBitCullingPrevention.ALLOW_ALL),
        'persisted_tuning':
        Tunable(
            description=
            '\n            Whether this bit will persist when saving a Sim. \n            \n            For example, a Sims is good_friends should be set to true, but\n            romantic_gettingMarried should not be saved.\n            ',
            tunable_type=bool,
            default=True),
        'bit_added_loot_list':
        TunableList(
            description=
            '\n            A list of loot operations to apply when this relationship bit is\n            added.\n            ',
            tunable=TunableReference(manager=services.get_instance_manager(
                sims4.resources.Types.ACTION),
                                     class_restrictions=('LootActions', ),
                                     pack_safe=True)),
        'directionality':
        TunableEnumEntry(
            description=
            '\n            The direction that this Relationship bit points.  Bidirectional\n            means that both Sims will be given this bit if it is added.\n            Unidirectional means that only one Sim will be given this bit.\n            If it is coming from loot that bit will be given to the Actor.\n            ',
            tunable_type=RelationshipDirection,
            default=RelationshipDirection.BIDIRECTIONAL)
    }
    is_track_bit = False
    trait_replacement_bits = None
    _cached_commodity_flags = None

    def __init__(self):
        self._buff_handles = None
        self._conditional_removal_listener = None
        self._appropriate_buffs_handles = None

    @classproperty
    def persisted(cls):
        return cls.persisted_tuning

    @classproperty
    def is_collection(cls):
        return False

    def add_buffs_for_bit_add(self, sim, relationship, from_load):
        for buff_data in self.buffs_on_add_bit:
            buff_type = buff_data.buff_ref.buff_type
            if from_load and buff_type.commodity:
                continue
            if buff_data.only_add_once:
                if buff_type.guid64 in relationship.get_bit_added_buffs(
                        sim.sim_id):
                    continue
                relationship.add_bit_added_buffs(sim.sim_id, buff_type)
            if buff_type.commodity:
                tracker = sim.get_tracker(buff_type.commodity)
                tracker.add_value(buff_type.commodity, buff_data.amount)
                sim.set_buff_reason(buff_type, buff_data.buff_ref.buff_reason)
            else:
                buff_handle = sim.add_buff(
                    buff_type, buff_reason=buff_data.buff_ref.buff_reason)
                if self._buff_handles is None:
                    self._buff_handles = []
                self._buff_handles.append((sim.sim_id, buff_handle))

    def _apply_bit_added_loot(self, sim_info, target_sim_info):
        resolver = DoubleSimResolver(sim_info, target_sim_info)
        for loot in self.bit_added_loot_list:
            loot.apply_to_resolver(resolver)

    def on_add_to_relationship(self, sim, target_sim_info, relationship,
                               from_load):
        if relationship._is_object_rel:
            return
        target_sim = target_sim_info.get_sim_instance()
        self.add_buffs_for_bit_add(sim, relationship, from_load)
        if target_sim is not None and self.directionality == RelationshipDirection.BIDIRECTIONAL:
            self.add_buffs_for_bit_add(target_sim, relationship, from_load)
        if not from_load:
            self._apply_bit_added_loot(sim.sim_info, target_sim_info)
            if self.directionality == RelationshipDirection.BIDIRECTIONAL:
                self._apply_bit_added_loot(target_sim_info, sim.sim_info)

    def on_remove_from_relationship(self, sim, target_sim_info):
        target_sim = target_sim_info.get_sim_instance()
        if self._buff_handles is not None:
            for (sim_id, buff_handle) in self._buff_handles:
                if sim.sim_id == sim_id:
                    sim.remove_buff(buff_handle)
                elif target_sim is not None:
                    target_sim.remove_buff(buff_handle)
            self._buff_handles = None

    def add_appropriateness_buffs(self, sim_info):
        if not self._appropriate_buffs_handles:
            if self.buffs_to_add_if_on_active_lot:
                self._appropriate_buffs_handles = []
                for buff in self.buffs_to_add_if_on_active_lot:
                    handle = sim_info.add_buff(buff.buff_type,
                                               buff_reason=buff.buff_reason)
                    self._appropriate_buffs_handles.append(handle)

    def remove_appropriateness_buffs(self, sim_info):
        if self._appropriate_buffs_handles is not None:
            for buff in self._appropriate_buffs_handles:
                sim_info.remove_buff(buff)
            self._appropriate_buffs_handles = None

    def add_conditional_removal_listener(self, listener):
        if self._conditional_removal_listener is not None:
            logger.error(
                'Attempting to add a conditional removal listener when one already exists; old one will be overwritten.',
                owner='jjacobson')
        self._conditional_removal_listener = listener

    def remove_conditional_removal_listener(self):
        listener = self._conditional_removal_listener
        self._conditional_removal_listener = None
        return listener

    def __repr__(self):
        bit_type = type(self)
        return '<({}) Type: {}.{}>'.format(bit_type.__name__,
                                           bit_type.__mro__[1].__module__,
                                           bit_type.__mro__[1].__name__)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.historical_bits is not None:
            for bit in cls.historical_bits:
                pass

    @classmethod
    def commodity_flags(cls):
        if cls._cached_commodity_flags is None:
            commodity_flags = set()
            for super_affordance in cls.get_provided_super_affordances_gen():
                commodity_flags.update(super_affordance.commodity_flags)
            cls._cached_commodity_flags = frozenset(commodity_flags)
        return cls._cached_commodity_flags

    def show_bit_added_dialog(self, owner, sim, target_sim_info):
        dialog = self.bit_added_notification.notification(
            owner, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    def show_bit_removed_dialog(self, sim, target_sim_info):
        dialog = self.bit_removed_notification(
            sim, DoubleSimResolver(sim, target_sim_info))
        dialog.show_dialog(additional_tokens=(sim, target_sim_info))

    @classmethod
    def matches_bit(cls, bit_type):
        return cls is bit_type
 def __init__(self, description='Apply an operation to a statistic.', **kwargs):
     super().__init__(who=TunableEnumEntry(ParticipantType, ParticipantType.Actor, description='Who or what to apply this test to'), stat=TunableReference(services.statistic_manager(), description='The commodity we are gaining.'), threshold=TunableThreshold(description='A commodity value and comparison that defines the exit condition hits (or hit commodity.maximum).'), absolute=Tunable(bool, True, needs_tuning=True, description="True = treat the threshold value as an absolute commodity value.  Otherwise, it is relative to the Sim's start value."), **kwargs)
 def __init__(self, **kwargs):
     super().__init__(skill_to_test=TunableReference(services.statistic_manager(), description='The skill used to earn the Simoleons, if applicable.'), **kwargs)
Beispiel #32
0
 def __init__(self, description=''):
     import statistics.commodity
     (super().__init__(description=description, key_type=TunableReference(services.statistic_manager(), class_restrictions=(statistics.commodity.Commodity,), description='\n                    The stat the modifier will apply to.\n                    '), value_type=Tunable(float, 0, description='Multiply statistic decay by this value.')),)