Пример #1
0
class BuffRemovalOp(BaseLootOperation):
    __qualname__ = 'BuffRemovalOp'
    FACTORY_TUNABLES = {
        'description':
        '\n        This loot will remove buffs from a Sim.\n        ',
        'remove_all_visible_buffs':
        Tunable(
            description=
            "\n        If checked, all visible buffs on the Sim, excluding those specified in\n        the 'buffs_to_ignore' list will be removed.  If unchecked, buff removal\n        will be handled by the 'buffs_to_remove' list.\n        ",
            tunable_type=bool,
            default=False),
        'buffs_to_remove':
        TunableList(
            description=
            "\n        If 'remove_all_buffs' is not checked, this is the list of buffs that\n        will be removed from the subject.  If 'remove_all_buffs' is checked,\n        this list will be ignored.\n        ",
            tunable=TunableReference(
                description='\n            Buff to be removed.\n            ',
                manager=services.buff_manager())),
        'buffs_to_ignore':
        TunableList(
            description=
            "\n        If 'remove_all_buffs' is checked, no buffs included in this list will\n        be removed.  If 'remove_all_buffs' is unchecked, this list will be\n        ignored.\n        ",
            tunable=TunableReference(
                description='\n            Buff to be removed.\n            ',
                manager=services.buff_manager()))
    }

    def __init__(self, remove_all_visible_buffs, buffs_to_remove,
                 buffs_to_ignore, **kwargs):
        super().__init__(**kwargs)
        self._remove_all_visible_buffs = remove_all_visible_buffs
        self._buffs_to_remove = buffs_to_remove
        self._buffs_to_ignore = buffs_to_ignore

    def _apply_to_subject_and_target(self, subject, target, resolver):
        removal_list = []
        if self._remove_all_visible_buffs:
            removal_list.extend(subject.Buffs)
            for buff in removal_list:
                if type(buff) in self._buffs_to_ignore:
                    pass
                if not buff.visible:
                    pass
                if buff.commodity is not None:
                    tracker = subject.get_tracker(buff.commodity)
                    commodity_inst = tracker.get_statistic(buff.commodity)
                    if commodity_inst.core:
                        pass
                subject.Buffs.remove_buff_entry(buff)
        else:
            for buff_type in self._buffs_to_remove:
                subject.Buffs.remove_buff_by_type(buff_type)
def archive_buff_message(buff_msg, shows_timeout, change_rate):
    buff_reason = hex(
        buff_msg.reason.hash) if buff_msg.HasField('reason') else None
    entry = {
        'buff_id': buff_msg.buff_id,
        'equipped': buff_msg.equipped,
        'buff_reason': buff_reason,
        'is_mood_buff': buff_msg.is_mood_buff,
        'commodity_guid': buff_msg.commodity_guid,
        'transition_into_buff_id': buff_msg.transition_into_buff_id
    }
    manager = services.buff_manager()
    if manager:
        buff_cls = manager.get(buff_msg.buff_id)
        entry['buff_name'] = buff_cls.__name__
    if buff_msg.timeout:
        entry['timeout'] = buff_msg.timeout
        entry['rate'] = buff_msg.rate_multiplier
    if buff_msg.equipped and shows_timeout and change_rate is not None:
        if buff_msg.buff_progress == Sims_pb2.BUFF_PROGRESS_NONE:
            entry['progress_arrow'] = 'No Arrow'
        elif buff_msg.buff_progress == Sims_pb2.BUFF_PROGRESS_UP:
            entry['progress_arrow'] = 'Arrow Up'
        else:
            entry['progress_arrow'] = 'Arrow Down'
    if buff_msg.HasField('mood_type_override'):
        entry['mood_type_override'] = buff_msg.mood_type_override
    sim_buff_log_archiver.archive(data=entry, object_id=buff_msg.sim_id)
Пример #3
0
class FishingBait(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'bait_name':
        TunableLocalizedStringFactory(
            description='\n            Name of fishing bait.\n            '),
        'bait_description':
        TunableLocalizedStringFactory(
            description=
            '\n            Description of fishing bait.\n            '),
        'bait_icon_definition':
        TunableReference(
            description=
            '\n            Object definition that will be used to render icon of fishing bait.\n            ',
            manager=services.definition_manager()),
        'bait_buff':
        TunableReference(
            description='\n            Buff of fishing bait.\n            ',
            manager=services.buff_manager()),
        'bait_priority':
        TunableRange(
            description=
            '\n            The priority of the bait. When an object can be categorized into\n            multiple fishing bait categories, the highest priority category \n            will be chosen.\n            ',
            tunable_type=int,
            default=1,
            minimum=1)
    }
Пример #4
0
class FishingLocationExamineWaterSuperInteraction(
        FishingLocationSuperInteraction):
    EXAMINE_SUCCESS_NOTIFICATION = ui.ui_dialog_notification.UiDialogNotification.TunableFactory(
        description=
        "\n        The notification that is displayed when a Sim successfully examines a fishing location.\n        \n        Notice that the text itself can't be tuned here. Those will be pulled\n        from the Examine Localization Map it a fish is found that requires\n        bait, or we'll use the Generic Examine Notification Text if there are\n        no fish that require bait.\n        ",
        locked_args={'text': None})
    BAIT_NOTIFICATION_TEXT_MAP = sims4.tuning.tunable.TunableMapping(
        key_type=sims4.tuning.tunable.TunableReference(
            manager=services.buff_manager(), pack_safe=True),
        key_name='Bait Buff',
        value_type=sims4.localization.TunableLocalizedStringFactory(
            description=
            '\n            If the Sim examines the water and a fish in the water requires the\n            tuned Bait Buff, there is a chance this is the string that will show\n            up in the TNS.\n            '
        ),
        value_name='Notification Text')
    GENERIC_EXAMINE_NOTIFICATION_TEXT = sims4.localization.TunableLocalizedStringFactory(
        description=
        '\n        If the Sim successfully examines the water but there are no fish that\n        require bait, this is the string that will show in the notification.\n        '
    )
    _notification_bait_types = singletons.EMPTY_SET

    @classmethod
    def _tuning_loaded_callback(cls):
        super()._tuning_loaded_callback()
        cls._notification_bait_types = frozenset(
            cls.BAIT_NOTIFICATION_TEXT_MAP)

    def _build_outcome_sequence(self):
        def end(_):
            if self.global_outcome_result == interactions.utils.outcome_enums.OutcomeResult.SUCCESS:
                self._show_success_notification()

        sequence = super()._build_outcome_sequence()
        return element_utils.build_critical_section_with_finally(sequence, end)

    def _decide_localized_string(self):
        fishing_data = self.get_fishing_data_from_target()
        if fishing_data is not None:
            required_baits = set()
            resolver = self.get_resolver()
            for fish in fishing_data.get_possible_fish_gen():
                bait = fish.fish.cls.required_bait_buff
                if bait in self._notification_bait_types:
                    if fish.fish.cls.can_catch(resolver):
                        required_baits.add(bait)
            if required_baits:
                chosen_bait = random.choice(list(required_baits))
                loc_string = self.BAIT_NOTIFICATION_TEXT_MAP.get(chosen_bait)
                return loc_string(self.sim)
        return self.GENERIC_EXAMINE_NOTIFICATION_TEXT(self.sim)

    def _show_success_notification(self):
        dialog = self.EXAMINE_SUCCESS_NOTIFICATION(
            self.sim,
            self.get_resolver(),
            text=lambda *_: self._decide_localized_string())
        dialog.show_dialog()
Пример #5
0
 def setup_customer(self):
     if self._buffs_to_load is not None:
         sim_info = services.sim_info_manager().get(self._sim_id)
         if sim_info.is_instanced():
             buff_manager = services.buff_manager()
             for buff_id in self._buffs_to_load:
                 buff = buff_manager.get(buff_id)
                 if buff is not None:
                     sim_info.add_buff(buff)
             self._buffs_to_load = None
             self._post_sim_info_loaded_init()
