예제 #1
0
class TunableRewardMoney(TunableRewardBase):
    FACTORY_TUNABLES = {
        'money':
        TunableLiteralOrRandomValue(
            description=
            '\n            Give money to a sim/household.\n            ',
            tunable_type=int,
            default=10)
    }

    def __init__(self, *args, money, **kwargs):
        super().__init__(*args, **kwargs)
        self._awarded_money = money.random_int()

    @constproperty
    def reward_type():
        return RewardType.MONEY

    def open_reward(self, sim_info, **kwargs):
        household = services.household_manager().get(sim_info.household_id)
        if household is not None:
            household.funds.add(self._awarded_money,
                                Consts_pb2.TELEMETRY_MONEY_ASPIRATION_REWARD,
                                sim_info.get_sim_instance())

    def _get_display_text(self, resolver=None):
        return LocalizationHelperTuning.get_money(self._awarded_money)
class MoneyChange(BaseLootOperation):
    __qualname__ = 'MoneyChange'
    DISPLAY_TEXT = TunableLocalizedStringFactory(
        description=
        '\n        A string displaying the Simoleon amount that this loot operation awards.\n        It will be provided one token: the amount of Simoleons awarded.\n        '
    )

    @staticmethod
    def _verify_tunable_callback(instance_class, tunable_name, source, value):
        if value._subject == interactions.ParticipantType.Invalid:
            logger.error("{} doesn't have a valid participant type tuned",
                         source)

    FACTORY_TUNABLES = {
        'amount':
        TunableLiteralOrRandomValue(
            description=
            '\n        The amount of Simoleons awarded. The value will be rounded to the\n        closest integer. When two integers are equally close, rounding is done\n        towards the even one (e.g. 0.5 -> 0, 1.5 -> 2).\n        ',
            tunable_type=float,
            minimum=0),
        'statistic_multipliers':
        TunableList(
            description=
            '\n        Tunables for adding statistic based multipliers to the payout in the\n        format:\n        \n        amount *= statistic.value\n        ',
            tunable=TunableStatisticModifierCurve.TunableFactory()),
        'display_to_user':
        Tunable(
            description=
            '\n        If true, the amount will be displayed in the interaction name.\n        ',
            tunable_type=bool,
            needs_tuning=True,
            default=True),
        'verify_tunable_callback':
        _verify_tunable_callback
    }

    def __init__(self, amount, statistic_multipliers, display_to_user,
                 **kwargs):
        super().__init__(**kwargs)
        self._amount = amount
        self._statistic_multipliers = statistic_multipliers
        self._display_to_user = display_to_user
        self._random_amount = None

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

    def get_simoleon_delta(self, interaction, target=DEFAULT, context=DEFAULT):
        if not self._display_to_user:
            return 0
        if not self._tests.run_tests(
                interaction.get_resolver(target=target, context=context)):
            return 0
        sim = context.sim if context is not DEFAULT else DEFAULT
        recipients = interaction.get_participants(
            participant_type=self.subject, sim=sim, target=target)
        skill_multiplier = 1 if context is DEFAULT else interaction.get_skill_multiplier(
            interaction.monetary_payout_multipliers, context.sim)
        return self.amount * len(recipients) * skill_multiplier

    def _apply_to_subject_and_target(self, subject, target, resolver):
        interaction = resolver.interaction
        if interaction is not None:
            money_liability = interaction.get_liability(
                MoneyLiability.LIABILITY_TOKEN)
            if money_liability is None:
                money_liability = MoneyLiability()
                interaction.add_liability(MoneyLiability.LIABILITY_TOKEN,
                                          money_liability)
            skill_multiplier = interaction.get_skill_multiplier(
                interaction.monetary_payout_multipliers, interaction.sim)
        else:
            money_liability = None
            skill_multiplier = 1
        subject_obj = self._get_object_from_recipient(subject)
        amount_multiplier = self._get_multiplier(
            resolver, subject_obj) * skill_multiplier
        amount = round(self.amount * amount_multiplier)
        if amount:
            if money_liability is not None:
                money_liability.amounts[self.subject] += amount
            if interaction is not None:
                interaction_category_tags = interaction.interaction_category_tags
            else:
                interaction_category_tags = None
            subject.household.funds.add(
                amount,
                Consts_pb2.TELEMETRY_INTERACTION_REWARD,
                subject_obj,
                tags=interaction_category_tags)

    def _on_apply_completed(self):
        self._random_amount = None

    def _get_display_text(self):
        return self.DISPLAY_TEXT(*self._get_display_text_tokens())

    def _get_display_text_tokens(self):
        return (self.amount, )

    def _get_multiplier(self, resolver, sim):
        amount_multiplier = 1
        if self._statistic_multipliers:
            for statistic_multiplier in self._statistic_multipliers:
                amount_multiplier *= statistic_multiplier.get_multiplier(
                    resolver, sim)
        return amount_multiplier

    @property
    def amount(self):
        if self._random_amount is None:
            self._random_amount = self._amount.random_float()
        return self._random_amount