Пример #6
0
class ChefTuning:
    CHEF_STATION_POT_OBJECT = TunablePackSafeReference(
        description=
        "\n        The pot object to create at the chef's station.\n        ",
        manager=services.definition_manager())
    CHEF_STATION_PAN_OBJECT = TunablePackSafeReference(
        description=
        "\n        The pan object to create at the chef's station.\n        ",
        manager=services.definition_manager())
    CHEF_STATION_CUTTING_BOARD_OBJECT = TunablePackSafeReference(
        description=
        "\n        The cutting board object to create at the chef's station.\n        ",
        manager=services.definition_manager())
    CHEF_STATION_PAN_SLOT = Tunable(
        description=
        '\n        The name of the slot in which the pan object should be placed.\n        ',
        tunable_type=str,
        default='_ctnm_SimInteraction_1')
    CHEF_STATION_POT_SLOT = Tunable(
        description=
        '\n        The name of the slot in which the pot object should be placed.\n        ',
        tunable_type=str,
        default='_ctnm_SimInteraction_2')
    CHEF_STATION_CUTTING_BOARD_SLOT = Tunable(
        description=
        '\n        The name of the slot in which the cutting board object should be placed.\n        ',
        tunable_type=str,
        default='_ctnm_SimInteraction_4')
    CHEF_STATION_SERVE_SLOT_TYPE = TunableReference(
        description=
        '\n        The slot type of the serve slots on the chef station.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.SLOT_TYPE))
    CHEF_STATION_SERVING_PLATTER_OBJECT = TunablePackSafeReference(
        description=
        "\n        The serving platter object the chef will create and place when they're\n        done cooking an order.\n        ",
        manager=services.definition_manager())
    CHEF_HAS_ORDER_BUFF = TunablePackSafeReference(
        description=
        '\n        The buff a chef should get when they have an order. This should drive\n        them to do the active cooking animations.\n        ',
        manager=services.buff_manager())
    CHEF_COMPLIMENT_LOOT = LootActions.TunablePackSafeReference(
        description=
        "\n        The loot action to trigger when a customer compliments a chef. This\n        won't happen until the waitstaff deliver the compliment.\n        \n        The customer Sim will be the Actor and the Chef will be TargetSim.\n        "
    )
    CHEF_INSULT_LOOT = LootActions.TunablePackSafeReference(
        description=
        "\n        The loot action to trigger when a customer insults a chef. This won't\n        happen until the waitstaff deliver the insult.\n        \n        The customer Sim will be the Actor and the Chef will be TargetSim.\n        "
    )
    PICK_UP_ORDER_INTERACTION = TunablePackSafeReference(
        description=
        '\n        The interaction the sim will run when they pick their order up from the\n        Chef Station. This is only used when a Sim places an order directly at\n        the chef station.\n        ',
        manager=services.get_instance_manager(
            sims4.resources.Types.INTERACTION))
Пример #7
0
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')
Пример #8
0
 def __init__(self, reload_dependent=False, **kwargs):
     super().__init__(
         buff_type=TunableReference(
             manager=services.buff_manager(),
             description='Buff that will get added to sim.',
             reload_dependent=reload_dependent),
         buff_reason=OptionalTunable(
             description=
             '\n                            If set, specify a reason why the buff was added.\n                            ',
             tunable=TunableLocalizedString(
                 description=
                 '\n                                The reason the buff was added. This will be displayed in the\n                                buff tooltip.\n                                '
             )),
         **kwargs)
Пример #9
0
class SicknessTuning:
    SICKNESS_BUFFS_PLAYER_FACED = TunableList(
        description=
        "\n        List of buffs that define if a sim is sick from what the player can \n        see.  The way sickness work, a sim might be sick but it may not be \n        visible to the player, so on this list we should only tune the buff's\n        that would make the sim sick on the players perspective.\n        i.e. buffs that would make a child sim take a day of school.\n        ",
        tunable=TunableReference(manager=services.buff_manager(),
                                 pack_safe=True))
    LOOT_ACTIONS_ON_CHILD_CAREER_AUTO_SICK = TunableList(
        description=
        '\n        Loot actions to test and apply on the event its time to go to work \n        and the child sim is sick.\n        i.e. notification...  \n        ',
        tunable=LootActions.TunableReference(pack_safe=True))

    @classmethod
    def is_child_sim_sick(cls, sim_info):
        if not sim_info.is_child:
            return False
        return any(
            sim_info.has_buff(buff_type)
            for buff_type in SicknessTuning.SICKNESS_BUFFS_PLAYER_FACED)
Пример #10
0
def archive_buff_message(buff_msg, shows_timeout, change_rate):
    buff_reason = hex(buff_msg.reason.hash) if buff_msg.HasField('reason') else None
    entry = {'buff_id': buff_msg.buff_id, 'equipped': buff_msg.equipped, 'buff_reason': buff_reason, 'is_mood_buff': buff_msg.is_mood_buff, 'commodity_guid': buff_msg.commodity_guid, 'transition_into_buff_id': buff_msg.transition_into_buff_id}
    manager = services.buff_manager()
    if manager:
        buff_cls = manager.get(buff_msg.buff_id)
        entry['buff_name'] = buff_cls.__name__
    if buff_msg.timeout:
        entry['timeout'] = buff_msg.timeout
        entry['rate'] = buff_msg.rate_multiplier
    if buff_msg.equipped and shows_timeout and change_rate is not None:
        if buff_msg.buff_progress == Sims_pb2.BUFF_PROGRESS_NONE:
            entry['progress_arrow'] = 'No Arrow'
        elif buff_msg.buff_progress == Sims_pb2.BUFF_PROGRESS_UP:
            entry['progress_arrow'] = 'Arrow Up'
        else:
            entry['progress_arrow'] = 'Arrow Down'
    if buff_msg.HasField('mood_type_override'):
        entry['mood_type_override'] = buff_msg.mood_type_override
    sim_buff_log_archiver.archive(data=entry, object_id=buff_msg.sim_id)
Пример #11
0
class RoyaltyTracker(SimInfoTracker):
    PAYMENT_SCHEDULE = WeeklySchedule.TunableFactory(
        description=
        '\n        The schedule for when payments should be made. This is global to all\n        sims that are receiving royalties..\n        '
    )
    ROYALTY_TYPE_DATA = TunableMapping(
        key_type=TunableEnumEntry(
            description='\n            The type of royalty.\n            ',
            tunable_type=RoyaltyType,
            default=RoyaltyType.INVALID,
            invalid_enums=(RoyaltyType.INVALID, )),
        value_type=TunableTuple(
            description=
            '\n            The data associated with the mapped royatly type.\n            ',
            royalty_name=TunableLocalizedString(
                description=
                '\n                The localized name of the RoyaltyType. This is how it will show up in the\n                Royalty notification to the player.\n                '
            ),
            one_time_bonus=OptionalTunable(
                description=
                '\n                If enabled, allows tuning a bonus to the next royalty payment if\n                the Sim has the corresponding buff.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Tuning that provides a bonus to a single royalty payment.\n                    ',
                    bonus_buff=TunablePackSafeReference(
                        description=
                        '\n                        If the sim has this buff when a royalty payment happens,\n                        that payment will be multiplied by the tuned Bonus\n                        Multiplier. This buff will then be removed from the Sim.\n                        ',
                        manager=services.buff_manager()),
                    bonus_multiplier=TunableRange(
                        description=
                        '\n                        The amount to multiply the next royalty payment by if the\n                        Sim has the tuned Bonus Buff.\n                        ',
                        tunable_type=float,
                        default=2,
                        minimum=0)))),
        verify_tunable_callback=_verify_tunable_callback)
    ROYALTY_ENTRY_ITEM = TunableLocalizedStringFactory(
        description=
        '\n        The localized string for a royalty entry.\n        {0.String}: {1.Money}\n        '
    )
    ROYALTY_NOTIFICATION = UiDialogNotification.TunableFactory(
        description=
        '\n        The notification displayed when royalties are viewed.\n        ',
        locked_args={'text': None})

    def __init__(self, sim_info):
        self._sim_ref = weakref.ref(sim_info)
        self._royalties = {}

    @property
    def sim_info(self):
        return self._sim_ref()

    @property
    def has_royalties(self):
        for (_, royalty_list) in self._royalties.items():
            if royalty_list:
                return True
        return False

    def clear_royalty_tracker(self):
        self._royalties.clear()

    def start_royalty(self,
                      royalty_type,
                      royalty_guid64,
                      entry_name,
                      multiplier,
                      starting_payment=0):
        if royalty_type not in self._royalties.keys():
            self._royalties[royalty_type] = []
        self._royalties[royalty_type].append(
            Royalty(royalty_guid64, entry_name, multiplier, starting_payment))

    def update_royalties_and_get_paid(self):
        if not self.has_royalties:
            return
        sim_info = self.sim_info
        if sim_info is None:
            logger.error(
                'Trying to pay out a Sim but the Sim is None. Perhaps they died? Clearing out royalties for this Sim. Sim: {}',
                sim_info)
            self._royalties.clear()
            return
        tag_payment_map = {}
        royalty_payment_dict = collections.defaultdict(list)
        for (royalty_type, royalty_list) in self._royalties.items():
            bonus_buff = None
            bonus_multiplier = None
            bonus_royalty = RoyaltyTracker.ROYALTY_TYPE_DATA.get(
                royalty_type).one_time_bonus
            if bonus_royalty is not None:
                bonus_buff = bonus_royalty.bonus_buff.buff_type
                if sim_info.Buffs.has_buff(bonus_buff):
                    bonus_multiplier = bonus_royalty.bonus_multiplier
            for royalty in reversed(royalty_list):
                royalty_tuning = RoyaltyPayment.get_royalty_payment_tuning(
                    royalty.royalty_guid64)
                if royalty_tuning is None:
                    logger.error(
                        'royalty_tuning is none for sim {}. royalty: {}.',
                        sim_info, royalty)
                elif royalty.update(royalty_tuning):
                    payment_tag = royalty_tuning.payment_tag
                    payment_amount = RoyaltyTracker.get_payment_amount(
                        royalty, royalty_tuning)
                    if bonus_multiplier is not None:
                        payment_amount *= bonus_multiplier
                    royalty_payment_dict[royalty] = payment_amount
                    if payment_tag not in tag_payment_map:
                        tag_payment_map[payment_tag] = 0
                    tag_payment_map[payment_tag] += payment_amount
                else:
                    royalty_list.remove(royalty)
            if bonus_multiplier is not None:
                sim_info.Buffs.remove_buff_by_type(bonus_buff)
        for (payment_tag, payment_amount) in tag_payment_map.items():
            tags = None
            if payment_tag != tag.Tag.INVALID:
                tags = frozenset((payment_tag, ))
            sim_info.household.funds.add(
                payment_amount,
                Consts_pb2.TELEMETRY_MONEY_ROYALTY,
                sim_info.get_sim_instance(
                    allow_hidden_flags=ALL_HIDDEN_REASONS),
                tags=tags)
        if royalty_payment_dict:
            self.show_royalty_notification(royalty_payment_dict)

    def show_royalty_notification(self, royalty_payment_dict):
        notification_text = LocalizationHelperTuning.get_new_line_separated_strings(
            *(LocalizationHelperTuning.get_bulleted_list(
                RoyaltyTracker.get_name_for_type(royalty_type),
                *(RoyaltyTracker.get_line_item_string(r.entry_name,
                                                      royalty_payment_dict[r])
                  for r in royalty_list))
              for (royalty_type, royalty_list) in self._royalties.items()
              if royalty_list))
        sim_info = self.sim_info
        resolver = SingleSimResolver(sim_info)
        dialog = self.ROYALTY_NOTIFICATION(sim_info,
                                           resolver,
                                           text=lambda *_: notification_text)
        dialog.show_dialog()

    @staticmethod
    def get_name_for_type(royalty_type):
        return RoyaltyTracker.ROYALTY_TYPE_DATA.get(royalty_type).royalty_name

    @staticmethod
    def get_payment_amount(royalty, royalty_tuning):
        deviation_percent = royalty_tuning.payment_deviation_percent
        payment_amount = royalty_tuning.pay_curve.get(
            royalty.current_payment) * royalty.multiplier
        if deviation_percent == 0:
            return int(payment_amount)
        deviation = payment_amount * deviation_percent
        min_payment = payment_amount - deviation
        max_payment = payment_amount + deviation
        return int(sims4.random.uniform(min_payment, max_payment))

    @staticmethod
    def get_line_item_string(name, amount):
        return RoyaltyTracker.ROYALTY_ENTRY_ITEM(name, amount)

    def save(self):
        data = protocols.PersistableRoyaltyTracker()
        for (royalty_type, royalty_list) in self._royalties.items():
            for royalty in royalty_list:
                with ProtocolBufferRollback(data.royalties) as royalty_data:
                    royalty_data.royalty_type = int(royalty_type)
                    royalty_data.royalty_guid64 = royalty.royalty_guid64
                    royalty_data.entry_name = royalty.entry_name
                    royalty_data.multiplier = royalty.multiplier
                    royalty_data.current_payment = royalty.current_payment
        return data

    def load(self, data):
        for royalty_data in data.royalties:
            entry_name = Localization_pb2.LocalizedString()
            entry_name.MergeFrom(royalty_data.entry_name)
            self.start_royalty(royalty_type=RoyaltyType(
                royalty_data.royalty_type),
                               royalty_guid64=royalty_data.royalty_guid64,
                               entry_name=entry_name,
                               multiplier=royalty_data.multiplier,
                               starting_payment=royalty_data.current_payment)

    @classproperty
    def _tracker_lod_threshold(cls):
        return SimInfoLODLevel.FULL

    def on_lod_update(self, old_lod, new_lod):
        if new_lod < self._tracker_lod_threshold:
            self.clear_royalty_tracker()
        elif old_lod < self._tracker_lod_threshold:
            sim_msg = services.get_persistence_service().get_sim_proto_buff(
                self._sim_ref().id)
            if sim_msg is not None:
                self.load(sim_msg.attributes.royalty_tracker)
class BuffCondition(HasTunableFactory, AutoFactoryInit, Condition):
    __qualname__ = 'BuffCondition'

    class Timing(enum.Int):
        __qualname__ = 'BuffCondition.Timing'
        ON_ADD = 0
        ON_REMOVE = 1
        HAS_BUFF = 2
        NOT_HAS_BUFF = 3

    FACTORY_TUNABLES = {'description': '\n            A condition that is satisfied when a Sim gains or loses a buff.\n            ', 'participant': TunableEnumEntry(description="\n            The participant whose buffs we're checking.\n            ", tunable_type=ParticipantTypeActorTargetSim, default=ParticipantTypeActorTargetSim.Actor), 'buff': TunableReference(description="\n            The buff we're checking.\n            ", manager=services.buff_manager()), 'timing': TunableEnumEntry(description='\n            When the condition satisfies.\n            Choices:\n            ON_ADD: Only check the condition on the edge of the buff being\n            added.  This will not satisfy if you have the buff when the\n            interaction starts.\n            ON_REMOVE: Only check the condition on the edge of the buff being\n            removed.  This will not satisfy if you do not have the buff when\n            the interaction starts.\n            HAS_BUFF: Check for the buff existing at any time this condition\n            is active.  This will satisfy if you have the buff when the\n            interaction starts.\n            NOT_HAS_BUFF: Check for the buff not existing at any time this\n            condition is active.  This will satisfy if you do not have the buff\n            when the interaction starts.\n            ', tunable_type=Timing, default=Timing.ON_ADD)}

    def __str__(self):
        return 'BuffCondition: {} {} {}'.format(self.participant, self.buff, self.timing)

    def attach_to_owner(self, owner, callback):
        self.si_callback = callback
        self._owner = owner
        sim = self._owner.get_participant(self.participant)
        if self.timing == BuffCondition.Timing.HAS_BUFF:
            if sim.has_buff(self.buff):
                self._satisfy()
        elif not (self.timing == BuffCondition.Timing.NOT_HAS_BUFF and sim.has_buff(self.buff)):
            self._satisfy()
        self._enable_buff_watcher()
        return (None, None)

    def detach_from_owner(self, owner, exiting=False):
        self._disable_buff_watcher()

    def _enable_buff_watcher(self):
        sim = self._owner.get_participant(self.participant)
        sim.Buffs.on_buff_added.append(self._on_buff_added)
        sim.Buffs.on_buff_removed.append(self._on_buff_removed)

    def _disable_buff_watcher(self):
        sim = self._owner.get_participant(self.participant)
        if self._on_buff_added in sim.Buffs.on_buff_added:
            sim.Buffs.on_buff_added.remove(self._on_buff_added)
        if self._on_buff_removed in sim.Buffs.on_buff_removed:
            sim.Buffs.on_buff_removed.remove(self._on_buff_removed)

    def _on_buff_added(self, buff_type):
        if buff_type is self.buff:
            if self.timing == BuffCondition.Timing.ON_ADD or self.timing == BuffCondition.Timing.HAS_BUFF:
                self._satisfy()
            else:
                self._unsatisfy()

    def _on_buff_removed(self, buff_type):
        if buff_type is self.buff:
            if self.timing == BuffCondition.Timing.ON_REMOVE or self.timing == BuffCondition.Timing.NOT_HAS_BUFF:
                self._satisfy()
            else:
                self._unsatisfy()
Пример #13
0
class RemoveBuffLiability(Liability, HasTunableFactory):
    LIABILITY_TOKEN = 'RemoveBuffLiability'
    FACTORY_TUNABLES = {'buff_to_remove': TunablePackSafeReference(description='\n            The buff to remove on the interaction finishing.\n            ', manager=services.buff_manager())}

    def __init__(self, interaction, buff_to_remove, **kwargs):
        super().__init__(**kwargs)
        self._sim_info = interaction.sim.sim_info
        self._buffs_to_remove = set()
        if buff_to_remove is not None:
            self._buffs_to_remove.add(buff_to_remove)

    def merge(self, interaction, key, new_liability):
        new_liability._buffs_to_remove.update(self._buffs_to_remove)
        return new_liability

    def release(self):
        for buff_type in self._buffs_to_remove:
            self._sim_info.remove_buff_by_type(buff_type)