예제 #3
0
class Topic(metaclass=TunedInstanceMetaclass,
            manager=services.topic_manager()):
    __qualname__ = 'Topic'
    INSTANCE_TUNABLES = {
        'score_bonus':
        Tunable(
            description=
            '\n            Score bonus for matching topic tag.\n            ',
            tunable_type=int,
            default=0),
        'guaranteed_content':
        OptionalTunable(
            TunableTuple(
                description=
                '\n            If enabled, will force content set generation to add options for\n            this topic.\n            ',
                count=Tunable(
                    description=
                    '\n                The number of options to force into the content set.\n                ',
                    tunable_type=int,
                    default=1),
                priority=Tunable(
                    description=
                    '\n                The priority of this Topic vs. other Topics. Ties are randomized.\n                ',
                    tunable_type=int,
                    default=0))),
        'relevancy_value':
        TunableLiteralOrRandomValue(
            description=
            '\n            Initial Decay value once value has reached zero topic will be\n            removed.  If is_timeout is set, this will the number of minutes\n            before topic will timeout.\n            ',
            tunable_type=int,
            default=1),
        'is_timed_relevancy':
        Tunable(
            description=
            '\n            If set, relevancy value is treated as number of minutes until topic\n            is removed.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def topic_exist_in_sim(cls, sim, target=None):
        return sim.has_topic(cls, target=target)

    @classmethod
    def score_for_sim(cls, sim, target=None):
        if cls.topic_exist_in_sim(sim, target):
            return cls.score_bonus
        return 0

    def __init__(self, target):
        def on_target_deleted(ref):
            self.is_valid = False

        self._target_ref = target.ref(
            on_target_deleted) if target is not None else None
        self.reset_relevancy()
        self.is_valid = True

    def reset_relevancy(self):
        relevancy = self.relevancy_value.random_int()
        if self.is_timed_relevancy:
            self.current_relevancy = services.time_service(
            ).sim_now + clock.interval_in_sim_minutes(relevancy)
        else:
            self.current_relevancy = relevancy

    def decay_topic(self, time):
        if not self.is_valid:
            return True
        if self.is_timed_relevancy:
            return time >= self.current_relevancy
        return self.current_relevancy <= 0

    def target_matches(self, target):
        return self.is_valid and target is self.target

    @property
    def target(self):
        if self._target_ref is not None:
            return self._target_ref()
예제 #4
0
class LifeSkillStatistic(HasTunableReference,
                         LifeSkillDisplayMixin,
                         TunedContinuousStatistic,
                         metaclass=HashedTunedInstanceMetaclass,
                         manager=services.get_instance_manager(
                             sims4.resources.Types.STATISTIC)):
    REMOVE_INSTANCE_TUNABLES = ('initial_value', )
    INSTANCE_TUNABLES = {
        'min_value_tuning':
        Tunable(description=
                '\n            The minimum value for this stat.\n            ',
                tunable_type=float,
                default=-100,
                export_modes=ExportModes.All),
        'max_value_tuning':
        Tunable(description=
                '\n            The maximum value for this stat.\n            ',
                tunable_type=float,
                default=100,
                export_modes=ExportModes.All),
        'initial_tuning':
        TunableLiteralOrRandomValue(
            description=
            '\n            The initial value of this stat.  Can be a single value or range.\n            ',
            tunable_type=float,
            default=0,
            minimum=-100),
        'initial_test_based_modifiers':
        TunableList(
            description=
            '\n            List of tuples containing test and a random value. If the test passes,\n            a random value is added to the already random initial value. \n            ',
            tunable=TunableTuple(
                description=
                '\n                A container for test and the corresponding random value.\n                ',
                initial_value_test=TunableTestSet(
                    description=
                    '\n                    If test passes, then the random value tuned will be applied\n                    to the initial value. \n                    '
                ),
                initial_modified_value=TunableLiteralOrRandomValue(
                    description=
                    '\n                    The initial value of this stat.  Can be a single value or range.\n                    ',
                    tunable_type=float,
                    default=0,
                    minimum=-100))),
        'age_to_remove_stat':
        TunableEnumEntry(
            description=
            '\n            When sim reaches this age, this stat will be removed permanently. \n            ',
            tunable_type=Age,
            default=Age.YOUNGADULT),
        'missing_career_decay_rate':
        Tunable(
            description=
            '\n            How much this life skill decay by if sim is late for school/work.\n            ',
            tunable_type=float,
            default=0.0),
        'trait_on_age_up_list':
        TunableList(
            description=
            '\n            A list of trait that will be applied on age up if this commodity \n            falls within the range specified in this tuple.\n            It also contains other visual information like VFX and notification.\n            ',
            tunable=TunableTuple(
                description=
                '\n                A container for the range and corresponding information.\n                ',
                export_class_name='TunableTraitOnAgeUpTuple',
                life_skill_range=TunableInterval(
                    description=
                    '\n                    If the commodity is in this range on age up, the trait\n                    will be applied. \n                    The vfx and notification will be played every time the \n                    range is crossed.\n                    ',
                    tunable_type=float,
                    default_lower=0,
                    default_upper=100,
                    export_modes=ExportModes.All),
                age_up_info=OptionalTunable(
                    description=
                    "\n                    If enabled, this trait will be added on age up given the specified age. \n                    Otherwise, no trait will be added.\n                    We don't use loot because UI needs this trait exported for display.\n                    ",
                    enabled_name='enabled_age_up_info',
                    tunable=TunableTuple(
                        export_class_name='TunableAgeUpInfoTuple',
                        age_to_apply_trait=TunableEnumEntry(
                            description=
                            '\n                            When sim reaches this age, this trait will be added on age up.\n                            ',
                            tunable_type=Age,
                            default=Age.YOUNGADULT),
                        life_skill_trait=Trait.TunableReference(
                            description=
                            '\n                            Trait that is added on age up.\n                            ',
                            pack_safe=True)),
                    export_modes=ExportModes.All),
                in_range_notification=
                OptionalTunable(tunable=TunableUiDialogNotificationSnippet(
                    description=
                    '\n                        Notification that is sent when the commodity reaches this range.\n                        '
                )),
                out_of_range_notification=
                OptionalTunable(tunable=TunableUiDialogNotificationSnippet(
                    description=
                    '\n                        Notification that is sent when the commodity exits this range.\n                        '
                )),
                vfx_triggered=TunablePlayEffectVariant(
                    description=
                    '\n                    Vfx to play on the sim when commodity enters this threshold.\n                    ',
                    tuning_group=GroupNames.ANIMATION),
                in_range_buff=OptionalTunable(tunable=TunableBuffReference(
                    description=
                    '\n                        Buff that is added when sim enters this threshold.\n                        '
                )))),
        'headline':
        TunableReference(
            description=
            '\n            The headline that we want to send down when this life skill updates.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.HEADLINE),
            tuning_group=GroupNames.UI)
    }

    def __init__(self, tracker):
        self._vfx = None
        super().__init__(tracker, self.get_initial_value())
        self._last_update_value = None
        if not tracker.load_in_progress:
            self._apply_initial_value_modifier()

    @classproperty
    def persists_across_gallery_for_state(cls):
        if cls.gallery_load_behavior == GalleryLoadBehavior.LOAD_FOR_ALL or cls.gallery_load_behavior == GalleryLoadBehavior.LOAD_ONLY_FOR_OBJECT:
            return True
        return False

    @classmethod
    def get_initial_value(cls):
        return cls.initial_tuning.random_int()

    def _apply_initial_value_modifier(self):
        initial_value = self._value
        resolver = SingleSimResolver(self.tracker.owner)
        for initial_modifier in self.initial_test_based_modifiers:
            if initial_modifier.initial_value_test.run_tests(resolver):
                initial_value += initial_modifier.initial_modified_value.random_float(
                )
        self.set_value(initial_value, from_add=True)

    def _update_value(self):
        old_value = self._value
        super()._update_value()
        new_value = self._value
        self._evaluate_threshold(old_value=old_value, new_value=new_value)

    def _evaluate_threshold(self, old_value=0, new_value=0, from_load=False):
        old_infos = []
        new_infos = []
        for range_info in self.trait_on_age_up_list:
            if old_value in range_info.life_skill_range:
                old_infos.append(range_info)
            if new_value in range_info.life_skill_range:
                new_infos.append(range_info)
        old_infos_set = set(old_infos)
        new_infos_set = set(new_infos)
        out_ranges = old_infos_set - new_infos_set
        in_ranges = new_infos_set - old_infos_set
        owner = self.tracker.owner
        is_household_sim = owner.is_selectable and owner.valid_for_distribution
        if not from_load:
            for out_range in out_ranges:
                if out_range.out_of_range_notification is not None and is_household_sim:
                    dialog = out_range.out_of_range_notification(
                        owner, resolver=SingleSimResolver(owner))
                    dialog.show_dialog(additional_tokens=(owner, ))
                if out_range.in_range_buff is not None:
                    owner.Buffs.remove_buff_by_type(
                        out_range.in_range_buff.buff_type)
        for in_range in in_ranges:
            if in_range.in_range_notification is not None and not from_load and is_household_sim:
                dialog = in_range.in_range_notification(
                    owner, resolver=SingleSimResolver(owner))
                dialog.show_dialog(additional_tokens=(owner, ))
            if in_range.vfx_triggered is not None and not from_load and is_household_sim:
                if self._vfx is not None:
                    self._vfx.stop(immediate=True)
                    self._vfx = None
                sim = owner.get_sim_instance(
                    allow_hidden_flags=ALL_HIDDEN_REASONS)
                if sim is not None:
                    self._vfx = in_range.vfx_triggered(sim)
                    self._vfx.start()
            if in_range.in_range_buff is not None:
                owner.Buffs.add_buff(
                    in_range.in_range_buff.buff_type,
                    buff_reason=in_range.in_range_buff.buff_reason)

    def _on_statistic_modifier_changed(self, notify_watcher=True):
        super()._on_statistic_modifier_changed(notify_watcher=notify_watcher)
        self.create_and_send_commodity_update_msg(is_rate_change=False)

    @constproperty
    def remove_on_convergence():
        return False

    def set_value(self,
                  value,
                  *args,
                  from_load=False,
                  interaction=None,
                  **kwargs):
        old_value = self._value
        super().set_value(value,
                          *args,
                          from_load=from_load,
                          interaction=interaction,
                          **kwargs)
        new_value = self._value
        self._evaluate_threshold(old_value=old_value,
                                 new_value=new_value,
                                 from_load=from_load)
        if from_load:
            return
        self.create_and_send_commodity_update_msg(is_rate_change=False,
                                                  from_add=kwargs.get(
                                                      'from_add', False))

    def on_remove(self, on_destroy=False):
        super().on_remove(on_destroy=on_destroy)
        if self._vfx is not None:
            self._vfx.stop(immediate=True)
            self._vfx = None

    def save_statistic(self, commodities, skills, ranked_statistics, tracker):
        message = protocols.Commodity()
        message.name_hash = self.guid64
        message.value = self.get_saved_value()
        if self._time_of_last_value_change:
            message.time_of_last_value_change = self._time_of_last_value_change.absolute_ticks(
            )
        commodities.append(message)

    def create_and_send_commodity_update_msg(self,
                                             is_rate_change=True,
                                             allow_npc=False,
                                             from_add=False):
        current_value = self.get_value()
        change_rate = self.get_change_rate()
        life_skill_msg = Commodities_pb2.LifeSkillUpdate()
        life_skill_msg.sim_id = self.tracker.owner.id
        life_skill_msg.life_skill_id = self.guid64
        life_skill_msg.curr_value = current_value
        life_skill_msg.rate_of_change = change_rate
        life_skill_msg.is_from_add = from_add
        send_sim_life_skill_update_message(self.tracker.owner, life_skill_msg)
        if self._last_update_value is None:
            value_to_send = change_rate
        else:
            value_to_send = current_value - self._last_update_value
        self._last_update_value = current_value
        if value_to_send != 0 and not from_add:
            self.headline.send_headline_message(self.tracker.owner,
                                                value_to_send)

    def create_and_send_life_skill_delete_msg(self):
        life_skill_msg = Commodities_pb2.LifeSkillDelete()
        life_skill_msg.sim_id = self.tracker.owner.id
        life_skill_msg.life_skill_id = self.guid64
        send_sim_life_skill_delete_message(self.tracker.owner, life_skill_msg)
예제 #5
0
class StoryProgressionDestinationPopulateAction(_StoryProgressionAction):
    FACTORY_TUNABLES = {
        '_region_to_rentable_zone_density':
        TunableMapping(
            description=
            '\n        Based on region what percent of available lots will be filled.\n        ',
            key_name='Region Description',
            key_type=TunableRegionDescription(pack_safe=True),
            value_name='Rentable Zone Density',
            value_type=TunableTuple(
                _venues_to_populate=TunableSet(
                    description=
                    '\n                A set of venue references that are considered to be rentable.\n                ',
                    tunable=TunableReference(
                        manager=services.
                        get_instance_manager(
                            sims4.resources.
                            Types.VENUE),
                        pack_safe
                        =True)),
                household_description_to_ideal_travel_group_size=TunableMapping(
                    description=
                    '\n                Based on the house description how many sims should go on vacation\n                ',
                    key_name='House Description',
                    key_type=TunableHouseDescription(pack_safe=True),
                    value_name='Travel Group Size',
                    value_type=TunableLiteralOrRandomValue(
                        description=
                        '\n                    The maximum number of sims that should go on vacation to\n                    that lot.\n                    ',
                        tunable_type=int,
                        minimum=0)),
                bed_count_to_travel_group_size=TunableMapping(
                    description=
                    '\n                Based on the house description how many sims should go on vacation\n                ',
                    key_name='Number of beds',
                    key_type=Tunable(
                        description=
                        '\n                    The number of beds on the lot to determine how many sims\n                    can go in the vacation group.\n                    ',
                        tunable_type=int,
                        default=1),
                    value_name='Travel Group Size',
                    value_type=TunableLiteralOrRandomValue(
                        description=
                        '\n                    The maximum number of sims that should go on vacation to\n                    that lot.\n                    ',
                        tunable_type=int,
                        minimum=0)),
                travel_group_size_to_household_template=TunableMapping(
                    description=
                    '\n                Mapping to travel group size to household templates. If there\n                are no household that fulfill the requirement of renting a\n                zone, then random household template will chosen to be created\n                to rent a zone.\n                ',
                    key_type=Tunable(tunable_type=int, default=1),
                    value_type=TunableList(
                        description=
                        '\n                    Household template that will be created for renting a zone.\n                    ',
                        tunable=HouseholdTemplate.TunableReference())),
                density=TunablePercent(
                    description=
                    '\n                Percent of lots will be occupied once a user sim has rented a lot.\n                ',
                    default=80),
                min_to_populate=TunableRange(
                    description=
                    '\n                Minimum number of lots that should be rented.\n                ',
                    tunable_type=int,
                    default=3,
                    minimum=0),
                duration=TunableLiteralOrRandomValue(
                    description=
                    "\n                The maximum in sim days npc's should stay on vacation.\n                ",
                    tunable_type=int,
                    minimum=1,
                    default=1)))
    }

    def should_process(self, options):
        if services.active_household_id() == 0:
            return False
        return True

    def _get_rentable_zones(self, rentable_zone_density_data,
                            neighborhood_proto_buff):
        num_zones_rented = 0
        available_zone_ids = []
        travel_group_manager = services.travel_group_manager()
        venue_manager = services.get_instance_manager(
            sims4.resources.Types.VENUE)
        for lot_owner_info in neighborhood_proto_buff.lots:
            if lot_owner_info.venue_key == 0:
                continue
            if venue_manager.get(
                    lot_owner_info.venue_key
            ) not in rentable_zone_density_data._venues_to_populate:
                continue
            zone_id = lot_owner_info.zone_instance_id
            if not travel_group_manager.is_zone_rentable(zone_id):
                num_zones_rented += 1
            else:
                available_zone_ids.append(zone_id)
        return (num_zones_rented, available_zone_ids)

    def process_action(self, story_progression_flags):
        zone = services.current_zone()
        neighborhood_proto = services.get_persistence_service(
        ).get_neighborhood_proto_buff(zone.neighborhood_id)
        region_id = neighborhood_proto.region_id
        rentable_zone_density_data = self._region_to_rentable_zone_density.get(
            region_id)
        if rentable_zone_density_data is None:
            return
        (num_zones_rented, available_zone_ids) = self._get_rentable_zones(
            rentable_zone_density_data, neighborhood_proto)
        num_available_zone_ids = len(available_zone_ids)
        if num_available_zone_ids == 0:
            return
        number_zones_to_fill = 0
        num_desired_zones_filled = math.floor(
            (num_zones_rented + num_available_zone_ids) *
            rentable_zone_density_data.density)
        if num_desired_zones_filled < rentable_zone_density_data.min_to_populate:
            num_desired_zones_filled = rentable_zone_density_data.min_to_populate
        if num_zones_rented < num_desired_zones_filled:
            number_zones_to_fill = num_desired_zones_filled - num_zones_rented
        neighborhood_population_service = services.neighborhood_population_service(
        )
        if neighborhood_population_service is None:
            return
        neighborhood_population_service.add_rentable_lot_request(
            number_zones_to_fill, zone.neighborhood_id, None,
            available_zone_ids, rentable_zone_density_data)
예제 #6
0
class _DesiredSituations(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'desired_sim_count':
        TunableLiteralOrRandomValue(
            description=
            '\n            The number of Sims desired to be participating in the situation.\n            ',
            tunable_type=int,
            default=0),
        'disable_churn':
        Tunable(
            description=
            "\n            If checked, we disable churn for this shift change. That means we\n            only fire the situation on shift change, not in between shifts. So\n            if you have a situation in this shift and it ends, we don't spin up\n            another one on the next churn (based on churn interval). Basically\n            means you want a one shot situation, fire and forget.\n            \n            If unchecked, we will try to maintain the desired number of\n            situations at every churn interval during this shift change.\n            ",
            tunable_type=bool,
            default=False)
    }

    @TunableFactory.factory_option
    def get_create_params(user_facing=False):
        create_params = {}
        create_params_locked = {}
        if user_facing:
            create_params['user_facing'] = Tunable(
                description=
                "\n                                                   If enabled, we will start the situation as user facing.\n                                                   Note: We can only have one user facing situation at a time,\n                                                   so make sure you aren't tuning multiple user facing\n                                                   situations to occur at once.\n                                                   ",
                tunable_type=bool,
                default=False)
        else:
            create_params_locked['user_facing'] = False
        return {
            'weighted_situations':
            TunableList(
                description=
                '\n            A weighted list of situations to be used while fulfilling the\n            desired Sim count.\n            ',
                tunable=TunableTuple(
                    situation=Situation.TunableReference(pack_safe=True),
                    params=TunableTuple(
                        description=
                        '\n                    Situation creation parameters.\n                    ',
                        locked_args=create_params_locked,
                        **create_params),
                    weight=Tunable(tunable_type=int, default=1),
                    weight_multipliers=TunableMultiplier.TunableFactory(
                        description=
                        "\n                    Tunable tested multiplier to apply to weight.\n                    \n                    *IMPORTANT* The only participants that work are ones\n                    available globally, such as Lot and ActiveHousehold. Only\n                    use these participant types or use tests that don't rely\n                    on any, such as testing all objects via Object Criteria\n                    test or testing active zone with the Zone test.\n                    ",
                        locked_args={'base_value': 1}),
                    tests=TunableTestSet(
                        description=
                        "\n                    A set of tests that must pass for the situation and weight\n                    pair to be available for selection.\n                    \n                    *IMPORTANT* The only participants that work are ones\n                    available globally, such as Lot and ActiveHousehold. Only\n                    use these participant types or use tests that don't rely\n                    on any, such as testing all objects via Object Criteria\n                    test or testing active zone with the Zone test.\n                    "
                    )))
        }

    def get_weighted_situations(self, predicate=lambda _: True):
        resolver = GlobalResolver()

        def get_weight(item):
            if not predicate(item.situation):
                return 0
            if not item.tests.run_tests(resolver):
                return 0
            return item.weight * item.weight_multipliers.get_multiplier(
                resolver) * item.situation.weight_multipliers.get_multiplier(
                    resolver)

        weighted_situations = tuple(
            (get_weight(item), (item.situation, dict(item.params.items())))
            for item in self.weighted_situations)
        return weighted_situations

    def get_situation_and_params(self,
                                 predicate=lambda _: True,
                                 additional_situations=None):
        weighted_situations = self.get_weighted_situations(predicate=predicate)
        if additional_situations is not None:
            weighted_situations = tuple(weighted_situations) + tuple(
                additional_situations)
        situation_and_params = random.weighted_random_item(weighted_situations)
        if situation_and_params is not None:
            return situation_and_params
        return (None, {})
예제 #7
0
class SlotItemTransfer(XevtTriggeredElement, HasTunableFactory,
                       AutoFactoryInit):
    FACTORY_TUNABLES = {
        'objects_to_transfer':
        TunableObjectGeneratorVariant(
            description=
            "\n            The objects whose slots will be checked for objects to be gathered\n            into the Sim's inventory.\n            ",
            participant_default=ParticipantType.Object),
        'object_tests':
        TunableTestSet(
            description=
            '\n            Tests that will run on each object, and will harvest the object\n            only if all the tests pass.\n            \n            The object will be the PickedObject participant type, so we can\n            preserve the interaction resolver.\n            '
        ),
        'fallback_to_household_inventory':
        Tunable(
            description=
            "\n            If enabled, and we fail to add the object to the Sim's inventory,\n            we will attempt to add it to the household inventory.\n            ",
            tunable_type=bool,
            default=False),
        'transfer_extra_objects':
        OptionalTunable(
            description=
            "\n            If enabled, extra slot items will be transferred to the Sim's inventory\n            from each object we gathered.\n            \n            For example: If we gathered 3 apple trees, and set count as 2, then we\n            will harvest 3 * 2 = 6 extra apples from this interaction\n            ",
            tunable=TunableTuple(
                count=TunableLiteralOrRandomValue(
                    description=
                    '\n                    The number of extra items to be transferred.\n                    ',
                    tunable_type=int,
                    default=1,
                    minimum=0,
                    maximum=10),
                tests=TunableTestSet(
                    description=
                    '\n                    A set of tests that must pass in order for this extra transfer\n                    to be applied.\n                    '
                ))),
        'use_gardening_optimization':
        Tunable(
            description=
            '\n            If enabled, we will precompact the objects prior to adding them\n            to inventory if the tuning ID and quality are the same regardless\n            of any other differences.\n            ',
            tunable_type=bool,
            default=True)
    }

    def _do_behavior(self):
        objects = self.objects_to_transfer.get_objects(self.interaction)
        if not objects:
            return False
        transfer_extra = self.transfer_extra_objects
        should_transfer_extra = False
        if transfer_extra:
            resolver = self.interaction.get_resolver()
            if transfer_extra.tests.run_tests(resolver):
                should_transfer_extra = True
        objects_to_transfer = []
        num_extra_objects = 0
        for obj in objects:
            has_extra_to_transfer = False
            for child_inst in obj.children:
                interaction_parameters = {'picked_item_ids': (child_inst.id, )}
                resolver = self.interaction.get_resolver(
                    **interaction_parameters)
                if self.object_tests.run_tests(resolver):
                    objects_to_transfer.append(child_inst)
                    has_extra_to_transfer = True
            if should_transfer_extra:
                if has_extra_to_transfer:
                    num_extra_objects += transfer_extra.count.random_int()
        stacked_objects = self._stack_objects_transfer(objects_to_transfer,
                                                       num_extra_objects)
        sim = self.interaction.sim
        sim_inventory = sim.inventory_component
        for (obj, _) in stacked_objects:
            obj.update_ownership(sim)
            if sim_inventory.can_add(obj):
                if obj.live_drag_component is not None:
                    obj.live_drag_component.resolve_live_drag_household_permission(
                    )
                sim_inventory.player_try_add_object(obj)
            elif self.fallback_to_household_inventory:
                build_buy.move_object_to_household_inventory(obj)

    def _stack_objects_transfer(self, objects_to_harvest, num_extra_objects):
        obj_count = {}
        dupe_objects = []
        if self.use_gardening_optimization:
            unique_objects = []
            while objects_to_harvest:
                obj = objects_to_harvest.pop()
                quality_value = obj.get_state(
                    GardeningTuning.QUALITY_STATE_VALUE) if obj.has_state(
                        GardeningTuning.QUALITY_STATE_VALUE) else 0
                object_count_key = (obj.guid64, quality_value)
                curr_count = obj_count.get(object_count_key, 0)
                if curr_count == 0:
                    unique_objects.append((obj, quality_value))
                else:
                    dupe_objects.append(obj)
                curr_count = curr_count + 1
                obj_count[object_count_key] = curr_count
            for (obj, quality_value) in unique_objects:
                object_count_key = (obj.guid64, quality_value)
                curr_count = obj_count.get(object_count_key, 0)
                obj.set_stack_count(curr_count)
        else:
            unique_objects = [(obj, 0) for obj in objects_to_harvest]
        if unique_objects:
            while num_extra_objects > 0:
                (obj, _) = random.choice(unique_objects)
                obj.update_stack_count(1)
                num_extra_objects -= 1
        if dupe_objects:
            services.get_reset_and_delete_service().trigger_batch_destroy(
                dupe_objects)
        return unique_objects
예제 #8
0
class AgingTransition(HasTunableSingletonFactory, AutoFactoryInit):

    class _AgeTransitionShowDialog(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'dialog': SimPersonalityAssignmentDialog.TunableFactory(locked_args={'phone_ring_type': PhoneRingType.NO_RING})}

        def __call__(self, sim_info, **kwargs):

            def on_response(dlg):
                if dlg.accepted:
                    sim_info.resend_trait_ids()

            dialog = self.dialog(sim_info, assignment_sim_info=sim_info, resolver=SingleSimResolver(sim_info))
            dialog.show_dialog(on_response=on_response, **kwargs)

    class _AgeTransitionShowNotification(HasTunableSingletonFactory, AutoFactoryInit):
        FACTORY_TUNABLES = {'dialog': UiDialogNotification.TunableFactory()}

        def __call__(self, sim_info, **__):
            dialog = self.dialog(sim_info, resolver=SingleSimResolver(sim_info))
            dialog.show_dialog()

    FACTORY_TUNABLES = {'age_up_warning_notification': OptionalTunable(tunable=UiDialogNotification.TunableFactory(description='\n                Notification to show up when Age Up is impending.\n                ', tuning_group=GroupNames.UI)), 'age_up_available_notification': OptionalTunable(tunable=UiDialogNotification.TunableFactory(description='\n                Notification to show when Age Up is ready.\n                ', tuning_group=GroupNames.UI)), '_age_duration': TunableLiteralOrRandomValue(description="\n            The time, in Sim days, required for a Sim to be eligible to\n            transition from this age to the next one.\n            \n            If a random range is specified, the random value will be seeded on\n            the Sim's ID.\n            ", tunable_type=float, default=1), '_use_initial_age_randomization': Tunable(description="\n            If checked, instead of randomizing the duration of each individual age,\n            the sims's initial age progress will be randomly offset on first load. \n            ", tunable_type=bool, default=False), 'age_transition_warning': Tunable(description='\n            Number of Sim days prior to the transition a Sim will get a warning\n            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- aging\n            occurs.\n            ', tunable_type=float, default=1), 'age_transition_dialog': TunableVariant(description='\n            The dialog or notification that is displayed when the Sim ages up.\n            ', show_dialog=_AgeTransitionShowDialog.TunableFactory(), show_notification=_AgeTransitionShowNotification.TunableFactory(), locked_args={'no_dialog': None}, default='no_dialog', tuning_group=GroupNames.UI), 'age_trait': TunableReference(description="\n            The age trait that corresponds to this Sim's age\n            ", manager=services.get_instance_manager(sims4.resources.Types.TRAIT)), 'relbit_based_loot': TunableList(description='\n            List of loots given based on bits set in existing relationships.\n            Applied after per household member loot.\n            ', tunable=TunableTuple(description='\n                Loot given to sim aging up (actor) and each sim (target) with a\n                "chained" relationship via recursing through the list of relbit\n                sets.\n                ', relationship=TunableList(description='\n                    List specifying a series of relationship(s) to recursively \n                    traverse to find the desired target sim.  i.e. to find \n                    "cousins", we get all the "parents".  And then we \n                    get "aunts/uncles" by getting the "siblings" of those \n                    "parents".  And then we finally get the "cousins" by \n                    getting the "children" of those "aunts/uncles".\n                    \n                    So:\n                     Set of "parent" bitflag(s)\n                     Set of "sibling" bitflag(s)\n                     Set of "children" bitflag(s)\n                    \n                    Can also find direct existing relationships by only having a\n                    single entry in the list.\n                    ', tunable=TunableSet(description='\n                        Set of relbits to use for this relationship.\n                        ', tunable=RelationshipBit.TunableReference(description='\n                            The relationship bit between greeted Sims.\n                            ', pack_safe=True))), loot=TunableList(description='\n                    Loot given between sim aging up and sims with the previously\n                    specified chain of relbits. (may create a relationship).\n                    ', tunable=LootActions.TunableReference(description='\n                        A loot action given to sim aging up.\n                        ', pack_safe=True))), tuning_group=GroupNames.TRIGGERS), 'per_household_member_loot': TunableList(description="\n            Loots given between sim aging up (actor) and each sim in that sims\n            household (target).  Applied before relbit based loot'\n            ", tunable=LootActions.TunableReference(description='\n                A loot action given between sim aging up (actor) and each sim in\n                that sims household (target).\n                ', pack_safe=True), tuning_group=GroupNames.TRIGGERS), 'single_sim_loot': TunableList(description='\n            Loots given to sim aging up (actor). Last loot applied.\n            ', tunable=LootActions.TunableReference(description='\n                A loot action given to sim aging up.\n                ', pack_safe=True), tuning_group=GroupNames.TRIGGERS)}

    def get_age_duration(self, sim_info):
        if self._use_initial_age_randomization:
            return (self._age_duration.upper_bound + self._age_duration.lower_bound)/2
        return self._get_random_age_duration(sim_info)

    def _get_random_age_duration(self, sim_info):
        return self._age_duration.random_float(seed=(self.age_trait.guid64, sim_info.sim_id))

    def get_randomized_progress(self, sim_info, age_progress):
        if self._use_initial_age_randomization:
            previous_age_duration = self._get_random_age_duration(sim_info)
            current_age_duration = self.get_age_duration(sim_info)
            if sims4.math.almost_equal(previous_age_duration, current_age_duration):
                return age_progress
            age_progress = current_age_duration + age_progress - previous_age_duration
            if age_progress < 0:
                age_progress = self._age_duration.upper_bound - self._age_duration.lower_bound + age_progress
        return age_progress

    def _apply_aging_transition_relbit_loot(self, source_info, cur_info, relbit_based_loot, level):
        if level == len(relbit_based_loot.relationship):
            resolver = DoubleSimResolver(source_info, cur_info)
            for loot in relbit_based_loot.loot:
                loot.apply_to_resolver(resolver)
            return
        relationship_tracker = cur_info.relationship_tracker
        for target_sim_id in relationship_tracker.target_sim_gen():
            if set(relationship_tracker.get_all_bits(target_sim_id)) & relbit_based_loot.relationship[level]:
                new_sim_info = services.sim_info_manager().get(target_sim_id)
                self._apply_aging_transition_relbit_loot(source_info, new_sim_info, relbit_based_loot, level + 1)

    def apply_aging_transition_loot(self, sim_info):
        if self.per_household_member_loot:
            for member_info in sim_info.household.sim_info_gen():
                if member_info is sim_info:
                    continue
                resolver = DoubleSimResolver(sim_info, member_info)
                for household_loot in self.per_household_member_loot:
                    household_loot.apply_to_resolver(resolver)
        for relbit_based_loot in self.relbit_based_loot:
            self._apply_aging_transition_relbit_loot(sim_info, sim_info, relbit_based_loot, 0)
        resolver = SingleSimResolver(sim_info)
        for loot in self.single_sim_loot:
            loot.apply_to_resolver(resolver)

    def show_age_transition_dialog(self, sim_info, **kwargs):
        if self.age_transition_dialog is not None:
            self.age_transition_dialog(sim_info, **kwargs)
예제 #9
0
class MoneyChange(BaseLootOperation):
    FACTORY_TUNABLES = {
        'amount':
        TunableLiteralOrRandomValue(
            description=
            '\n            The amount of Simoleons awarded. The value will be rounded to the\n            closest integer. When two integers are equally close, rounding is done\n            towards the even one (e.g. 0.5 -> 0, 1.5 -> 2).  Negative amounts allowed\n            and allow partial deductions (will only take balance to zero, not negative).\n            ',
            tunable_type=float,
            default=0,
            minimum=None),
        'statistic_multipliers':
        TunableList(
            description=
            '\n            Tunables for adding statistic based multipliers to the payout in the\n            format:\n            \n            amount *= statistic.value\n            ',
            tunable=TunableStatisticModifierCurve.TunableFactory()),
        'display_to_user':
        Tunable(
            description=
            '\n            If true, the amount will be displayed in the interaction name.\n            ',
            tunable_type=bool,
            default=False),
        'notification':
        OptionalTunable(
            description=
            '\n            If set and an amount is awarded, displays a dialog to the user.\n            \n            The notification will have access to the amount awarded as a localization token. e.g. {0.Money} \n            ',
            tunable=TunableUiDialogNotificationSnippet())
    }

    def __init__(self, amount, statistic_multipliers, display_to_user,
                 notification, **kwargs):
        super().__init__(**kwargs)
        self._amount = amount
        self._statistic_multipliers = statistic_multipliers
        self._display_to_user = display_to_user
        self._random_amount = None
        self._notification = notification

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

    def get_simoleon_delta(self,
                           interaction,
                           target=DEFAULT,
                           context=DEFAULT,
                           **interaction_parameters):
        if not self._display_to_user:
            return (0, FundsSource.HOUSEHOLD)
        if not self._tests.run_tests(
                interaction.get_resolver(
                    target=target, context=context, **interaction_parameters)):
            return (0, FundsSource.HOUSEHOLD)
        sim = context.sim if context is not DEFAULT else DEFAULT
        recipients = interaction.get_participants(
            participant_type=self.subject,
            sim=sim,
            target=target,
            **interaction_parameters)
        skill_multiplier = 1 if context is DEFAULT else interaction.get_skill_multiplier(
            interaction.monetary_payout_multipliers, context.sim)
        return (self.amount * len(recipients) * skill_multiplier,
                FundsSource.HOUSEHOLD)

    def _apply_to_subject_and_target(self, subject, target, resolver):
        interaction = resolver.interaction
        if interaction is not None:
            money_liability = interaction.get_liability(
                MoneyLiability.LIABILITY_TOKEN)
            if money_liability is None:
                money_liability = MoneyLiability()
                interaction.add_liability(MoneyLiability.LIABILITY_TOKEN,
                                          money_liability)
            skill_multiplier = interaction.get_skill_multiplier(
                interaction.monetary_payout_multipliers, interaction.sim)
        else:
            money_liability = None
            skill_multiplier = 1
        subject_obj = self._get_object_from_recipient(subject)
        amount_multiplier = self._get_multiplier(
            resolver, subject_obj) * skill_multiplier
        amount = round(self.amount * amount_multiplier)
        if amount:
            if money_liability is not None:
                money_liability.amounts[self.subject] += amount
            if interaction is not None:
                interaction_category_tags = interaction.interaction_category_tags
            else:
                interaction_category_tags = None
            if amount < 0:
                subject.household.funds.try_remove_amount(
                    -amount,
                    Consts_pb2.TELEMETRY_INTERACTION_REWARD,
                    subject_obj,
                    require_full_amount=False)
            else:
                subject.household.funds.add(
                    amount,
                    Consts_pb2.TELEMETRY_INTERACTION_REWARD,
                    subject_obj,
                    tags=interaction_category_tags)
            if self._notification is not None:
                dialog = self._notification(subject, resolver=resolver)
                dialog.show_dialog(additional_tokens=(amount, ))

    def _on_apply_completed(self):
        self._random_amount = None

    def _get_display_text(self, resolver=None):
        return LocalizationHelperTuning.MONEY(*self._get_display_text_tokens())

    def _get_display_text_tokens(self, resolver=None):
        return (self.amount, )

    def _get_multiplier(self, resolver, sim):
        amount_multiplier = 1
        if self._statistic_multipliers:
            for statistic_multiplier in self._statistic_multipliers:
                amount_multiplier *= statistic_multiplier.get_multiplier(
                    resolver, sim)
        return amount_multiplier

    @property
    def amount(self):
        if self._random_amount is None:
            self._random_amount = self._amount.random_float()
        return self._random_amount