Пример #14
0
class Region(HasTunableReference,
             metaclass=HashedTunedInstanceMetaclass,
             manager=services.region_manager()):
    REGION_DESCRIPTION_TUNING_MAP = TunableMapping(
        description=
        '\n        A mapping between Catalog region description and tuning instance. This\n        way we can find out what region description the current zone belongs to\n        at runtime then grab its tuning instance.\n        ',
        key_type=TunableRegionDescription(
            description=
            '\n            Catalog-side Region Description.\n            ',
            pack_safe=True,
            export_modes=ExportModes.All),
        value_type=TunableReference(
            description=
            "\n            Region Tuning instance. This is retrieved at runtime based on what\n            the active zone's region description is.\n            ",
            pack_safe=True,
            manager=services.region_manager(),
            export_modes=ExportModes.All),
        key_name='RegionDescription',
        value_name='Region',
        tuple_name='RegionDescriptionMappingTuple',
        export_modes=ExportModes.All)
    INSTANCE_TUNABLES = {
        'gallery_download_venue_map':
        TunableMapping(
            description=
            '\n            A map from gallery venue to instanced venue. We need to be able to\n            convert gallery venues into other venues that are only compatible\n            with that region.\n            ',
            key_type=TunableReference(
                description=
                '\n                A venue type that exists in the gallery.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            value_type=TunableReference(
                description=
                '\n                The venue type that the gallery venue will become when it is\n                downloaded into this region.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            key_name='gallery_venue_type',
            value_name='region_venue_type',
            tuple_name='GalleryDownloadVenueMappingTuple',
            export_modes=ExportModes.All),
        'compatible_venues':
        TunableList(
            description=
            '\n            A list of venues that are allowed to be set by the player in this\n            region.\n            ',
            tunable=TunableReference(
                description=
                '\n                A venue that the player can set in this region.\n                ',
                manager=services.venue_manager(),
                export_modes=ExportModes.All,
                pack_safe=True),
            export_modes=ExportModes.All),
        'tags':
        TunableList(
            description=
            '\n            Tags that are used to group regions. Destination Regions will\n            likely have individual tags, but Home/Residential Regions will\n            share a tag.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                A Tag used to group this region. Destination Regions will\n                likely have individual tags, but Home/Residential Regions will\n                share a tag.\n                ',
                tunable_type=tag.Tag,
                default=tag.Tag.INVALID,
                pack_safe=True)),
        'region_buffs':
        TunableList(
            description=
            '\n            A list of buffs that are added on Sims while they are instanced in\n            this region.\n            ',
            tunable=TunableReference(
                description=
                '\n                A buff that exists on Sims while they are instanced in this\n                region.\n                ',
                manager=services.buff_manager(),
                pack_safe=True)),
        'store_travel_group_placed_objects':
        Tunable(
            description=
            '\n            If checked, any placed objects while in a travel group will be returned to household inventory once\n            travel group is disbanded.\n            ',
            tunable_type=bool,
            default=False),
        'travel_group_build_disabled_tooltip':
        TunableLocalizedString(
            description=
            '\n            The string that will appear in the tooltip of the grayed out build\n            mode button if build is being disabled because of a travel group in\n            this region.\n            ',
            allow_none=True,
            export_modes=ExportModes.All),
        'sunrise_time':
        TunableTimeOfDay(
            description=
            '\n            The time, in Sim-time, the sun rises in this region.\n            ',
            default_hour=6,
            tuning_group=GroupNames.TIME),
        'seasonal_sunrise_time':
        TunableMapping(
            description=
            '\n            A mapping between season and sunrise time.  If the current season\n            is not found then we will default to the tuned sunrise time.\n            ',
            key_type=TunableEnumEntry(
                description='\n                The season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SUMMER),
            value_type=TunableTimeOfDay(
                description=
                '\n                The time, in Sim-time, the sun rises in this region, in this\n                season.\n                ',
                default_hour=6,
                tuning_group=GroupNames.TIME)),
        'sunset_time':
        TunableTimeOfDay(
            description=
            '\n            The time, in Sim-time, the sun sets in this region.\n            ',
            default_hour=20,
            tuning_group=GroupNames.TIME),
        'seasonal_sunset_time':
        TunableMapping(
            description=
            '\n            A mapping between season and sunset time.  If the current season\n            is not found then we will default to the tuned sunset time.\n            ',
            key_type=TunableEnumEntry(
                description='\n                The season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SUMMER),
            value_type=TunableTimeOfDay(
                description=
                '\n                The time, in Sim-time, the sun sets in this region, in this\n                season.\n                ',
                default_hour=20,
                tuning_group=GroupNames.TIME)),
        'provides_sunlight':
        Tunable(
            description=
            '\n            If enabled, this region provides sunlight between the tuned Sunrise\n            Time and Sunset Time. This is used for gameplay effect (i.e.\n            Vampires).\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.TIME),
        'weather':
        TunableMapping(
            description=
            '\n            Forecasts for this region for the various seasons\n            ',
            key_type=TunableEnumEntry(
                description='\n                The Season.\n                ',
                tunable_type=SeasonType,
                default=SeasonType.SPRING),
            value_type=TunableWeatherSeasonalForecastsReference(
                description=
                '\n                The forecasts for the season by part of season\n                ',
                pack_safe=True)),
        'weather_supports_fresh_snow':
        Tunable(
            description=
            '\n            If enabled, this region supports fresh snow.\n            ',
            tunable_type=bool,
            default=True),
        'seasonal_parameters':
        TunableMapping(
            description='\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The parameter that we wish to change.\n                ',
                tunable_type=SeasonParameters,
                default=SeasonParameters.LEAF_ACCUMULATION),
            value_type=TunableList(
                description=
                '\n                A list of the different seasonal parameter changes that we want to\n                send over the course of a year.\n                ',
                tunable=TunableTuple(
                    season=TunableEnumEntry(
                        description=
                        '\n                        The Season that this change is in.\n                        ',
                        tunable_type=SeasonType,
                        default=SeasonType.SPRING),
                    time_in_season=TunableRange(
                        description=
                        '\n                        The time within the season that this will occur.\n                        ',
                        tunable_type=float,
                        minimum=0.0,
                        maximum=1.0,
                        default=0.0),
                    value=Tunable(
                        description=
                        '\n                        The value that we will set this parameter to in the\n                        season\n                        ',
                        tunable_type=float,
                        default=0.0))),
            verify_tunable_callback=verify_seasonal_parameters),
        'fishing_data':
        OptionalTunable(
            description=
            '\n            If enabled, define all of the data for fishing locations in this region.\n            Only used if objects are tuned to use region fishing data.\n            ',
            tunable=TunableFishingDataSnippet()),
        'welcome_wagon_replacement':
        OptionalTunable(
            description=
            '\n            If enabled then we will replace the Welcome Wagon with a new situation.\n            \n            If the narrative is also set to replace the welcome wagon that will take precedent over this replacement.\n            ',
            tunable=TunableReference(
                description=
                '\n                The situation we will use to replace the welcome wagon.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SITUATION)))
    }

    @classmethod
    def _cls_repr(cls):
        return "Region: <class '{}.{}'>".format(cls.__module__, cls.__name__)

    @classmethod
    def is_region_compatible(cls, region_instance, ignore_tags=False):
        if region_instance is cls or region_instance is None:
            return True
        if ignore_tags:
            return False
        for tag in cls.tags:
            if tag in region_instance.tags:
                return True
        return False

    @classmethod
    def is_sim_info_compatible(cls, sim_info):
        other_region = get_region_instance_from_zone_id(sim_info.zone_id)
        if cls.is_region_compatible(other_region):
            return True
        else:
            travel_group_id = sim_info.travel_group_id
            if travel_group_id:
                travel_group = services.travel_group_manager().get(
                    travel_group_id)
                if travel_group is not None and not travel_group.played:
                    return True
        return False

    @classmethod
    def get_sunrise_time(cls):
        season_service = services.season_service()
        if season_service is None:
            return cls.sunrise_time
        return cls.seasonal_sunrise_time.get(season_service.season,
                                             cls.sunrise_time)

    @classmethod
    def get_sunset_time(cls):
        season_service = services.season_service()
        if season_service is None:
            return cls.sunset_time
        return cls.seasonal_sunset_time.get(season_service.season,
                                            cls.sunset_time)
Пример #15
0
class DynamicBuffLootOp(BaseLootOperation):
    __qualname__ = 'DynamicBuffLootOp'

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        if value.subject == ParticipantType.All:
            logger.error(
                'Tuning error: DynamicBuffLootOp should not be subject All.  Use AllSims:{}, {}',
                instance_class, tunable_name)
        elif value.subject == ParticipantType.PickedItemId:
            logger.error(
                'Tuning error: DynamicBuffLootOp should not be subject PickedItemId, use PickedSim:{}, {}',
                instance_class, tunable_name)

    FACTORY_TUNABLES = {
        'description':
        '\n        This loot will give a random buff based on the weight get tuned inside.\n        ',
        'buffs':
        TunableMapping(
            description='\n            ',
            key_type=TunableReference(
                description=
                '\n                Buff that will get this weight in the random.',
                manager=services.buff_manager()),
            value_type=Tunable(
                description='\n                The weight value.',
                tunable_type=float,
                default=0)),
        'buff_reason':
        OptionalTunable(
            description=
            '\n            If set, specify a reason why the buff was added.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The reason the buff was added. This will be displayed in the\n                buff tooltip.\n                '
            )),
        'verify_tunable_callback':
        _verify_tunable_callback
    }

    def __init__(self, buffs, buff_reason, **kwargs):
        super().__init__(**kwargs)
        self._buffs = buffs
        self._buff_reason = buff_reason
        self._random_buff = None

    def _get_random_buff(self):
        if self._random_buff is None:
            buff_pair_list = list(self._buffs.items())
            self._random_buff = sims4.random.pop_weighted(buff_pair_list,
                                                          flipped=True)
        return self._random_buff

    def _apply_to_subject_and_target(self, subject, target, resolver):
        random_buff = self._get_random_buff()
        if random_buff is not None:
            if not subject.is_sim:
                logger.error(
                    'Tuning error: subject {} of DynamicBuffLootOp giving buff {} for reason {} is not a sim',
                    self.subject, random_buff, self._buff_reason)
                return
            subject.add_buff_from_op(random_buff, self._buff_reason)

    def _on_apply_completed(self):
        random_buff = self._random_buff
        self._random_buff = None
        return random_buff
Пример #16
0
class CurfewService(Service):
    ALLOWED_CURFEW_TIMES = TunableList(
        description=
        '\n        A list of times (in military time) that are allowed to be set as curfew\n        times.\n        \n        NOTE: Many objects will have curfew components and will only support\n        a visual of certain values. Changing these values without making sure\n        the art supports the value will not work properly. Please only change\n        these values if you know for sure they need to be changed and are \n        getting support from modelling to make the change.\n        ',
        tunable=TunableRange(
            description=
            '\n            The hour for which the curfew will be set to.\n            ',
            tunable_type=int,
            default=0,
            minimum=0,
            maximum=23))
    CURFEW_END_TIME = TunableRange(
        description=
        '\n        The time when the curfew is considered to be over and the Sims are \n        no longer subject to it.\n        \n        This should probably be set to some time in the morning. 6am perhaps.\n        ',
        tunable_type=int,
        default=0,
        minimum=0,
        maximum=23)
    MINUTES_BEFORE_CURFEW_WARNING = TunableSimMinute(
        description=
        '\n        The minutes before the curfew starts that a Sim should receive a \n        warning about the curfew being about to start.\n        ',
        default=30)
    BREAK_CURFEW_WARNING = TunableLocalizedStringFactory(
        description=
        '\n        The string that is used to warn the player that a pie menu action will\n        cause the Sim to break curfew. This will wrap around the name of the \n        interaction so should be tuned to something like [Warning] {0.String}.\n        '
    )
    CURFEW_WARNING_TEXT_MESSAGE_DIALOG = UiDialogOk.TunableFactory(
        description=
        '\n        The dialog to display as a text message when warning a Sim that their\n        curfew is about to expire.\n        '
    )
    CURFEW_WARNING_SIM_TESTS = TunableTestSet(
        description=
        '\n        Tests to run on each of the Sims to determine if they should receive\n        the curfew warning text message or not.\n        '
    )
    BREAK_CURFEW_BUFF = TunablePackSafeReference(
        description=
        "\n        The buff that get's added to a Sim that breaks curfew. This buff will\n        enable the Sim to be disciplined for their behavior.\n        ",
        manager=services.buff_manager())
    INTERACTION_BLACKLIST_TAGS = TunableSet(
        description=
        '\n        A list of all the tags that blacklist interactions from causing Sims to\n        break curfew.\n        ',
        tunable=TunableEnumEntry(
            description=
            '\n            A tag that when tagged on the interaction will allow the Sim to run\n            the interaction and not break curfew.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            pack_safe=True))
    CURFEW_BEGIN_LOOT = TunablePackSafeReference(
        description=
        '\n        The loot to apply to all Sims in the family when curfew begins. This\n        will allow us to give buffs that affect the behavior of the Sims if\n        they pass certain tests.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.ACTION))
    CURFEW_END_LOOT = TunablePackSafeReference(
        description=
        '\n        The loot to apply to all Sims in the family when curfew ends. This will\n        allow us to remove buffs that affect the behavior of the Sims.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.ACTION))
    UNSET = -1

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._zone_curfew_data = {}
        self._curfew_warning_alarm_handle = None
        self._curfew_started_alarm_handle = None
        self._curfew_ended_alarm_handle = None
        self._curfew_message_alarm_handle = None
        self._curfew_warning_callback = CallableList()
        self._curfew_started_callback = CallableList()
        self._curfew_ended_callback = CallableList()
        self._time_set_callback = CallableList()

    def get_zone_curfew(self, zone_id):
        curfew_setting = self._zone_curfew_data.get(zone_id, self.UNSET)
        return curfew_setting

    def set_zone_curfew(self, zone_id, curfew_setting):
        if self._zone_curfew_data.get(zone_id, None) == curfew_setting:
            return
        if curfew_setting not in CurfewService.ALLOWED_CURFEW_TIMES and curfew_setting != CurfewService.UNSET:
            return
        self._zone_curfew_data[zone_id] = curfew_setting
        self._update_curfew_settings(zone_id, curfew_setting)
        self._setup_curfew_text_message()

    def _update_curfew_settings(self, current_zone_id, current_setting):
        self._create_alarm_handles(current_zone_id)
        self._time_set_callback(current_setting)

    def _create_alarm_handles(self, zone_id):
        for alarm in (self._curfew_warning_alarm_handle,
                      self._curfew_started_alarm_handle,
                      self._curfew_ended_alarm_handle):
            if alarm is not None:
                alarms.cancel_alarm(alarm)
        time = self._zone_curfew_data.get(zone_id, self.UNSET)
        now = services.time_service().sim_now
        self._create_warning_callback(now, time)
        self._create_curfew_callback(now, time)
        self._create_curfew_ended_callback(now, time)

    def _create_warning_callback(self, now, time):
        if time is not CurfewService.UNSET:
            alarm_time = date_and_time.create_date_and_time(hours=time - 1)
            warning_span = now.time_till_next_day_time(alarm_time)
            if warning_span.in_ticks() == 0:
                warning_span += TimeSpan(date_and_time.sim_ticks_per_day())
            self._curfew_warning_alarm_handle = alarms.add_alarm(
                self, warning_span, self._handle_warning_callback, False)

    def _handle_warning_callback(self, handle):
        self._curfew_warning_callback()
        now = services.time_service().sim_now
        time = self._zone_curfew_data.get(services.current_zone_id(),
                                          CurfewService.UNSET)
        self._create_warning_callback(now, time)

    def _create_curfew_callback(self, now, time):
        if time is not self.UNSET:
            alarm_time = date_and_time.create_date_and_time(hours=time)
            curfew_span = now.time_till_next_day_time(alarm_time)
            if curfew_span.in_ticks() == 0:
                curfew_span += TimeSpan(date_and_time.sim_ticks_per_day())
            self._curfew_started_alarm_handle = alarms.add_alarm(
                self, curfew_span, self._handle_curfew_callback, False)

    def _handle_curfew_callback(self, handle):
        self._curfew_started_callback()
        now = services.time_service().sim_now
        time = self._zone_curfew_data.get(services.current_zone_id(),
                                          CurfewService.UNSET)
        self.apply_curfew_loots()
        self._create_curfew_callback(now, time)

    def _create_curfew_ended_callback(self, now, time):
        if time is not CurfewService.UNSET:
            alarm_time = date_and_time.create_date_and_time(
                hours=CurfewService.CURFEW_END_TIME)
            curfew_span = now.time_till_next_day_time(alarm_time)
            if curfew_span.in_ticks() == 0:
                curfew_span += TimeSpan(date_and_time.sim_ticks_per_day())
            self._curfew_ended_alarm_handle = alarms.add_alarm(
                self, curfew_span, self._handle_curfew_ended_callback, False)

    def _handle_curfew_ended_callback(self, handle):
        self._curfew_ended_callback()
        now = services.time_service().sim_now
        time = CurfewService.CURFEW_END_TIME
        self.remove_curfew_loots()
        self._create_curfew_ended_callback(now, time)

    def register_for_alarm_callbacks(self, warning_callback, curfew_callback,
                                     curfew_over_callback, time_set_callback):
        self._curfew_warning_callback.append(warning_callback)
        self._curfew_started_callback.append(curfew_callback)
        self._curfew_ended_callback.append(curfew_over_callback)
        self._time_set_callback.append(time_set_callback)

    def unregister_for_alarm_callbacks(self, warning_callback, curfew_callback,
                                       curfew_over_callback,
                                       time_set_callback):
        if warning_callback in self._curfew_warning_callback:
            self._curfew_warning_callback.remove(warning_callback)
        if curfew_callback in self._curfew_started_callback:
            self._curfew_started_callback.remove(curfew_callback)
        if curfew_over_callback in self._curfew_ended_callback:
            self._curfew_ended_callback.remove(curfew_over_callback)
        if time_set_callback in self._time_set_callback:
            self._time_set_callback.remove(time_set_callback)

    def sim_breaking_curfew(self, sim, target, interaction=None):
        if interaction is not None and self.interaction_blacklisted(
                interaction):
            return False
        if sim.sim_info.is_in_travel_group():
            return False
        situation_manager = services.get_zone_situation_manager()
        sim_situations = situation_manager.get_situations_sim_is_in(sim)
        if any(situation.disallows_curfew_violation
               for situation in sim_situations):
            return False
        active_household = services.active_household()
        if active_household is None:
            return False
        home_zone_id = active_household.home_zone_id
        curfew_setting = self._zone_curfew_data.get(home_zone_id,
                                                    CurfewService.UNSET)
        if sim.sim_info not in active_household:
            return False
        if curfew_setting is not CurfewService.UNSET:
            if sim.sim_info.is_young_adult_or_older:
                return False
            elif self.past_curfew(curfew_setting):
                if not services.current_zone_id() == home_zone_id:
                    ensemble_service = services.ensemble_service()
                    ensemble = ensemble_service.get_visible_ensemble_for_sim(
                        sim)
                    if ensemble is not None and any(
                            sim.sim_info.is_young_adult_or_older
                            and sim.sim_info in active_household
                            for sim in ensemble):
                        return False
                    return True
                if target is not None and not target.is_in_inventory(
                ) and not services.active_lot().is_position_on_lot(
                        target.position):
                    return True
                elif target is None and not services.active_lot(
                ).is_position_on_lot(sim.position):
                    return True
            return True
            if target is not None and not target.is_in_inventory(
            ) and not services.active_lot().is_position_on_lot(
                    target.position):
                return True
            elif target is None and not services.active_lot(
            ).is_position_on_lot(sim.position):
                return True
        return False

    def interaction_blacklisted(self, interaction):
        interaction_tags = interaction.get_category_tags()
        for tag in CurfewService.INTERACTION_BLACKLIST_TAGS:
            if tag in interaction_tags:
                return True
        return False

    def past_curfew(self, curfew_setting):
        now = services.time_service().sim_now
        if now.hour() >= curfew_setting or now.hour(
        ) < CurfewService.CURFEW_END_TIME:
            return True
        return False

    def _setup_curfew_text_message(self):
        if self._curfew_message_alarm_handle is not None:
            self._curfew_message_alarm_handle.cancel()
            self._curfew_message_alarm_handle = None
        current_household = services.active_household()
        if current_household is None:
            return
        home_zone_id = current_household.home_zone_id
        curfew_setting = self._zone_curfew_data.get(home_zone_id,
                                                    CurfewService.UNSET)
        if curfew_setting is CurfewService.UNSET:
            return
        now = services.time_service().sim_now
        alarm_time = date_and_time.create_date_and_time(hours=curfew_setting)
        time_till_alarm = now.time_till_next_day_time(alarm_time)
        span = date_and_time.create_time_span(
            minutes=CurfewService.MINUTES_BEFORE_CURFEW_WARNING)
        time_till_alarm -= span
        self._curfew_message_alarm_handle = alarms.add_alarm(
            self, time_till_alarm, self._handle_curfew_message_callback, False)

    def _handle_curfew_message_callback(self, handle):
        active_lot = services.active_lot()
        if active_lot.lot_id != services.active_household_lot_id():
            from_sim = None
            for sim_info in services.active_household():
                if sim_info.is_young_adult_or_older:
                    if not sim_info.is_instanced():
                        from_sim = sim_info
                        break
            if from_sim is None:
                return
            for sim_info in services.active_household():
                if sim_info.get_sim_instance() is None:
                    continue
                resolver = DoubleSimResolver(sim_info, from_sim)
                if not CurfewService.CURFEW_WARNING_SIM_TESTS.run_tests(
                        resolver):
                    continue
                dialog = self.CURFEW_WARNING_TEXT_MESSAGE_DIALOG(
                    sim_info, target_sim_id=from_sim.id, resolver=resolver)
                dialog.show_dialog()

    def add_broke_curfew_buff(self, sim):
        if not sim.has_buff(CurfewService.BREAK_CURFEW_BUFF):
            sim.add_buff(CurfewService.BREAK_CURFEW_BUFF)

    def remove_broke_curfew_buff(self, sim):
        if sim.has_buff(CurfewService.BREAK_CURFEW_BUFF):
            sim.remove_buff_by_type(CurfewService.BREAK_CURFEW_BUFF)

    def is_curfew_active_on_lot_id(self, lot_id):
        curfew_setting = self._zone_curfew_data.get(lot_id,
                                                    CurfewService.UNSET)
        if curfew_setting == CurfewService.UNSET:
            return False
        return self.past_curfew(curfew_setting)

    def apply_curfew_loots(self):
        for sim_info in services.active_household():
            resolver = SingleSimResolver(sim_info)
            CurfewService.CURFEW_BEGIN_LOOT.apply_to_resolver(resolver)

    def remove_curfew_loots(self):
        for sim_info in services.active_household():
            resolver = SingleSimResolver(sim_info)
            CurfewService.CURFEW_END_LOOT.apply_to_resolver(resolver)

    @classproperty
    def save_error_code(cls):
        return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_CURFEW_SERVICE

    def save(self,
             object_list=None,
             zone_data=None,
             open_street_data=None,
             store_travel_group_placed_objects=False,
             save_slot_data=None):
        persistence_service = services.get_persistence_service()
        for save_zone_data in persistence_service.zone_proto_buffs_gen():
            setting = self._zone_curfew_data.get(save_zone_data.zone_id,
                                                 CurfewService.UNSET)
            save_zone_data.gameplay_zone_data.curfew_setting = setting

    def load(self, zone_data=None):
        persistence_service = services.get_persistence_service()
        for zone_data in persistence_service.zone_proto_buffs_gen():
            self._zone_curfew_data[
                zone_data.
                zone_id] = zone_data.gameplay_zone_data.curfew_setting

    def on_zone_load(self):
        current_zone_id = services.current_zone_id()
        self._setup_curfew_text_message()
        self._create_alarm_handles(current_zone_id)
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        current_venue_tuning = venue_manager.get(
            build_buy.get_current_venue(current_zone_id))
        if current_venue_tuning.is_residential or current_venue_tuning.is_university_housing:
            current_setting = self._zone_curfew_data.get(
                current_zone_id, CurfewService.UNSET)
            self._update_curfew_settings(current_zone_id, current_setting)
        else:
            self._update_curfew_settings(current_zone_id, CurfewService.UNSET)
Пример #17
0
class Trait(HasTunableReference,
            SuperAffordanceProviderMixin,
            TargetSuperAffordanceProviderMixin,
            HasTunableLodMixin,
            MixerActorMixin,
            MixerProviderMixin,
            metaclass=HashedTunedInstanceMetaclass,
            manager=services.trait_manager()):
    EQUIP_SLOT_NUMBER_MAP = TunableMapping(
        description=
        '\n        The number of personality traits available to Sims of specific ages.\n        ',
        key_type=TunableEnumEntry(
            description="\n            The Sim's age.\n            ",
            tunable_type=sim_info_types.Age,
            default=sim_info_types.Age.YOUNGADULT),
        value_type=Tunable(
            description=
            '\n            The number of personality traits available to a Sim of the specified\n            age.\n            ',
            tunable_type=int,
            default=3),
        key_name='Age',
        value_name='Slot Number')
    PERSONALITY_TRAIT_TAG = TunableEnumEntry(
        description=
        '\n        The tag that marks a trait as a personality trait.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        invalid_enums=(tag.Tag.INVALID, ))
    DAY_NIGHT_TRACKING_BUFF_TAG = TunableEnumWithFilter(
        description=
        '\n        The tag that marks buffs as opting in to Day Night Tracking on traits..\n        ',
        tunable_type=tag.Tag,
        filter_prefixes=['buff'],
        default=tag.Tag.INVALID,
        invalid_enums=(tag.Tag.INVALID, ))
    INSTANCE_TUNABLES = {
        'trait_type':
        TunableEnumEntry(
            description='\n            The type of the trait.\n            ',
            tunable_type=TraitType,
            default=TraitType.PERSONALITY,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.APPEARANCE),
        'display_name':
        TunableLocalizedStringFactory(
            description=
            "\n            The trait's display name. This string is provided with the owning\n            Sim as its only token.\n            ",
            allow_none=True,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.APPEARANCE),
        'display_name_gender_neutral':
        TunableLocalizedString(
            description=
            "\n            The trait's gender-neutral display name. This string is not provided\n            any tokens, and thus can't rely on context to properly form\n            masculine and feminine forms.\n            ",
            allow_none=True,
            tuning_group=GroupNames.APPEARANCE),
        'trait_description':
        TunableLocalizedStringFactory(
            description="\n            The trait's description.\n            ",
            allow_none=True,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.APPEARANCE),
        'trait_origin_description':
        TunableLocalizedString(
            description=
            "\n            A description of how the Sim obtained this trait. Can be overloaded\n            for other uses in certain cases:\n            - When the trait type is AGENT this string is the name of the \n                agency's Trade type and will be provided with the owning sim \n                as its token.\n            - When the trait type is HIDDEN and the trait is used by the CAS\n                STORIES flow, this can be used as a secondary description in \n                the CAS Stories UI. If this trait is tagged as a CAREER CAS \n                stories trait, this description will be used to explain which \n                skills are also granted with this career.\n            ",
            allow_none=True,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.APPEARANCE),
        'icon':
        TunableResourceKey(
            description="\n            The trait's icon.\n            ",
            allow_none=True,
            resource_types=CompoundTypes.IMAGE,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.APPEARANCE),
        'pie_menu_icon':
        TunableResourceKey(
            description=
            "\n            The trait's pie menu icon.\n            ",
            resource_types=CompoundTypes.IMAGE,
            default=None,
            allow_none=True,
            tuning_group=GroupNames.APPEARANCE),
        'trait_asm_overrides':
        TunableTuple(
            description=
            '\n            Tunables that will specify if a Trait will add any parameters\n            to the Sim and how it will affect their boundary conditions.\n            ',
            param_type=OptionalTunable(
                description=
                '\n                Define if this trait is parameterized as an on/off value or as\n                part of an enumeration.\n                ',
                tunable=Tunable(
                    description=
                    '\n                    The name of the parameter enumeration. For example, if this\n                    value is tailType, then the tailType actor parameter is set\n                    to the value specified in param_value, for this Sim.\n                    ',
                    tunable_type=str,
                    default=None),
                disabled_name='boolean',
                enabled_name='enum'),
            trait_asm_param=Tunable(
                description=
                "\n                The ASM parameter for this trait. If unset, it will be auto-\n                generated depending on the instance name (e.g. 'trait_Clumsy').\n                ",
                tunable_type=str,
                default=None),
            consider_for_boundary_conditions=Tunable(
                description=
                '\n                If enabled the trait_asm_param will be considered when a Sim\n                is building the goals and validating against its boundary\n                conditions.\n                This should ONLY be enabled, if we need this parameter for\n                cases like a posture transition, or boundary specific cases. \n                On regular cases like an animation outcome, this is not needed.\n                i.e. Vampire trait has an isVampire parameter set to True, so\n                when animatin out of the coffin it does different get in/out \n                animations.  When this is enabled, isVampire will be set to \n                False for every other Sim.\n                ',
                tunable_type=bool,
                default=False),
            tuning_group=GroupNames.ANIMATION),
        'ages':
        TunableSet(
            description=
            '\n            The allowed ages for this trait. If no ages are specified, then all\n            ages are considered valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=Age,
                                     default=None,
                                     export_modes=ExportModes.All),
            tuning_group=GroupNames.AVAILABILITY),
        'genders':
        TunableSet(
            description=
            '\n            The allowed genders for this trait. If no genders are specified,\n            then all genders are considered valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=Gender,
                                     default=None,
                                     export_modes=ExportModes.All),
            tuning_group=GroupNames.AVAILABILITY),
        'species':
        TunableSet(
            description=
            '\n            The allowed species for this trait. If not species are specified,\n            then all species are considered valid.\n            ',
            tunable=TunableEnumEntry(tunable_type=Species,
                                     default=Species.HUMAN,
                                     invalid_enums=(Species.INVALID, ),
                                     export_modes=ExportModes.All),
            tuning_group=GroupNames.AVAILABILITY),
        'conflicting_traits':
        TunableList(
            description=
            '\n            Conflicting traits for this trait. If the Sim has any of the\n            specified traits, then they are not allowed to be equipped with this\n            one.\n            \n            e.g.\n             Family Oriented conflicts with Hates Children, and vice-versa.\n            ',
            tunable=TunableReference(manager=services.trait_manager(),
                                     pack_safe=True),
            export_modes=ExportModes.All,
            tuning_group=GroupNames.AVAILABILITY),
        'is_npc_only':
        Tunable(
            description=
            '\n            If checked, this trait will get removed from Sims that have a home\n            when the zone is loaded or whenever they switch to a household that\n            has a home zone.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.AVAILABILITY),
        'cas_selected_icon':
        TunableResourceKey(
            description=
            '\n            Icon to be displayed in CAS when this trait has already been applied\n            to a Sim.\n            ',
            resource_types=CompoundTypes.IMAGE,
            default=None,
            allow_none=True,
            export_modes=(ExportModes.ClientBinary, ),
            tuning_group=GroupNames.CAS),
        'cas_idle_asm_key':
        TunableInteractionAsmResourceKey(
            description=
            '\n            The ASM to use for the CAS idle.\n            ',
            default=None,
            allow_none=True,
            category='asm',
            export_modes=ExportModes.All,
            tuning_group=GroupNames.CAS),
        'cas_idle_asm_state':
        Tunable(
            description=
            '\n            The state to play for the CAS idle.\n            ',
            tunable_type=str,
            default=None,
            source_location='cas_idle_asm_key',
            source_query=SourceQueries.ASMState,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.CAS),
        'cas_trait_asm_param':
        Tunable(
            description=
            '\n            The ASM parameter for this trait for use with CAS ASM state machine,\n            driven by selection of this Trait, i.e. when a player selects the a\n            romantic trait, the Flirty ASM is given to the state machine to\n            play. The name tuned here must match the animation state name\n            parameter expected in Swing.\n            ',
            tunable_type=str,
            default=None,
            export_modes=ExportModes.All,
            tuning_group=GroupNames.CAS),
        'tags':
        TunableList(
            description=
            "\n            The associated categories of the trait. Need to distinguish among\n            'Personality Traits', 'Achievement Traits' and 'Walkstyle\n            Traits'.\n            ",
            tunable=TunableEnumEntry(tunable_type=tag.Tag,
                                     default=tag.Tag.INVALID),
            export_modes=ExportModes.All,
            tuning_group=GroupNames.CAS),
        'sim_info_fixup_actions':
        TunableList(
            description=
            '\n            A list of fixup actions which will be performed on a sim_info with\n            this trait when it is loaded.\n            ',
            tunable=TunableVariant(
                career_fixup_action=_SimInfoCareerFixupAction.TunableFactory(
                    description=
                    '\n                    A fix up action to set a career with a specific level.\n                    '
                ),
                skill_fixup_action=_SimInfoSkillFixupAction.TunableFactory(
                    description=
                    '\n                    A fix up action to set a skill with a specific level.\n                    '
                ),
                unlock_fixup_action=_SimInfoUnlockFixupAction.TunableFactory(
                    description=
                    '\n                    A fix up action to unlock certain things for a Sim\n                    '
                ),
                perk_fixup_action=_SimInfoPerkFixupAction.TunableFactory(
                    description=
                    '\n                    A fix up action to grant perks to a Sim. It checks perk required\n                    unlock tuning and unlocks prerequisite perks first.\n                    '
                ),
                default='career_fixup_action'),
            tuning_group=GroupNames.CAS),
        'sim_info_fixup_actions_timing':
        TunableEnumEntry(
            description=
            "\n            This is DEPRECATED, don't tune this field. We usually don't do trait-based\n            fixup unless it's related to CAS stories. We keep this field only for legacy\n            support reason.\n            \n            This is mostly to optimize performance when applying fix-ups to\n            a Sim.  We ideally would not like to spend time scanning every Sim \n            on every load to see if they need fixups.  Please be sure you \n            consult a GPE whenever you are creating fixup tuning.\n            ",
            tunable_type=SimInfoFixupActionTiming,
            default=SimInfoFixupActionTiming.ON_FIRST_SIMINFO_LOAD,
            tuning_group=GroupNames.DEPRECATED,
            deprecated=True),
        'teleport_style_interaction_to_inject':
        TunableReference(
            description=
            '\n             When this trait is added to a Sim, if a teleport style interaction\n             is specified, any time another interaction runs, we may run this\n             teleport style interaction to shorten or replace the route to the \n             target.\n             ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            class_restrictions=('TeleportStyleSuperInteraction', ),
            allow_none=True,
            tuning_group=GroupNames.SPECIAL_CASES),
        'interactions':
        OptionalTunable(
            description=
            '\n            Mixer interactions that are available to Sims equipped with this\n            trait.\n            ',
            tunable=ContentSet.TunableFactory(locked_args={
                'phase_affordances': frozendict(),
                'phase_tuning': None
            })),
        'buffs_add_on_spawn_only':
        Tunable(
            description=
            '\n            If unchecked, buffs are added to the Sim as soon as this trait is\n            added. If checked, buffs will be added only when the Sim is\n            instantiated and removed when the Sim uninstantiates.\n            \n            General guidelines: If the buffs only matter to Sims, for example\n            buffs that alter autonomy behavior or walkstyle, this should be\n            checked.\n            ',
            tunable_type=bool,
            default=True),
        'buffs':
        TunableList(
            description=
            '\n            Buffs that should be added to the Sim whenever this trait is\n            equipped.\n            ',
            tunable=TunableBuffReference(pack_safe=True),
            unique_entries=True),
        'buffs_proximity':
        TunableList(
            description=
            '\n            Proximity buffs that are active when this trait is equipped.\n            ',
            tunable=TunableReference(manager=services.buff_manager())),
        'buff_replacements':
        TunableMapping(
            description=
            '\n            A mapping of buff replacement. If Sim has this trait on, whenever he\n            get the buff tuned in the key of the mapping, it will get replaced\n            by the value of the mapping.\n            ',
            key_type=TunableReference(
                description=
                '\n                Buff that will get replaced to apply on Sim by this trait.\n                ',
                manager=services.buff_manager(),
                reload_dependent=True,
                pack_safe=True),
            value_type=TunableTuple(
                description=
                '\n                Data specific to this buff replacement.\n                ',
                buff_type=TunableReference(
                    description=
                    '\n                    Buff used to replace the buff tuned as key.\n                    ',
                    manager=services.buff_manager(),
                    reload_dependent=True,
                    pack_safe=True),
                buff_reason=OptionalTunable(
                    description=
                    '\n                    If enabled, override the buff reason.\n                    ',
                    tunable=TunableLocalizedString(
                        description=
                        '\n                        The overridden buff reason.\n                        '
                    )),
                buff_replacement_priority=TunableEnumEntry(
                    description=
                    "\n                    The priority of this buff replacement, relative to other\n                    replacements. Tune this to be a higher value if you want\n                    this replacement to take precedence.\n                    \n                    e.g.\n                     (NORMAL) trait_HatesChildren (buff_FirstTrimester -> \n                                                   buff_FirstTrimester_HatesChildren)\n                     (HIGH)   trait_Male (buff_FirstTrimester -> \n                                          buff_FirstTrimester_Male)\n                                          \n                     In this case, both traits have overrides on the pregnancy\n                     buffs. However, we don't want males impregnated by aliens\n                     that happen to hate children to lose their alien-specific\n                     buffs. Therefore we tune the male replacement at a higher\n                     priority.\n                    ",
                    tunable_type=TraitBuffReplacementPriority,
                    default=TraitBuffReplacementPriority.NORMAL))),
        'excluded_mood_types':
        TunableList(
            TunableReference(
                description=
                '\n            List of moods that are prevented by having this trait.\n            ',
                manager=services.mood_manager())),
        'outfit_replacements':
        TunableMapping(
            description=
            "\n            A mapping of outfit replacements. If the Sim has this trait, outfit\n            change requests are intercepted to produce the tuned result. If\n            multiple traits with outfit replacements exist, the behavior is\n            undefined.\n            \n            Tuning 'Invalid' as a key acts as a fallback and applies to all\n            reasons.\n            \n            Tuning 'Invalid' as a value keeps a Sim in their current outfit.\n            ",
            key_type=TunableEnumEntry(tunable_type=OutfitChangeReason,
                                      default=OutfitChangeReason.Invalid),
            value_type=TunableEnumEntry(tunable_type=OutfitChangeReason,
                                        default=OutfitChangeReason.Invalid)),
        'disable_aging':
        OptionalTunable(
            description=
            '\n            If enabled, aging out of specific ages can be disabled.\n            ',
            tunable=TunableTuple(
                description=
                '\n                The tuning that disables aging out of specific age groups.\n                ',
                allowed_ages=TunableSet(
                    description=
                    '\n                    A list of ages that the Sim CAN age out of. If an age is in\n                    this list then the Sim is allowed to age out of it. If an\n                    age is not in this list than a Sim is not allowed to age out\n                    of it. For example, if the list only contains Child and\n                    Teen, then a Child Sim would be able to age up to Teen and\n                    a Teen Sim would be able to age up to Young Adult. But, a\n                    Young Adult, Adult, or Elder Sim would not be able to age\n                    up.\n                    ',
                    tunable=TunableEnumEntry(Age, default=Age.ADULT)),
                tooltip=OptionalTunable(
                    description=
                    '\n                    When enabled, this tooltip will be displayed in the aging\n                    progress bar when aging is disabled because of the trait.\n                    ',
                    tunable=TunableLocalizedStringFactory(
                        description=
                        '\n                        The string that displays in the aging UI when aging up\n                        is disabled due to the trait.\n                        '
                    ))),
            tuning_group=GroupNames.SPECIAL_CASES),
        'can_die':
        Tunable(
            description=
            '\n            When set, Sims with this trait are allowed to die. When unset, Sims\n            are prevented from dying.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.SPECIAL_CASES),
        'culling_behavior':
        TunableVariant(
            description=
            '\n            The culling behavior of a Sim with this trait.\n            ',
            default_behavior=CullingBehaviorDefault.TunableFactory(),
            immune_to_culling=CullingBehaviorImmune.TunableFactory(),
            importance_as_npc_score=CullingBehaviorImportanceAsNpc.
            TunableFactory(),
            default='default_behavior',
            tuning_group=GroupNames.SPECIAL_CASES),
        'always_send_test_event_on_add':
        Tunable(
            description=
            '\n            If checked, will send out a test event when added to a trait\n            tracker even if the receiving sim is hidden or not instanced.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.SPECIAL_CASES),
        'voice_effect':
        OptionalTunable(
            description=
            '\n            The voice effect of a Sim with this trait. This is prioritized\n            against other traits with voice effects.\n            \n            The Sim may only have one voice effect at a time.\n            ',
            tunable=VoiceEffectRequest.TunableFactory()),
        'plumbbob_override':
        OptionalTunable(
            description=
            '\n            If enabled, allows a new plumbbob model to be used when a Sim has\n            this occult type.\n            ',
            tunable=PlumbbobOverrideRequest.TunableFactory()),
        'vfx_mask':
        OptionalTunable(
            description=
            '\n            If enabled when this trait is added the masks will be applied to\n            the Sim affecting the visibility of specific VFX.\n            Example: TRAIT_CHILDREN will provide a mask MASK_CHILDREN which \n            the monster battle object will only display VFX for any Sim \n            using that mask.\n            ',
            tunable=TunableEnumFlags(
                description=
                "\n                Mask that will be added to the Sim's mask when the trait is\n                added.\n                ",
                enum_type=VFXMask),
            enabled_name='apply_vfx_mask',
            disabled_name='no_vfx_mask'),
        'day_night_tracking':
        OptionalTunable(
            description=
            "\n            If enabled, allows this trait to track various aspects of day and\n            night via buffs on the owning Sim.\n            \n            For example, if this is enabled and the Sunlight Buff is tuned with\n            buffs, the Sim will get the buffs added every time they're in\n            sunlight and removed when they're no longer in sunlight.\n            ",
            tunable=DayNightTracking.TunableFactory()),
        'persistable':
        Tunable(
            description=
            '\n            If checked then this trait will be saved onto the sim.  If\n            unchecked then the trait will not be saved.\n            Example unchecking:\n            Traits that are applied for the sim being in the region.\n            ',
            tunable_type=bool,
            default=True),
        'initial_commodities':
        TunableSet(
            description=
            '\n            A list of commodities that will be added to a sim on load, if the\n            sim has this trait.\n            \n            If a given commodity is also blacklisted by another trait that the\n            sim also has, it will NOT be added.\n            \n            Example:\n            Adult Age Trait adds Hunger.\n            Vampire Trait blacklists Hunger.\n            Hunger will not be added.\n            ',
            tunable=Commodity.TunableReference(pack_safe=True)),
        'initial_commodities_blacklist':
        TunableSet(
            description=
            "\n            A list of commodities that will be prevented from being\n            added to a sim that has this trait.\n            \n            This always takes priority over any commodities listed in any\n            trait's initial_commodities.\n            \n            Example:\n            Adult Age Trait adds Hunger.\n            Vampire Trait blacklists Hunger.\n            Hunger will not be added.\n            ",
            tunable=Commodity.TunableReference(pack_safe=True)),
        'ui_commodity_sort_override':
        OptionalTunable(
            description=
            '\n            Optional list of commodities to override the default UI sort order.\n            ',
            tunable=TunableList(
                description=
                '\n                The position of the commodity in this list represents the sort order.\n                Add all possible combination of traits in the list.\n                If we have two traits which have sort override, we will implement\n                a priority system to determine which determines which trait sort\n                order to use.\n                ',
                tunable=Commodity.TunableReference())),
        'ui_category':
        OptionalTunable(
            description=
            '\n            If enabled then this trait will be displayed in a specific category\n            within the relationship panel if this trait would be displayed\n            within that panel.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The UI trait category that we use to categorize this trait\n                within the relationship panel.\n                ',
                tunable_type=TraitUICategory,
                default=TraitUICategory.PERSONALITY),
            export_modes=ExportModes.All,
            enabled_name='ui_trait_category_tag'),
        'loot_on_trait_add':
        OptionalTunable(
            description=
            '\n            If tuned, this list of loots will be applied when trait is added in game.\n            ',
            tunable=TunableList(
                description=
                '\n                List of loot to apply on the sim when this trait is added not\n                through CAS.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    Loot to apply.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.ACTION),
                    pack_safe=True))),
        'npc_leave_lot_interactions':
        OptionalTunable(
            description=
            '\n            If enabled, allows tuning a set of Leave Lot and Leave Lot Must Run\n            interactions that this trait provides. NPC Sims with this trait will\n            use these interactions to leave the lot instead of the defaults.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Leave Lot Now and Leave Lot Now Must Run interactions.\n                ',
                leave_lot_now_interactions=TunableSet(
                    TunableReference(
                        description=
                        '\n                    If tuned, the Sim will consider these interaction when trying to run\n                    any "leave lot" situation.\n                    ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.INTERACTION),
                        allow_none=False,
                        pack_safe=True)),
                leave_lot_now_must_run_interactions=TunableSet(
                    TunableReference(
                        description=
                        '\n                    If tuned, the Sim will consider these interaction when trying to run\n                    any "leave lot must run" situation.\n                    ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.INTERACTION),
                        allow_none=False,
                        pack_safe=True)))),
        'hide_relationships':
        Tunable(
            description=
            '\n            If checked, then any relationships with a Sim who has this trait\n            will not be displayed in the UI. This is done by keeping the\n            relationship from having any tracks to actually track which keeps\n            it out of the UI.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.RELATIONSHIP),
        'whim_set':
        OptionalTunable(
            description=
            '\n            If enabled then this trait will offer a whim set to the Sim when it\n            is active.\n            ',
            tunable=TunableReference(
                description=
                '\n                A whim set that is active when this trait is active.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.ASPIRATION),
                class_restrictions=('ObjectivelessWhimSet', ))),
        'allow_from_gallery':
        Tunable(
            description=
            '\n            If checked, then this trait is allowed to be transferred over from\n            Sims downloaded from the gallery.\n            ',
            tunable_type=bool,
            default=True,
            tuning_group=GroupNames.SPECIAL_CASES),
        'remove_on_death':
        Tunable(
            description=
            '\n            If checked, when a Sim dies this trait will be removed.\n            ',
            tunable_type=bool,
            default=False,
            tuning_group=GroupNames.SPECIAL_CASES),
        'build_buy_purchase_tracking':
        OptionalTunable(
            description=
            '\n            If enabled, allows this trait to track various build-buy purchases\n            via event listening in the trait tracker.\n            ',
            tunable=TunableList(
                description=
                '\n                Loots to apply to the hamper when clothing pile is being put.\n                ',
                tunable=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.ACTION),
                                         class_restrictions=('LootActions', ),
                                         pack_safe=True)))
    }
    _asm_param_name = None
    default_trait_params = set()

    def __repr__(self):
        return '<Trait:({})>'.format(self.__name__)

    def __str__(self):
        return '{}'.format(self.__name__)

    @classmethod
    def _tuning_loaded_callback(cls):
        cls._asm_param_name = cls.trait_asm_overrides.trait_asm_param
        if cls._asm_param_name is None:
            cls._asm_param_name = cls.__name__
        if cls.trait_asm_overrides.trait_asm_param is not None and cls.trait_asm_overrides.consider_for_boundary_conditions:
            cls.default_trait_params.add(
                cls.trait_asm_overrides.trait_asm_param)
        for (buff, replacement_buff) in cls.buff_replacements.items():
            if buff.trait_replacement_buffs is None:
                buff.trait_replacement_buffs = {}
            buff.trait_replacement_buffs[cls] = replacement_buff
        for mood in cls.excluded_mood_types:
            if mood.excluding_traits is None:
                mood.excluding_traits = []
            mood.excluding_traits.append(cls)

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.display_name:
            if not cls.display_name_gender_neutral.hash:
                logger.error(
                    'Trait {} specifies a display name. It must also specify a gender-neutral display name. These must use different string keys.',
                    cls,
                    owner='BadTuning')
            if cls.display_name._string_id == cls.display_name_gender_neutral.hash:
                logger.error(
                    'Trait {} has the same string tuned for its display name and its gender-neutral display name. These must be different strings for localization.',
                    cls,
                    owner='BadTuning')
        if cls.day_night_tracking is not None:
            if not cls.day_night_tracking.sunlight_buffs and not (
                    not cls.day_night_tracking.shade_buffs
                    and not (not cls.day_night_tracking.day_buffs
                             and not cls.day_night_tracking.night_buffs)):
                logger.error(
                    'Trait {} has Day Night Tracking enabled but no buffs are tuned. Either tune buffs or disable the tracking.',
                    cls,
                    owner='BadTuning')
            else:
                tracking_buff_tag = Trait.DAY_NIGHT_TRACKING_BUFF_TAG
                if any(
                        buff for buff in cls.day_night_tracking.sunlight_buffs
                        if not buff.buff_type.has_tag(tracking_buff_tag)
                ) or (any(buff for buff in cls.day_night_tracking.shade_buffs
                          if not buff.buff_type.has_tag(tracking_buff_tag))
                      or any(buff for buff in cls.day_night_tracking.day_buffs
                             if not buff.buff_type.has_tag(tracking_buff_tag))
                      ) or any(
                          buff
                          for buff in cls.day_night_tracking.night_buffs
                          if not buff.buff_type.has_tag(tracking_buff_tag)):
                    logger.error(
                        'Trait {} has Day Night tracking with an invalid\n                    buff. All buffs must be tagged with {} in order to be\n                    used as part of Day Night Tracking. Add these buffs with the\n                    understanding that, regardless of what system added them, they\n                    will always be on the Sim when the condition is met (i.e.\n                    Sunlight Buffs always added with sunlight is out) and they will\n                    always be removed when the condition is not met. Even if another\n                    system adds the buff, they will be removed if this trait is\n                    tuned to do that.\n                    ',
                        cls, tracking_buff_tag)
        for buff_reference in cls.buffs:
            if buff_reference.buff_type.broadcaster is not None:
                logger.error(
                    'Trait {} has a buff {} with a broadcaster tuned that will never be removed. This is a potential performance hit, and a GPE should decide whether this is the best place for such.',
                    cls,
                    buff_reference,
                    owner='rmccord')
        for commodity in cls.initial_commodities:
            if not commodity.persisted_tuning:
                logger.error(
                    'Trait {} has an initial commodity {} that does not have persisted tuning.',
                    cls, commodity)

    @classproperty
    def is_personality_trait(cls):
        return cls.trait_type == TraitType.PERSONALITY

    @classproperty
    def is_aspiration_trait(cls):
        return cls.trait_type == TraitType.ASPIRATION

    @classproperty
    def is_gender_option_trait(cls):
        return cls.trait_type == TraitType.GENDER_OPTIONS

    @classproperty
    def is_ghost_trait(cls):
        return cls.trait_type == TraitType.GHOST

    @classproperty
    def is_robot_trait(cls):
        return cls.trait_type == TraitType.ROBOT

    @classmethod
    def is_valid_trait(cls, sim_info_data):
        if cls.ages and sim_info_data.age not in cls.ages:
            return False
        if cls.genders and sim_info_data.gender not in cls.genders:
            return False
        elif cls.species and sim_info_data.species not in cls.species:
            return False
        return True

    @classmethod
    def should_apply_fixup_actions(cls, fixup_source):
        if cls.sim_info_fixup_actions and cls.sim_info_fixup_actions_timing == fixup_source:
            if fixup_source != SimInfoFixupActionTiming.ON_FIRST_SIMINFO_LOAD:
                logger.warn(
                    'Trait {} has fixup actions not from CAS flow.This should only happen to old saves before EP08',
                    cls,
                    owner='yozhang')
            return True
        return False

    @classmethod
    def apply_fixup_actions(cls, sim_info):
        for fixup_action in cls.sim_info_fixup_actions:
            fixup_action(sim_info)

    @classmethod
    def can_age_up(cls, current_age):
        if not cls.disable_aging:
            return True
        return current_age in cls.disable_aging.allowed_ages

    @classmethod
    def is_conflicting(cls, trait):
        if trait is None:
            return False
        if cls.conflicting_traits and trait in cls.conflicting_traits:
            return True
        elif trait.conflicting_traits and cls in trait.conflicting_traits:
            return True
        return False

    @classmethod
    def get_outfit_change_reason(cls, outfit_change_reason):
        replaced_reason = cls.outfit_replacements.get(
            outfit_change_reason if outfit_change_reason is not None else
            OutfitChangeReason.Invalid)
        if replaced_reason is not None:
            return replaced_reason
        elif outfit_change_reason is not None:
            replaced_reason = cls.outfit_replacements.get(
                OutfitChangeReason.Invalid)
            if replaced_reason is not None:
                return replaced_reason
        return outfit_change_reason

    @classmethod
    def get_teleport_style_interaction_to_inject(cls):
        return cls.teleport_style_interaction_to_inject
Пример #18
0
 def __init__(self, **kwargs):
     super().__init__(age_up_warning_notification=UiDialogNotification.TunableFactory(description='\n                Message sent to client to warn of impending age up.\n                '), age_up_available_notification=UiDialogNotification.TunableFactory(description='\n                Message sent to client to alert age up is ready.\n                '), age_transition_threshold=Tunable(description='\n                Number of Sim days required to be eligible to transition from\n                the mapping key age to the next one."\n                ', tunable_type=float, default=1), age_transition_warning=Tunable(description='\n                Number of Sim days prior to the transition a Sim will get a\n                warning of impending new age.\n                ', tunable_type=float, default=1), age_transition_delay=Tunable(description='\n                Number of Sim days after transition time elapsed before auto-\n                aging occurs.\n                ', tunable_type=float, default=1), auto_aging_actions=TunableTuple(buff=TunableReference(description='\n                    Buff that will be applied to the Sim when aging up to the\n                    current age.  This buff will be applied if the Sim\n                    auto-ages rather than if they age up with the birthday\n                    cake.\n                    ', manager=services.buff_manager()), buff_tests=TunableTestSet(description='\n                    Tests that will be run to determine if to apply the buff on\n                    the Sim.  This should be used to not apply the auto-aging\n                    under certain circumstances, example auto-aging while a\n                    birthday party is being thrown for the sim.\n                    '), description='\n                Tuning related to actions that will be applied on auto-aging\n                rather than aging up normally through the birday cake.\n                '), age_trait_awards=TunableList(description='\n                Traits available for selection to the Sim upon completing the\n                current age. They traits are presented in a menu for the player\n                to choose from.\n                ', tunable=TunableReference(services.trait_manager())), age_trait=TunableReference(description="\n                The age trait that corresponds to this Sim's age\n                ", manager=services.trait_manager()), age_transition_dialog=SimPersonalityAssignmentDialog.TunableFactory(description='\n                Dialog displayed to the player when their sim ages up.\n                ', locked_args={'phone_ring_type': PhoneRingType.NO_RING}))
Пример #19
0
class DeathTracker(SimInfoTracker):
    DEATH_ZONE_ID = 0
    DEATH_TYPE_GHOST_TRAIT_MAP = TunableMapping(
        description=
        '\n        The ghost trait to be applied to a Sim when they die with a given death\n        type.\n        ',
        key_type=TunableEnumEntry(
            description=
            '\n            The death type to map to a ghost trait.\n            ',
            tunable_type=DeathType,
            default=DeathType.NONE),
        key_name='Death Type',
        value_type=TunableReference(
            description=
            '\n            The ghost trait to apply to a Sim when they die from the specified\n            death type.\n            ',
            manager=services.trait_manager()),
        value_name='Ghost Trait')
    DEATH_BUFFS = TunableList(
        description=
        '\n        A list of buffs to apply to Sims when another Sim dies. For example, use\n        this tuning to tune a "Death of a Good Friend" buff.\n        ',
        tunable=TunableTuple(
            test_set=TunableReference(
                description=
                "\n                The test that must pass between the dying Sim (TargetSim) and\n                the Sim we're considering (Actor). If this test passes, no\n                further test is executed.\n                ",
                manager=services.get_instance_manager(sims4.resources.Types.
                                                      SNIPPET),
                class_restrictions=('TestSetInstance', ),
                pack_safe=True),
            buff=TunableBuffReference(
                description=
                '\n                The buff to apply to the Sim.\n                ',
                pack_safe=True),
            notification=OptionalTunable(
                description=
                '\n                If enabled, an off-lot death generates a notification for the\n                target Sim. This is limited to one per death instance.\n                ',
                tunable=TunableUiDialogNotificationReference(
                    description=
                    '\n                    The notification to show.\n                    ',
                    pack_safe=True))))
    IS_DYING_BUFF = TunableReference(
        description=
        '\n        A reference to the buff a Sim is given when they are dying.\n        ',
        manager=services.buff_manager())
    DEATH_RELATIONSHIP_BIT_FIXUP_LOOT = TunableReference(
        description=
        '\n        A reference to the loot to apply to a Sim upon death.\n        \n        This is where the relationship bit fixup loots will be tuned. This\n        used to be on the interactions themselves but if the interaction was\n        reset then the bits would stay as they were. If we add more relationship\n        bits we want to clean up on death, the references Loot is the place to \n        do it.\n        ',
        manager=services.get_instance_manager(sims4.resources.Types.ACTION))

    def __init__(self, sim_info):
        self._sim_info = sim_info
        self._death_type = None
        self._death_time = None

    @property
    def death_type(self):
        return self._death_type

    @property
    def death_time(self):
        return self._death_time

    @property
    def is_ghost(self):
        return self._sim_info.trait_tracker.has_any_trait(
            self.DEATH_TYPE_GHOST_TRAIT_MAP.values())

    def get_ghost_trait(self):
        return self.DEATH_TYPE_GHOST_TRAIT_MAP.get(self._death_type)

    def set_death_type(self, death_type, is_off_lot_death=False):
        is_npc = self._sim_info.is_npc
        household = self._sim_info.household
        self._sim_info.inject_into_inactive_zone(self.DEATH_ZONE_ID,
                                                 start_away_actions=False,
                                                 skip_instanced_check=True,
                                                 skip_daycare=True)
        household.remove_sim_info(self._sim_info,
                                  destroy_if_empty_household=True)
        if is_off_lot_death:
            household.pending_urnstone_ids.append(self._sim_info.sim_id)
        self._sim_info.transfer_to_hidden_household()
        clubs.on_sim_killed_or_culled(self._sim_info)
        if death_type is None:
            return
        relationship_service = services.relationship_service()
        for target_sim_info in relationship_service.get_target_sim_infos(
                self._sim_info.sim_id):
            resolver = DoubleSimResolver(target_sim_info, self._sim_info)
            for death_data in self.DEATH_BUFFS:
                if not death_data.test_set(resolver):
                    continue
                target_sim_info.add_buff_from_op(
                    death_data.buff.buff_type,
                    buff_reason=death_data.buff.buff_reason)
                if is_npc and not target_sim_info.is_npc:
                    notification = death_data.notification(target_sim_info,
                                                           resolver=resolver)
                    notification.show_dialog()
                break
        ghost_trait = DeathTracker.DEATH_TYPE_GHOST_TRAIT_MAP.get(death_type)
        if ghost_trait is not None:
            self._sim_info.add_trait(ghost_trait)
        traits = list(self._sim_info.trait_tracker.equipped_traits)
        for trait in traits:
            if trait.remove_on_death:
                self._sim_info.remove_trait(trait)
        self._death_type = death_type
        self._death_time = services.time_service().sim_now.absolute_ticks()
        self._sim_info.reset_age_progress()
        self._sim_info.resend_death_type()
        self._handle_remove_rel_bits_on_death()
        services.get_event_manager().process_event(
            test_events.TestEvent.SimDeathTypeSet, sim_info=self._sim_info)

    def _handle_remove_rel_bits_on_death(self):
        resolver = SingleSimResolver(self._sim_info)
        if self.DEATH_RELATIONSHIP_BIT_FIXUP_LOOT is not None:
            for (loot,
                 _) in self.DEATH_RELATIONSHIP_BIT_FIXUP_LOOT.get_loot_ops_gen(
                 ):
                result = loot.test_resolver(resolver)
                if result:
                    loot.apply_to_resolver(resolver)

    def clear_death_type(self):
        self._death_type = None
        self._death_time = None
        self._sim_info.resend_death_type()

    def save(self):
        if self._death_type is not None:
            data = protocols.PersistableDeathTracker()
            data.death_type = self._death_type
            data.death_time = self._death_time
            return data

    def load(self, data):
        try:
            self._death_type = DeathType(data.death_type)
        except:
            self._death_type = DeathType.NONE
        self._death_time = data.death_time

    @classproperty
    def _tracker_lod_threshold(cls):
        return SimInfoLODLevel.MINIMUM
Пример #20
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
Пример #21
0
class BuffRemovalOp(BaseLootOperation):
    FACTORY_TUNABLES = {
        'remove_all_visible_buffs':
        Tunable(
            description=
            "\n            If checked, all visible buffs on the Sim, excluding those specified in\n            the 'buffs_to_ignore' list will be removed.  If unchecked, buff removal\n            will be handled by the 'buffs_to_remove' list.\n            ",
            tunable_type=bool,
            default=False),
        'buffs_to_remove':
        TunableList(
            description=
            "\n            If 'remove_all_buffs' is not checked, this is the list of buffs that\n            will be removed from the subject.  If 'remove_all_buffs' is checked,\n            this list will be ignored.\n            ",
            tunable=TunableReference(
                description=
                '\n                Buff to be removed.\n                ',
                manager=services.buff_manager(),
                pack_safe=True)),
        'buff_tags_to_remove':
        TunableTags(
            description=
            "\n            If 'remove_all_buffs' is not checked, buffs with any tag in this list\n            will be removed from the subject. If 'remove_all_buffs' is checked, this\n            list will be ignored. You can also specify how many buffs you want to remove\n            by tags in count_to_remove_by_tags\n            ",
            filter_prefixes=('buff', )),
        'count_to_remove_by_tags':
        OptionalTunable(tunable=TunableRange(
            description=
            '\n                If enabled, randomly remove x number of buffs specified in buff_tags_to_remove.\n                If disabled, all buffs specified in buff_tags_to_remove will be removed\n                ',
            tunable_type=int,
            default=1,
            minimum=1)),
        'buffs_to_ignore':
        TunableList(
            description=
            "\n            If 'remove_all_buffs' is checked, no buffs included in this list will\n            be removed.  If 'remove_all_buffs' is unchecked, this list will be\n            ignored.\n            ",
            tunable=TunableReference(
                description=
                '\n                Buff to be removed.\n                ',
                manager=services.buff_manager()))
    }

    def __init__(self, remove_all_visible_buffs, buffs_to_remove,
                 buff_tags_to_remove, count_to_remove_by_tags, buffs_to_ignore,
                 **kwargs):
        super().__init__(**kwargs)
        self._remove_all_visible_buffs = remove_all_visible_buffs
        self._buffs_to_remove = buffs_to_remove
        self._buff_tags_to_remove = buff_tags_to_remove
        self._count_to_remove_by_tags = count_to_remove_by_tags
        self._buffs_to_ignore = buffs_to_ignore

    def _apply_to_subject_and_target(self, subject, target, resolver):
        if self._remove_all_visible_buffs:
            removal_list = []
            removal_list.extend(subject.Buffs)
            for buff in removal_list:
                if type(buff) in self._buffs_to_ignore:
                    continue
                if not buff.visible:
                    continue
                if buff.commodity is not None:
                    if subject.is_statistic_type_added_by_modifier(
                            buff.commodity):
                        continue
                    tracker = subject.get_tracker(buff.commodity)
                    commodity_inst = tracker.get_statistic(buff.commodity)
                    if commodity_inst is not None and commodity_inst.core:
                        continue
                subject.Buffs.remove_buff_entry(buff)
            else:
                subject.Buffs.remove_buffs_by_tags(
                    self._buff_tags_to_remove,
                    count_to_remove=self._count_to_remove_by_tags)
        else:
            for buff_type in self._buffs_to_remove:
                subject.Buffs.remove_buff_by_type(buff_type)
            subject.Buffs.remove_buffs_by_tags(
                self._buff_tags_to_remove,
                count_to_remove=self._count_to_remove_by_tags)
Пример #22
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
Пример #23
0
class Fish(objects.game_object.GameObject):
    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),
        'inventory_to_fish_vfx':
        sims4.tuning.tunable.TunableMapping(
            description=
            '\n            The inventory type to fish vfx to play when fish is placed in\n            inventory type.  If inventory type does not exist in mapping, use\n            fishbowl_vfx as fallback vfx to play.\n            ',
            key_type=sims4.tuning.tunable.TunableEnumEntry(
                tunable_type=objects.components.inventory_enums.InventoryType,
                default=objects.components.inventory_enums.InventoryType.
                UNDEFINED),
            value_type=sims4.tuning.tunable.TunableTuple(
                vfx_name=sims4.tuning.tunable.Tunable(tunable_type=str,
                                                      default=''),
                vfx_base_bone_name=sims4.tuning.tunable.Tunable(
                    tunable_type=str, default='_FX_fish_')),
            key_name='inventory_type',
            value_name='base vfx name',
            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),
        '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),
        'global_policy_value_mapping':
        TunableMapping(
            description=
            '\n            The mapping of global policies that when enacted are used to\n            increment the base value of the fish by a percent of its original value.\n            ',
            key_type=TunableReference(
                description=
                '\n                The global policy that when completed updates the cost of the\n                fish by the paired percent.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.SNIPPET),
                class_restrictions=('GlobalPolicy', ),
                pack_safe=True),
            value_type=TunablePercent(
                description=
                "\n                The percent of the fish's value to increment the base value.\n                ",
                default=50),
            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

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._active_global_policy_modifiers = None

    @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)
        self._update_fish_cost(self.WEIGHT_STATISTIC.default_value)
        self._register_for_tuned_global_policy_events()

    def _update_fish_cost(self, new_cost):
        fish_stat_tracker = self.get_tracker(self.WEIGHT_STATISTIC)
        fish_stat_tracker.set_value(self.WEIGHT_STATISTIC, new_cost)
        self.base_value += int(new_cost * self.weight_money_multiplier)
        self.remove_global_policy_value_mod()
        self.add_global_policy_value_mod()
        self.update_object_tooltip()

    def _register_for_tuned_global_policy_events(self):
        active_global_policies = self._active_global_policy_modifiers is not None
        for policy in self.global_policy_value_mapping:
            if active_global_policies and policy in self._active_global_policy_modifiers:
                continue
            services.get_event_manager().register_with_custom_key(
                self, TestEvent.GlobalPolicyProgress, policy)

    def remove_global_policy_value_mod(self):
        if self._active_global_policy_modifiers is None:
            return
        global_policy_service = services.global_policy_service()
        if global_policy_service is None:
            return
        total_percent_decrease = 1.0
        policies_to_remove = []
        enacted_policies = global_policy_service.get_enacted_global_policies()
        for modifying_policy in self._active_global_policy_modifiers:
            if modifying_policy not in enacted_policies:
                total_percent_decrease += self.global_policy_value_mapping.get(
                    type(modifying_policy))
                services.get_event_manager().register_with_custom_key(
                    self, TestEvent.GlobalPolicyProgress,
                    type(modifying_policy))
                policies_to_remove.append(modifying_policy)
        for policy_to_remove in policies_to_remove:
            self._active_global_policy_modifiers.remove(policy_to_remove)
        if total_percent_decrease != 0:
            self.base_value = int(self.base_value / total_percent_decrease)

    def add_global_policy_value_mod(self):
        if not self.global_policy_value_mapping:
            return
        global_policy_service = services.global_policy_service()
        if global_policy_service is None:
            return
        total_percent_increase = 0
        active_global_policy_modifiers = self._active_global_policy_modifiers is not None
        for enacted_policy in global_policy_service.get_enacted_global_policies(
        ):
            if active_global_policy_modifiers and enacted_policy in self._active_global_policy_modifiers:
                continue
            policy_percent_increase = self.global_policy_value_mapping.get(
                type(enacted_policy))
            if policy_percent_increase:
                if not active_global_policy_modifiers:
                    self._active_global_policy_modifiers = [enacted_policy]
                else:
                    self._active_global_policy_modifiers.append(enacted_policy)
                services.get_event_manager().register_with_custom_key(
                    self, TestEvent.GlobalPolicyProgress, type(enacted_policy))
                total_percent_increase += policy_percent_increase
        self.base_value += int(self.base_value * total_percent_increase)

    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
        else:
            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)
        self._update_fish_cost(actual_weight)
        self.update_ownership(sim)

    def get_catch_buffs_gen(self):
        yield from 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 get_notebook_information(self, notebook_entry, notebook_sub_entries):
        sub_entries = None
        if notebook_sub_entries is not None:
            for sub_entry in notebook_sub_entries:
                bait_data = FishingTuning.get_fishing_bait_data(
                    sub_entry.definition)
                if bait_data is not None:
                    if sub_entries is None:
                        sub_entries = []
                    sub_entries.append(SubEntryData(bait_data.guid64, True))
        return (notebook_entry(self.definition.id, sub_entries=sub_entries), )

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

    def handle_event(self, sim_info, event_type, resolver):
        if event_type == TestEvent.GlobalPolicyProgress:
            self._update_fish_cost(self.WEIGHT_STATISTIC.default_value)

    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)
        if self.global_policy_value_mapping:
            for policy in self.global_policy_value_mapping:
                services.get_event_manager().unregister_with_custom_key(
                    self, TestEvent.GlobalPolicyProgress, policy)
            if self._active_global_policy_modifiers is not None:
                for modifying_policy in self._active_global_policy_modifiers:
                    services.get_event_manager().unregister_with_custom_key(
                        self, TestEvent.GlobalPolicyProgress,
                        type(modifying_policy))
        self._active_global_policy_modifiers = None
        super().on_remove()

    def _ui_metadata_gen(self):
        tooltip_component = self.get_component(
            objects.components.types.TOOLTIP_COMPONENT)
        yield from 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
class FishingLocationGoFishingSuperInteraction(FishingLocationSuperInteraction
                                               ):
    __qualname__ = 'FishingLocationGoFishingSuperInteraction'
    BAIT_TAG_BUFF_MAP = sims4.tuning.tunable.TunableMapping(
        key_type=sims4.tuning.tunable.TunableEnumEntry(
            description=
            '\n            The bait tag to which we want to map a buff.\n            ',
            tunable_type=tag.Tag,
            default=tag.Tag.INVALID),
        key_name='Bait Tag',
        value_type=sims4.tuning.tunable.TunableReference(
            manager=services.buff_manager()),
        value_name='Bait Buff')
    FISHING_WITH_BAIT_INTERACTION_NAME = sims4.localization.TunableLocalizedStringFactory(
        description=
        '\n        When a Sim fishes with bait, this is the interaction name. This name\n        will revert to the normal name of the interaction when they run out of\n        bait.\n        \n        Uses the same tokens as the interaction display name.\n        '
    )
    OUT_OF_BAIT_NOTIFICATION = ui.ui_dialog_notification.UiDialogNotification.TunableFactory(
        description=
        "\n        This notification will be displayed when the player started using bait but ran out.\n        Token 0 is the actor sim. e.g. {0.SimFirstName}\n        Token 1 is the target fishing location (probably don't want to use this.\n        Token 2 is the bait object they just ran out of. e.g. {2.ObjectCatalogName} will show the type\n        "
    )

    def __init__(self,
                 aop,
                 context,
                 *args,
                 exit_functions=(),
                 force_inertial=False,
                 additional_post_run_autonomy_commodities=None,
                 **kwargs):
        super().__init__(aop,
                         context,
                         exit_functions=(),
                         force_inertial=False,
                         additional_post_run_autonomy_commodities=None,
                         *args,
                         **kwargs)
        self._bait = None
        self._buff_handle_ids = []

    def build_basic_elements(self, sequence=(), **kwargs):
        sequence = super().build_basic_elements(sequence=sequence, **kwargs)
        sequence = element_utils.build_critical_section_with_finally(
            self._interaction_start, sequence, self._interaction_end)
        return sequence

    @property
    def bait(self):
        return self._bait

    def _interaction_start(self, _):
        self._bait = self.get_participant(
            interactions.ParticipantType.PickedObject)
        self._try_apply_bait_and_buffs()

    def _try_apply_bait_and_buffs(self):
        if self._bait:
            if not self.sim.inventory_component.try_move_object_to_hidden_inventory(
                    self._bait):
                logger.error('Tried hiding the bait object, {}, but failed.',
                             self._bait)
                self._bait = None
            else:
                self._add_bait_buffs()
                self.sim.ui_manager.set_interaction_icon_and_name(
                    self.id,
                    icon=None,
                    name=self.create_localized_string(
                        localized_string_factory=self.
                        FISHING_WITH_BAIT_INTERACTION_NAME))

    def _interaction_end(self, _):
        if self._bait:
            sim_inventory = self.sim.inventory_component
            if not sim_inventory.try_remove_object_by_id(self._bait.id):
                logger.error(
                    "Tried removing the bait object, {}, but it couldn't be found.",
                    self._bait)
            if not sim_inventory.player_try_add_object(self._bait):
                logger.error(
                    "Tried adding the bait object, {}, back into the sim's, {}, inventory but failed.",
                    self._bait, self.sim)
            self._remove_bait_buffs()

    def kill_and_try_reapply_bait(self):
        if self._bait:
            sim_inventory = self.sim.inventory_component
            old_bait = self._bait
            self._bait = sim_inventory.get_item_with_definition(
                old_bait.definition, ignore_hidden=True)
            if self._bait is not None:
                self._try_apply_bait_and_buffs()
            else:
                self._remove_bait_buffs()
                self.sim.ui_manager.set_interaction_icon_and_name(
                    self.id, icon=None, name=self.get_name())
                notification = self.OUT_OF_BAIT_NOTIFICATION(
                    self.sim, self.get_resolver())
                notification.show_dialog(additional_tokens=(old_bait, ))
            if not sim_inventory.try_remove_object_by_id(old_bait.id):
                logger.error(
                    "Tried destroying the bait object, {}, but the destroy failed. It probably wasn't found in the sim's inventory or hidden inventory.",
                    old_bait)

    def _add_bait_buffs(self):
        if self._bait:
            for (tag, buff) in self.BAIT_TAG_BUFF_MAP.items():
                while self._bait.has_tag(tag):
                    self._buff_handle_ids.append(self.sim.add_buff(buff))

    def _remove_bait_buffs(self):
        for handle_id in self._buff_handle_ids:
            self.sim.remove_buff(handle_id)
        self._buff_handle_ids = []
Пример #25
0
class DynamicBuffLootOp(BaseLootOperation):
    FACTORY_TUNABLES = {
        'description':
        '\n        This loot will give a random buff based on the weight get tuned inside.\n        ',
        'buffs':
        TunableMapping(
            description='\n            ',
            key_type=TunableReference(
                description=
                '\n                Buff that will get this weight in the random.',
                manager=services.buff_manager()),
            value_type=Tunable(
                description='\n                The weight value.',
                tunable_type=float,
                default=0)),
        'buff_reason':
        OptionalTunable(
            description=
            '\n            If set, specify a reason why the buff was added.\n            ',
            tunable=TunableLocalizedString(
                description=
                '\n                The reason the buff was added. This will be displayed in the\n                buff tooltip.\n                '
            ))
    }

    def __init__(self, buffs, buff_reason, **kwargs):
        super().__init__(**kwargs)
        self._buffs = buffs
        self._buff_reason = buff_reason
        self._random_buff = None

    @TunableFactory.factory_option
    def subject_participant_type_options(description=singletons.DEFAULT,
                                         **kwargs):
        return BaseLootOperation.get_participant_tunable(
            *('subject', ),
            invalid_participants=(ParticipantType.Invalid, ParticipantType.All,
                                  ParticipantType.PickedItemId),
            **kwargs)

    def _get_random_buff(self):
        if self._random_buff is None:
            buff_pair_list = list(self._buffs.items())
            self._random_buff = sims4.random.pop_weighted(buff_pair_list,
                                                          flipped=True)
        return self._random_buff

    def _apply_to_subject_and_target(self, subject, target, resolver):
        random_buff = self._get_random_buff()
        if random_buff is not None:
            if not subject.is_sim:
                logger.error(
                    'Tuning error: subject {} of DynamicBuffLootOp giving buff {} for reason {} is not a sim',
                    self.subject, random_buff, self._buff_reason)
                return
            subject.add_buff_from_op(random_buff, self._buff_reason)

    def _on_apply_completed(self):
        random_buff = self._random_buff
        self._random_buff = None
        return random_buff