コード例 #1
0
class StyleService(Service):
    STYLE_OUTFIT_SELECTION_ODDS = TunablePercent(description='\n        Chance a situation sim will wear a styled outfit\n        ', default=20)
    STYLE_OUTFIT_DEFAULT = TunableMapping(description='\n        The mapping between gender and default outfit information\n        ', key_type=TunableEnumEntry(tunable_type=Gender, default=Gender.MALE), value_type=TunableResourceKey(description='\n            The SimInfo file to use when editing outfits.\n            ', default=None, resource_types=(sims4.resources.Types.SIMINFO,)), minlength=2)

    def __init__(self):
        self._style_outfit_data = {}

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

    def save(self, save_slot_data=None, **kwargs):
        data = GameplaySaveData_pb2.PersistableStyleService()
        for (gender, outfit_data) in self._style_outfit_data.items():
            with ProtocolBufferRollback(data.outfit_infos) as outfit_info:
                outfit_info.outfit_info_data.mannequin_id = outfit_data.sim_id
                outfit_data.save_sim_info(outfit_info.outfit_info_data)
        save_slot_data.gameplay_data.style_service = data

    def load(self, **_):
        persistence_service = services.get_persistence_service()
        save_slot_data_msg = persistence_service.get_save_slot_proto_buff()
        data = save_slot_data_msg.gameplay_data.style_service
        for outfit_info in data.outfit_infos:
            outfit_info_data = outfit_info.outfit_info_data
            outfit_data = self.get_style_outfit_data(outfit_info_data.gender, sim_id=outfit_info_data.mannequin_id)
            if persistence_service is not None:
                persisted_data = persistence_service.get_mannequin_proto_buff(outfit_info_data.mannequin_id)
                if persisted_data is not None:
                    outfit_info_data = persisted_data
            outfit_data.load_sim_info(outfit_info_data)
            outfit_data.manager = services.sim_info_manager()
            Distributor.instance().add_object(outfit_data)
            persistence_service.del_mannequin_proto_buff(outfit_info.outfit_info_data.mannequin_id)

    def get_style_outfit_data(self, gender:Gender, sim_id=0):
        outfit_data = self._style_outfit_data.get(gender)
        if outfit_data is None:
            outfit_data = SimInfoBaseWrapper(sim_id=sim_id)
            self._style_outfit_data[gender] = outfit_data
            default_outfit = self.STYLE_OUTFIT_DEFAULT.get(gender)
            outfit_data.load_from_resource(default_outfit)
            if not sim_id:
                outfit_data.manager = services.sim_info_manager()
                Distributor.instance().add_object(outfit_data)
        return outfit_data

    def clear_style_outfit_data(self, gender:Gender):
        self._style_outfit_data[gender] = None

    def try_set_style_outfit(self, sim):
        if sim.is_human and sim.sim_info.is_teen_or_older and random.random() <= self.STYLE_OUTFIT_SELECTION_ODDS:
            outfit_sim_info = self._style_outfit_data.get(sim.gender, None)
            if outfit_sim_info is not None:
                outfit = outfit_sim_info.get_random_outfit(outfit_categories=(OutfitCategory.CAREER,))
                if outfit[0] == OutfitCategory.CAREER:
                    sim.sim_info.generate_merged_outfit(outfit_sim_info, (OutfitCategory.SITUATION, 0), sim.sim_info.get_current_outfit(), outfit)
                    sim.sim_info.set_current_outfit((OutfitCategory.SITUATION, 0))
                    return True
        return False

    def has_active_style_outfit(self, gender):
        outfit_data = self._style_outfit_data.get(gender)
        return outfit_data != None
コード例 #2
0
class AgingData(HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'ages':
        TunableMapping(
            description=
            '\n            All available ages for this Sim, and any data associated with that\n            specific age.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The available age for the Sim.\n                ',
                tunable_type=Age,
                default=Age.ADULT,
                binary_type=EnumBinaryExportType.EnumUint32),
            value_type=TunableTuple(
                description=
                '\n                Any further data associated with this age.\n                ',
                transition=TunableAgingTransitionReference(
                    description=
                    '\n                    The transition data associated with this age, such as\n                    dialogs, notifications, durations, etc...\n                    ',
                    pack_safe=True),
                personality_trait_count=TunableRange(
                    description=
                    '\n                    The number of traits available to a Sim of this age.\n                    ',
                    tunable_type=int,
                    default=3,
                    minimum=0,
                    export_modes=ExportModes.All),
                cas_icon=TunableIcon(
                    description=
                    '\n                    Icon to be displayed in the ui for the age.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                cas_icon_selected=TunableIcon(
                    description=
                    '\n                    Icon to be displayed in the UI for the age when buttons are\n                    selected.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                cas_name=TunableLocalizedStringFactory(
                    description=
                    '\n                    The name to be displayed in the UI for the age.\n                    ',
                    export_modes=ExportModes.ClientBinary),
                export_class_name='AvailableAgeDataTuple'),
            minlength=1,
            tuple_name='AvailableAgeDataMapping'),
        'age_up_interaction':
        TunableReference(
            description=
            '\n            The default interaction that ages Sims up. This is called when Sims\n            auto-age or when the "Age Up" cheat is invoked.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            pack_safe=True),
        'old_age_interaction':
        TunableReference(
            description=
            '\n            The default interaction that transitions a Sim from old age to\n            death.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.INTERACTION),
            pack_safe=True),
        'old_age_npc_death_type_fallback':
        TunableEnumEntry(
            description=
            "\n            Used if the Old Age Interaction is not a death interaction.  In that\n            case, the non-instanced NPCs are not running the interaction but also\n            can't get their death type from the interaction's tuning.  This value\n            is used as a fallback.  The NPC's death type set to this value, and \n            it will effectively become a ghost.\n            ",
            tunable_type=DeathType,
            default=DeathType.NONE,
            pack_safe=True),
        'bonus_days':
        TunableMapping(
            description=
            '\n            Specify how much bonus time is added to elder Sims\n            possessing these traits.\n            ',
            key_type=TunableReference(
                description=
                '\n                The trait associated with this modifier.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.TRAIT),
                pack_safe=True),
            value_type=Tunable(
                description=
                '\n                The modifier associated with this trait.\n                ',
                tunable_type=float,
                default=0))
    }

    def get_age_transition_data(self, age):
        return self.ages[age].transition

    def get_birth_age(self):
        return min(self.ages)

    def get_lifetime_duration(self, sim_info):
        total_lifetime = sum(
            age_data.transition.get_age_duration(sim_info)
            for age_data in self.ages.values())
        aging_service = services.get_aging_service()
        total_lifetime /= aging_service.get_speed_multiple(
            aging_service.aging_speed)
        return total_lifetime

    def get_lifetime_bonus(self, sim_info):
        lifetime_duration = self.get_lifetime_duration(sim_info)
        bonus_multiplier = sum(modifier
                               for (trait,
                                    modifier) in self.bonus_days.items()
                               if sim_info.has_trait(trait))
        return lifetime_duration * bonus_multiplier

    def get_personality_trait_count(self, age):
        age_data = self.ages.get(age, None)
        if age_data is None:
            raise ValueError('{} is not in {}'.format(age, self.ages))
        return age_data.personality_trait_count

    def get_next_age(self, age):
        ages = tuple(sorted(self.ages))
        for (current_age, next_age) in zip(ages, ages[1:]):
            if current_age <= age < next_age:
                return next_age
        raise ValueError('There is no age after {}'.format(age))

    def get_previous_age(self, age):
        ages = tuple(sorted(self.ages))
        for (previous_age, current_age) in zip(ages, ages[1:]):
            if previous_age < age <= current_age:
                return previous_age
コード例 #3
0
class WhimsTracker:
    __qualname__ = 'WhimsTracker'
    MAX_GOALS = sims4.tuning.tunable.Tunable(
        description=
        '\n        The maximum number of concurrent whims a Sim can have offered at once',
        tunable_type=int,
        default=2,
        export_modes=sims4.tuning.tunable_base.ExportModes.All)
    TICKS_PER_SIM_MINUTE = date_and_time.MILLISECONDS_PER_SECOND * date_and_time.SECONDS_PER_MINUTE

    class WhimAwardTypes(enum.Int):
        __qualname__ = 'WhimsTracker.WhimAwardTypes'
        MONEY = 0
        BUFF = 1
        OBJECT = 2
        TRAIT = 3
        CASPART = 4

    SATISFACTION_STORE_ITEMS = sims4.tuning.tunable.TunableMapping(
        description=
        '\n        A list of Sim based Tunable Rewards offered from the Satisfaction Store.',
        key_type=TunableReference(description='The reward to offer .',
                                  manager=services.get_instance_manager(
                                      sims4.resources.Types.REWARD)),
        value_type=TunableTuple(
            description='A collection of data about this reward.',
            cost=Tunable(tunable_type=int, default=100),
            award_type=TunableEnumEntry(WhimAwardTypes, WhimAwardTypes.MONEY)))

    def __init__(self, sim_info):
        self._sim_info = sim_info
        self._goal_id_generator = uid.UniqueIdGenerator(1)
        self._whim_goal_proto = None
        self._active_whims = []
        for _ in range(WhimsTracker.MAX_GOALS + 1):
            self._active_whims.append(None)
        self._realized_goals = {}
        self._whimset_target_map = {}
        self._completed_goals = {}
        self._whimset_objective_map = {}
        self._test_results_map = {}
        self.active_sets = []
        self.active_chained_sets = []
        self.sets_on_cooldown = []
        self.alarm_handles = {}
        self.delay_alarm_handles = []
        self._goals_dirty = True

    def cache_whim_goal_proto(self, whim_tracker_proto, skip_load=False):
        if not skip_load:
            self._whim_goal_proto = whim_tracker_proto

    def load_whims_info_from_proto(self):
        if self._sim_info.is_npc:
            return
        if self._whim_goal_proto is None:
            return
        for goal in tuple(self._realized_goals.keys()):
            self.dismiss_whim(goal.guid64)
        active_whims_index = 0
        aspiration_mgr = services.get_instance_manager(
            sims4.resources.Types.ASPIRATION)
        whims_to_whimsets = {}
        for whim_whimset_pair in self._whim_goal_proto.whims_to_whimsets:
            whims_to_whimsets[whim_whimset_pair.
                              whim_guid64] = whim_whimset_pair.whimset_guid64
        whims_to_targets = {}
        for whim_target_pair in self._whim_goal_proto.whims_to_targets:
            whims_to_targets[
                whim_target_pair.whim_guid64] = whim_target_pair.target_id
        if len(self._whim_goal_proto.whim_goals) > WhimsTracker.MAX_GOALS + 1:
            logger.error(
                'More whims saved than the max number of goals allowed',
                owner='jjacobson')
        for goal_proto in self._whim_goal_proto.whim_goals:
            goal_seed = GoalSeedling.deserialize_from_proto(goal_proto)
            if goal_seed is None:
                pass
            goal_id = goal_seed.goal_type.guid64
            source_set = aspiration_mgr.get(whims_to_whimsets[goal_id])
            if source_set is None:
                logger.warn(
                    'Whimset for whim {} not found during whim tracker load.  Whim was probably saved with whimset that no longer exists.  Skipping whim.',
                    goal_seed.goal_type,
                    owner='jjacobson')
            goal_target_sim_info = None
            if goal_id in whims_to_targets:
                goal_target_sim_info = services.sim_info_manager().get(
                    whims_to_targets[goal_id])
                if goal_target_sim_info is None:
                    pass
            goal = goal_seed.goal_type(
                sim_info=self._sim_info,
                goal_id=self._goal_id_generator(),
                inherited_target_sim_info=goal_target_sim_info,
                count=goal_seed.count,
                reader=goal_seed.reader)
            goal.register_for_on_goal_completed_callback(
                self._on_goal_completed)
            self._realized_goals[goal] = source_set
            if goal_target_sim_info is not None:
                self._whimset_target_map[source_set] = goal_target_sim_info
            if self.get_emotion_guid(source_set) != 0:
                self._active_whims[WhimsTracker.MAX_GOALS] = goal
            else:
                self._active_whims[active_whims_index] = goal
                active_whims_index += 1
            while active_whims_index > WhimsTracker.MAX_GOALS:
                break
        if active_whims_index > 0:
            self._goals_dirty = True
        self._whim_goal_proto = None

    def save_whims_info_to_proto(self, whim_tracker_proto):
        if self._sim_info.is_npc:
            return
        for (whim, whimset) in self._realized_goals.items():
            with ProtocolBufferRollback(
                    whim_tracker_proto.whims_to_whimsets) as whim_whimset_pair:
                whim_whimset_pair.whim_guid64 = whim.guid64
                whim_whimset_pair.whimset_guid64 = whimset.guid64
        for (whimset, target) in self._whimset_target_map.items():
            while target is not None:
                with ProtocolBufferRollback(whim_tracker_proto.whims_to_targets
                                            ) as whim_target_pair:
                    for (whim, source_set) in self._realized_goals.items():
                        while source_set.guid64 == whimset.guid64:
                            whim_target_pair.whim_guid64 = whim.guid64
                            break
                    while whim_target_pair.whim_guid64 != 0:
                        whim_target_pair.target_id = target.id
        if len(self._realized_goals) > self.MAX_GOALS + 1:
            logger.error('Trying to save too many whims. Current whims: {}',
                         self._realized_goals.keys(),
                         owner='jjacobson')
        for given_goal in self._realized_goals.keys():
            goal_seed = given_goal.create_seedling()
            goal_seed.finalize_creation_for_save()
            with ProtocolBufferRollback(
                    whim_tracker_proto.whim_goals) as goal_proto:
                goal_seed.serialize_to_proto(goal_proto)

    @property
    def _sim(self):
        return self._sim_info.get_sim_instance(
            allow_hidden_flags=ALL_HIDDEN_REASONS_EXCEPT_UNINITIALIZED)

    def clean_up(self):
        for goal in self._realized_goals.keys():
            while goal is not None:
                goal.destroy()
        self._realized_goals.clear()
        for goal in self._active_whims:
            while goal is not None:
                goal.destroy()
        self._active_whims.clear()
        for alarm_handle in self.alarm_handles.values():
            alarms.cancel_alarm(alarm_handle)
        self.alarm_handles.clear()
        for delay_alarm_handle in self.delay_alarm_handles:
            alarms.cancel_alarm(delay_alarm_handle)
        self.delay_alarm_handles.clear()
        self._whimset_objective_map.clear()
        self._test_results_map.clear()

    def remove_alarm_handle(self, whim_set):
        if whim_set in self.alarm_handles:
            alarms.cancel_alarm(self.alarm_handles[whim_set])
            del self.alarm_handles[whim_set]

    def purchase_whim_award(self, reward_guid64):
        reward_instance = services.get_instance_manager(
            sims4.resources.Types.REWARD).get(reward_guid64)
        award = reward_instance
        cost = self.SATISFACTION_STORE_ITEMS[reward_instance].cost
        if self._sim_info.get_whim_bucks() < cost:
            logger.debug(
                'Attempting to purchase a whim award with insufficient funds: Cost: {}, Funds: {}',
                cost, self._sim_info.get_whim_bucks())
            return
        self._sim_info.add_whim_bucks(-cost, SetWhimBucks.PURCHASED_REWARD)
        award.give_reward(self._sim_info)

    def send_satisfaction_reward_list(self):
        msg = Sims_pb2.SatisfactionRewards()
        for (reward, data) in self.SATISFACTION_STORE_ITEMS.items():
            reward_msg = Sims_pb2.SatisfactionReward()
            reward_msg.reward_id = reward.guid64
            reward_msg.cost = data.cost
            reward_msg.affordable = True if data.cost <= self._sim_info.get_whim_bucks(
            ) else False
            reward_msg.available = reward.is_valid(self._sim_info)
            reward_msg.type = data.award_type
            msg.rewards.append(reward_msg)
        msg.sim_id = self._sim_info.id
        distributor = Distributor.instance()
        distributor.add_op_with_no_owner(
            GenericProtocolBufferOp(Operation.SIM_SATISFACTION_REWARDS, msg))

    def activate_set(self, whim_set, is_cheat=False):
        if whim_set in self.sets_on_cooldown:
            return
        if whim_set not in self.active_sets and whim_set not in self.active_chained_sets:
            if __debug__ and not is_cheat:
                self._whimset_objective_map[
                    whim_set] = self._sim_info.aspiration_tracker.latest_objective
            self.active_sets.append(whim_set)
            self.remove_alarm_handle(whim_set)
            self.alarm_handles[whim_set] = alarms.add_alarm(
                self,
                create_time_span(minutes=whim_set.active_timer),
                lambda _, this_whim_set=whim_set: self.deactivate_set(
                    this_whim_set),
                False)
        refresh_delay = whim_set.new_whim_delay.random_int()
        self.refresh_goals(request_single_goal=True,
                           request_single_delay=refresh_delay)

    def activate_chained_set(self, last_whim_set, goal,
                             inherited_target_sim_info):
        for whim_set in last_whim_set.connected_whim_sets:
            while whim_set not in self.active_chained_sets and whim_set not in self.sets_on_cooldown:
                self._whimset_target_map[whim_set] = inherited_target_sim_info
                self.active_chained_sets.append(whim_set)
                if whim_set in self.active_sets:
                    self.active_sets.remove(whim_set)
                self.remove_alarm_handle(whim_set)
                self.alarm_handles[whim_set] = alarms.add_alarm(
                    self,
                    create_time_span(minutes=whim_set.active_timer),
                    lambda _, this_whim_set=whim_set: self.deactivate_set(
                        this_whim_set),
                    False)
        if goal in last_whim_set.connected_whims:
            whim_set = last_whim_set.connected_whims[goal]
            if whim_set not in self.active_chained_sets and whim_set not in self.sets_on_cooldown:
                self._whimset_target_map[whim_set] = inherited_target_sim_info
                self.active_chained_sets.append(whim_set)
                if whim_set in self.active_sets:
                    self.active_sets.remove(whim_set)
                self.remove_alarm_handle(whim_set)
                self.alarm_handles[whim_set] = alarms.add_alarm(
                    self,
                    create_time_span(minutes=whim_set.active_timer),
                    lambda _, this_whim_set=whim_set: self.deactivate_set(
                        this_whim_set),
                    False)

    def deactivate_set(self, whim_set, from_cheat=False, from_cancel=False):
        if whim_set.timeout_retest is not None and not from_cancel:
            resolver = event_testing.resolver.SingleSimResolver(self._sim_info)
            if resolver(whim_set.timeout_retest.objective_test):
                self.remove_alarm_handle(whim_set)
                self.alarm_handles[whim_set] = alarms.add_alarm(
                    self,
                    create_time_span(minutes=whim_set.active_timer),
                    lambda _, this_whim_set=whim_set: self.deactivate_set(
                        this_whim_set),
                    False)
                return
        if whim_set in self.active_sets:
            self.active_sets.remove(whim_set)
        elif whim_set in self.active_chained_sets:
            self.active_chained_sets.remove(whim_set)
        self.remove_alarm_handle(whim_set)
        if whim_set.cooldown_timer == 0:
            self._sim_info.aspiration_tracker.reset_milestone(whim_set.guid64)
        elif whim_set not in self.sets_on_cooldown and not from_cheat:
            self.sets_on_cooldown.append(whim_set)
            self.alarm_handles[whim_set] = alarms.add_alarm(
                self,
                create_time_span(minutes=whim_set.cooldown_timer),
                lambda _, this_whim_set=whim_set: self.finish_cooldown(
                    this_whim_set),
                False)

    def finish_cooldown(self, whim_set):
        if whim_set in self.sets_on_cooldown:
            self.sets_on_cooldown.remove(whim_set)
            self._sim_info.aspiration_tracker.reset_milestone(whim_set.guid64)

    def get_priority(self, whim_set):
        if whim_set in self.active_chained_sets:
            return whim_set.chained_priority
        if whim_set in self.active_sets:
            return whim_set.activated_priority
        return whim_set.base_priority

    def get_whimset_target(self, whim_set):
        if whim_set not in self._whimset_target_map:
            return
        return self._whimset_target_map[whim_set]

    @property
    def whims_needed(self):
        normal_whim_count = 0
        for whimset in self._realized_goals.values():
            while whimset.whimset_emotion is None:
                normal_whim_count += 1
        return self.MAX_GOALS - normal_whim_count

    @property
    def emotion_whim_needed(self):
        for whimset in self._realized_goals.values():
            while whimset.whimset_emotion is not None:
                return False
        return True

    def _select_goals(self,
                      prioritized_tuned_whim_sets,
                      chosen_tuned_goals,
                      debug_goal=None,
                      debug_target=None,
                      request_single_goal=None):
        sim = self._sim
        if request_single_goal:
            goals_needed = 1
        else:
            goals_needed = self.whims_needed if self.whims_needed > 0 else 1
        goals_found = 0
        while prioritized_tuned_whim_sets:
            tuned_whim_set = sims4.random.pop_weighted(
                prioritized_tuned_whim_sets)
            weighted_goal_refs = []
            if debug_goal is None:
                if tuned_whim_set in self.sets_on_cooldown:
                    continue
                for whim in tuned_whim_set.whims:
                    while whim not in self._realized_goals:
                        weighted_goal_refs.append((whim.weight, whim.goal))
            else:
                weighted_goal_refs.append((1, debug_goal))
            whimset_target = self._whimset_target_map.get(tuned_whim_set)
            while weighted_goal_refs:
                tuned_goal = sims4.random.pop_weighted(weighted_goal_refs)
                if tuned_goal in chosen_tuned_goals:
                    continue
                is_duplicate = False
                for (goal_instance, goal_set) in self._realized_goals.items():
                    if isinstance(goal_instance, tuned_goal):
                        is_duplicate = True
                    while goal_set is tuned_whim_set:
                        is_duplicate = True
                if is_duplicate:
                    continue
                old_goal_instance = self._completed_goals.get(tuned_goal)
                if old_goal_instance is not None and old_goal_instance[
                        0].is_on_cooldown():
                    continue
                if debug_target is not None:
                    potential_target = debug_target
                    tuned_goal._target_option = SituationGoalSimTargetingOptions.DebugChoice
                else:
                    potential_target = whimset_target
                    if tuned_whim_set.force_target is not None:
                        potential_target = tuned_whim_set.force_target(
                            self._sim_info)
                        if not potential_target:
                            continue
                if potential_target is not None and isinstance(
                        potential_target, Sim):
                    potential_target = potential_target.sim_info
                pretest = tuned_goal.can_be_given_as_goal(
                    sim, None, inherited_target_sim_info=potential_target)
                if pretest:
                    chosen_tuned_goals[tuned_goal] = tuned_whim_set
                    self._whimset_target_map[tuned_whim_set] = potential_target
                    self._goals_dirty = True
                    goals_found += 1
                    break
                else:
                    while debug_goal is not None:
                        logger.error(
                            'Whim Goal {} failed pre-tests during offering: {}',
                            debug_goal,
                            pretest.reason,
                            owner='jjacobson')
                        continue
            while goals_found >= goals_needed:
                break
                continue

    def offer_goals(self,
                    debug_goal=None,
                    debug_target=None,
                    request_single_goal=False,
                    emotion_only=False):
        if not self.emotion_whim_needed and self.whims_needed == 0:
            return
        if self._sim_info.is_npc:
            return
        if self._sim is None:
            return
        chosen_tuned_goals = {}
        if self.whims_needed > 0:
            normal_whimset_list = services.get_instance_manager(
                sims4.resources.Types.ASPIRATION).normal_whim_sets
            prioritized_tuned_whim_sets = []
            for whim_set in normal_whimset_list:
                priority = self.get_priority(whim_set)
                while priority != 0:
                    prioritized_tuned_whim_sets.append((priority, whim_set))
            if not emotion_only:
                self._select_goals(prioritized_tuned_whim_sets,
                                   chosen_tuned_goals, debug_goal,
                                   debug_target, request_single_goal)
        if self.emotion_whim_needed:
            emotion_whimset_list = services.get_instance_manager(
                sims4.resources.Types.ASPIRATION).emotion_whim_sets
            prioritized_tuned_whim_sets = []
            for whim_set in emotion_whimset_list:
                priority = self.get_priority(whim_set)
                while priority != 0 and whim_set.whimset_emotion is self._sim_mood:
                    prioritized_tuned_whim_sets.append((priority, whim_set))
            self._select_goals(prioritized_tuned_whim_sets, chosen_tuned_goals,
                               debug_goal, debug_target)
        if self._goals_dirty:
            index = 0
            for tuned_goal in chosen_tuned_goals:
                goal_added = False
                if chosen_tuned_goals[tuned_goal].whimset_emotion is not None:
                    goal = tuned_goal(
                        sim_info=self._sim_info,
                        goal_id=self._goal_id_generator(),
                        inherited_target_sim_info=self._whimset_target_map[
                            chosen_tuned_goals[tuned_goal]])
                    self._active_whims[WhimsTracker.MAX_GOALS] = goal
                    goal_added = True
                else:
                    while index < WhimsTracker.MAX_GOALS:
                        if self._active_whims[index] is None:
                            goal = tuned_goal(
                                sim_info=self._sim_info,
                                goal_id=self._goal_id_generator(),
                                inherited_target_sim_info=self.
                                _whimset_target_map[
                                    chosen_tuned_goals[tuned_goal]])
                            self._active_whims[index] = goal
                            goal_added = True
                            break
                        index += 1
                if goal_added:
                    self._realized_goals[goal] = chosen_tuned_goals[tuned_goal]
                    goal.register_for_on_goal_completed_callback(
                        self._on_goal_completed)
                    logger.debug('Added whim for {}: {}',
                                 self._sim_info,
                                 goal,
                                 owner='jjacobson')
                else:
                    logger.error(
                        'Trying to add a whim when the active whims are already full.',
                        owner='jjacobson.')
                with telemetry_helper.begin_hook(writer,
                                                 TELEMETRY_HOOK_WHIM_EVENT,
                                                 sim=self._sim_info) as hook:
                    hook.write_int(TELEMETRY_WHIM_EVENT_TYPE,
                                   TelemetryWhimEvents.ADDED)
                    hook.write_guid(TELEMETRY_WHIM_GUID, goal.guid64)
        if len(self._realized_goals) > WhimsTracker.MAX_GOALS + 1:
            logger.error('Too many whims active.  Current Whims: {}',
                         self._realized_goals.keys(),
                         owner='jjacobson')

    def refresh_goals(self,
                      completed_goal=None,
                      debug_goal=None,
                      debug_target=None,
                      request_single_goal=False,
                      request_single_delay=0,
                      emotion_only=False):
        if completed_goal is not None:
            logger.debug('Whim completed for {}: {}',
                         self._sim_info,
                         completed_goal,
                         owner='jjacobson')
            op = distributor.ops.SetWhimComplete(completed_goal.guid64)
            Distributor.instance().add_op(self._sim_info, op)
            if completed_goal.score > 0:
                self._sim_info.add_whim_bucks(completed_goal.score,
                                              SetWhimBucks.WHIM)
            self._remove_goal_from_current_order(completed_goal)
            completed_goal.unregister_for_on_goal_completed_callback(
                self._on_goal_completed)
            del self._realized_goals[completed_goal]
            completed_goal.decommision()
        if request_single_delay == 0 or debug_goal is not None:
            self.offer_goals(debug_goal=debug_goal,
                             debug_target=debug_target,
                             request_single_goal=request_single_goal,
                             emotion_only=emotion_only)
        else:
            delay_alarm = alarms.add_alarm(
                self, create_time_span(minutes=request_single_delay),
                self._delayed_offer_goals, False)
            self.delay_alarm_handles.append(delay_alarm)
        self._send_goals_update()

    def _delayed_offer_goals(self, delay_alarm_handle):
        self.offer_goals(request_single_goal=True)
        self._send_goals_update()
        alarms.cancel_alarm(delay_alarm_handle)
        self.delay_alarm_handles.remove(delay_alarm_handle)

    def _verify_goals(self):
        desired_whims_amount = WhimsTracker.MAX_GOALS + 1
        emotion_whim_found = False
        for goal in list(self._active_whims):
            if goal is None:
                pass
            goal_whimset = self._realized_goals.get(goal, None)
            emotion_guid = self.get_emotion_guid(goal_whimset)
            while emotion_guid != 0:
                if emotion_guid != self._sim_mood.guid64 or emotion_whim_found:
                    self._remove_whim_goal(goal, goal_whimset)
                else:
                    emotion_whim_found = True
        if len(self._active_whims) > desired_whims_amount:
            for goal in list(self._active_whims):
                while goal is None:
                    self._active_whims.remove(goal)
        while len(self._active_whims) > desired_whims_amount:
            extra_goal = self._active_whims[desired_whims_amount + 1]
            if goal is None:
                self._active_whims.remove(goal)
                continue
            goal_whimset = self._realized_goals.get(goal, None)
            self._remove_whim_goal(extra_goal, goal_whimset)
        if len(self._active_whims) < desired_whims_amount:
            spots_to_fill = desired_whims_amount - len(self._active_whims)
            self._active_whims.extend([None] * spots_to_fill)
        if len(self._active_whims) != desired_whims_amount:
            logger.error(
                'Whim Goal Verification failed to prepare whim goals message for distribution. Active Whims: {}',
                self._active_whims)
            return False
        return True

    def _send_goals_update(self):
        if not self._verify_goals():
            return
        logger.debug('Sending whims update for {}.  Current active whims: {}',
                     self._sim_info,
                     self._active_whims,
                     owner='jjacobson')
        current_whims = []
        for goal in self._active_whims:
            if goal is None:
                whim_goal = DistributorOps_pb2.WhimGoal()
                current_whims.append(whim_goal)
            goal_target_id = 0
            goal_whimset = self._realized_goals[goal]
            goal_target = goal.get_required_target_sim_info()
            goal_target_id = goal_target.id if goal_target is not None else 0
            whim_goal = DistributorOps_pb2.WhimGoal()
            whim_goal.whim_guid64 = goal.guid64
            whim_goal.whim_name = goal.display_name
            whim_goal.whim_score = goal.score
            whim_goal.whim_noncancel = goal.noncancelable
            whim_goal.whim_icon_key.type = goal._icon.type
            whim_goal.whim_icon_key.group = goal._icon.group
            whim_goal.whim_icon_key.instance = goal._icon.instance
            whim_goal.whim_goal_count = goal.max_iterations
            whim_goal.whim_current_count = goal.completed_iterations
            whim_goal.whim_target_sim = goal_target_id
            whim_goal.whim_tooltip = goal.tooltip
            whim_goal.whim_mood_guid64 = self.get_emotion_guid(goal_whimset)
            whim_goal.whim_tooltip_reason = goal_whimset.whim_reason(
                *goal.get_localization_tokens())
            current_whims.append(whim_goal)
        if self._goals_dirty:
            self._sim_info.current_whims = current_whims
            self._goals_dirty = False

    def get_emotion_guid(self, whimset):
        if whimset is None or whimset.whimset_emotion is None:
            return 0
        return whimset.whimset_emotion.guid64

    def get_goal_info(self):
        if self._realized_goals is None:
            return
        return list(self._realized_goals.keys())

    def get_goal_set(self, goal):
        if goal in self._realized_goals:
            return self._realized_goals[goal]

    def get_completed_goal_info(self):
        return self._completed_goals.keys()

    @property
    def _sim_mood(self):
        return self._sim_info.get_component('buffs_component').get_mood()

    def _on_goal_completed(self, goal, goal_completed):
        if not goal_completed:
            self._goals_dirty = True
            self._send_goals_update()
            return
        services.get_event_manager().process_event(
            test_events.TestEvent.WhimCompleted,
            sim_info=self._sim_info,
            whim_completed=goal)
        with telemetry_helper.begin_hook(writer,
                                         TELEMETRY_HOOK_WHIM_EVENT,
                                         sim=self._sim_info) as hook:
            hook.write_int(TELEMETRY_WHIM_EVENT_TYPE,
                           TelemetryWhimEvents.COMPLETED)
            hook.write_guid(TELEMETRY_WHIM_GUID, goal.guid64)
        prev_goal_set = self._realized_goals.get(goal, None)
        self._completed_goals[type(goal)] = (goal, prev_goal_set)
        inherited_target_sim_info = goal._get_actual_target_sim_info()
        refresh_delay = prev_goal_set.new_whim_delay.random_int()
        if prev_goal_set not in prev_goal_set.connected_whim_sets:
            self.deactivate_set(prev_goal_set)
        self.activate_chained_set(prev_goal_set, goal,
                                  inherited_target_sim_info)
        self._goals_dirty = True
        logger.debug('Goal completed: {}, from Whim Set: {}',
                     goal.__class__.__name__,
                     self._realized_goals[goal].__name__)
        self.refresh_goals(goal,
                           request_single_goal=True,
                           request_single_delay=refresh_delay)

    def dismiss_whim(self, whim_guid64):
        this_goal = None
        emotion_only = False
        for goal in self._realized_goals.keys():
            while goal.guid64 == whim_guid64:
                this_goal = goal
                break
        refresh_delay = 0
        if this_goal is not None:
            if this_goal is self._active_whims[WhimsTracker.MAX_GOALS]:
                emotion_only = True
            prev_goal_set = self._realized_goals.get(this_goal, None)
            refresh_delay = prev_goal_set.whim_cancel_refresh_delay.random_int(
            )
            self._remove_whim_goal(this_goal, prev_goal_set)
            logger.debug('Removing whim {}: {}.',
                         self._sim_info,
                         this_goal,
                         owner='jjacobson')
        self._goals_dirty = True
        self.refresh_goals(request_single_goal=True,
                           request_single_delay=refresh_delay,
                           emotion_only=emotion_only)

    def _remove_whim_goal(self, whim_goal, whim_goal_set):
        with telemetry_helper.begin_hook(writer,
                                         TELEMETRY_HOOK_WHIM_EVENT,
                                         sim=self._sim_info) as hook:
            hook.write_int(TELEMETRY_WHIM_EVENT_TYPE,
                           TelemetryWhimEvents.CANCELED)
            hook.write_guid(TELEMETRY_WHIM_GUID, whim_goal.guid64)
        self.deactivate_set(whim_goal_set, from_cancel=True)
        whim_goal.unregister_for_on_goal_completed_callback(
            self._on_goal_completed)
        self._remove_goal_from_current_order(whim_goal)
        if __debug__ and whim_goal.__class__ in self._test_results_map:
            del self._test_results_map[whim_goal.__class__]
        if whim_goal in self._realized_goals:
            del self._realized_goals[whim_goal]
        whim_goal.decommision()

    def _dismiss_emotion_whim(self):
        if len(self._active_whims) != 0:
            emotion_whim = self._active_whims[WhimsTracker.MAX_GOALS]
            if emotion_whim is not None:
                whimset = self._realized_goals[emotion_whim]
                if whimset.whimset_emotion is not self._sim_mood:
                    emotion_guid64 = emotion_whim.guid64
                    with telemetry_helper.begin_hook(
                            writer, TELEMETRY_HOOK_WHIM_EVENT,
                            sim=self._sim_info) as hook:
                        hook.write_int(TELEMETRY_WHIM_EVENT_TYPE,
                                       TelemetryWhimEvents.NO_LONGER_AVAILABLE)
                        hook.write_guid(TELEMETRY_WHIM_GUID, emotion_guid64)
                    self.dismiss_whim(emotion_guid64)
                    return
            self._goals_dirty = True
            self._send_goals_update()

    def refresh_emotion_whim(self):
        self._dismiss_emotion_whim()
        self.offer_goals(emotion_only=True)

    def force_whim_complete(self, whim, target_sim=None):
        goals = list(self._realized_goals.keys())
        for goal in goals:
            while whim.guid64 is goal.guid64:
                goal.debug_force_complete(target_sim=target_sim)

    def force_whim(self, whim, target=None):
        for active_set in self.active_sets:
            self.deactivate_set(active_set, from_cheat=True)
        goals = list(self._realized_goals.keys())
        for goal in goals:
            goal.unregister_for_on_goal_completed_callback(
                self._on_goal_completed)
            self._remove_goal_from_current_order(goal)
            if goal.__class__ in self._test_results_map:
                del self._test_results_map[goal.__class__]
            del self._realized_goals[goal]
            goal.decommision()
        self.refresh_goals(completed_goal=None,
                           debug_goal=whim,
                           debug_target=target)

    def force_whimset(self, whimset):
        self.activate_set(whimset, is_cheat=True)

    def force_whim_from_whimset(self, whimset):
        for active_set in self.active_sets:
            self.deactivate_set(active_set, from_cheat=True)
        goals = list(self._realized_goals.keys())
        for goal in goals:
            goal.unregister_for_on_goal_completed_callback(
                self._on_goal_completed)
            self._remove_goal_from_current_order(goal)
            if goal.__class__ in self._test_results_map:
                del self._test_results_map[goal.__class__]
            del self._realized_goals[goal]
            goal.decommision()
        self.force_whimset(whimset)

    def _remove_goal_from_current_order(self, goal):
        index = 0
        while index <= WhimsTracker.MAX_GOALS:
            if self._active_whims[index] is goal:
                self._active_whims[index] = None
                break
            index += 1

    def validate_goals(self):
        sim = self._sim_info.get_sim_instance()
        if sim is None:
            return
        for whim in tuple(self._active_whims):
            if whim is None:
                pass
            required_sim_info = whim.get_required_target_sim_info()
            while not whim.can_be_given_as_goal(
                    sim, None, inherited_target_sim_info=required_sim_info):
                self.dismiss_whim(whim.guid64)
コード例 #4
0
class WeatherTest(HasTunableSingletonFactory, AutoFactoryInit, BaseTest):
    class TunableWeatherTestTuple(TunableTuple):
        def __init__(self, minimum_range=0, **kwargs):
            super().__init__(
                range=TunableInterval(
                    description=
                    '\n                    Range to return true.\n                    ',
                    tunable_type=Tunable100ConvertRange,
                    minimum=minimum_range,
                    maximum=100,
                    default_lower=0,
                    default_upper=100),
                zero_is_true=Tunable(
                    description=
                    '\n                    If checked, will return True if amount is 0.\n                    If unchecked, will return False if amount is 0.\n                    \n                    Even if inside (or outside) specified range.\n                    ',
                    tunable_type=bool,
                    default=False),
                **kwargs)

    FACTORY_TUNABLES = {
        'temperature':
        OptionalTunable(
            TunableTuple(
                temperature=TunableEnumSet(
                    description=
                    '\n                    The temperature(s) we must be in to pass the test\n                    ',
                    minlength=1,
                    enum_type=Temperature),
                invert=Tunable(
                    description=
                    '\n                    If checked must NOT be one of the specified temperatures\n                    ',
                    tunable_type=bool,
                    default=False))),
        'precipitation':
        OptionalTunable(
            TunableWeatherTestTuple(
                description=
                '\n                Specify amount and type of precipitation\n                ',
                precipitation_type=TunableEnumEntry(
                    description=
                    '\n                    The type of precipitation we are testing\n                    ',
                    tunable_type=PrecipitationType,
                    default=PrecipitationType.RAIN))),
        'lightning':
        OptionalTunable(
            Tunable(
                description=
                '\n                If checked must be lightning.\n                If unchecked must not be.\n                ',
                tunable_type=bool,
                default=True)),
        'thunder':
        OptionalTunable(
            Tunable(
                description=
                '\n                If checked must be thundering.\n                If unchecked must not be.\n                ',
                tunable_type=bool,
                default=True)),
        'water_freeze':
        OptionalTunable(
            TunableWeatherTestTuple(
                description=
                '\n                Specify amount water (ponds, etc.) is frozen\n                '
            )),
        'wind':
        OptionalTunable(
            TunableWeatherTestTuple(
                description=
                '\n                Specify amount of wind.\n                ')
        ),
        'cloud_state':
        OptionalTunable(
            TunableTuple(
                whitelist=TunableEnumSet(
                    description=
                    '\n                    If any are specified, the value of one must be > the\n                    whitelist threshold for the test to pass.\n                    \n                    If weather_service is not running, test will pass if\n                    partly_cloudy is in white list (or whitelist is empty) but \n                    not black list.\n                    ',
                    enum_type=CloudType),
                whitelist_threshold=Tunable100ConvertRange(
                    description=
                    '\n                    The value of a whitelist cloudtype must be above this \n                    threshold to pass.\n                    ',
                    default=0,
                    minimum=0,
                    maximum=100),
                blacklist=TunableEnumSet(
                    description=
                    '\n                    If any are specified, the value of all must be <= the\n                    blacklist threshold for the test to pass.\n                    \n                    If weather_service is not running, test will pass if\n                    partly_cloudy is in white list (or whitelist is empty) but \n                    not black list.\n                    ',
                    enum_type=CloudType),
                blacklist_threshold=Tunable100ConvertRange(
                    description=
                    '\n                    The maximum value for each of the blacklist cloud types for \n                    the test to pass.\n                    ',
                    default=0,
                    minimum=0,
                    maximum=100))),
        'ground_cover':
        OptionalTunable(
            TunableWeatherTestTuple(
                description=
                '\n                Specify amount and type of ground cover\n                ',
                minimum_range=-100,
                cover_type=TunableEnumEntry(
                    description=
                    '\n                    The type of precipitation we are testing\n                    ',
                    tunable_type=GroundCoverType,
                    default=GroundCoverType.RAIN_ACCUMULATION)))
    }

    def get_expected_args(self):
        return {}

    def _get_processed_weather_element_value(self, element_type, service,
                                             time):
        if service is not None:
            return service.get_weather_element_value(element_type, time=time)
        return 0

    @cached_test
    def __call__(self):
        weather_service = services.weather_service()
        time = services.time_service().sim_now
        if self.temperature is not None:
            if weather_service is not None:
                current_temp = Temperature(
                    weather_service.get_weather_element_value(
                        WeatherEffectType.TEMPERATURE,
                        time=time,
                        default=Temperature.WARM))
            else:
                current_temp = Temperature.WARM
            if self.temperature.invert:
                if current_temp in self.temperature.temperature:
                    return TestResult(
                        False,
                        'Temperature ({}) is an invalid temperature',
                        current_temp,
                        tooltip=self.tooltip)
            elif current_temp not in self.temperature.temperature:
                return TestResult(
                    False,
                    'Temperature ({}) is not a valid temperature',
                    current_temp,
                    tooltip=self.tooltip)
        if self.cloud_state is not None:
            if weather_service is None:
                if self.cloud_state.whitelist and CloudType.PARTLY_CLOUDY not in self.cloud_state.whitelist:
                    return TestResult(
                        False,
                        "Whitelist specified and CloudType.PARTLY_CLOUDY isn't in it",
                        tooltip=self.tooltip)
                if CloudType.PARTLY_CLOUDY in self.cloud_state.blacklist:
                    return TestResult(
                        False,
                        'Current cloud type CloudType.PARTLY_CLOUDY is in blacklist',
                        tooltip=self.tooltip)
            else:
                if self.cloud_state.whitelist:
                    threshold = self.cloud_state.whitelist_threshold
                    for cloud_type in self.cloud_state.whitelist:
                        if weather_service.get_weather_element_value(
                                cloud_type, time=time) > threshold:
                            break
                    else:
                        return TestResult(
                            False,
                            'Whitelist specified and no specified cloud type is above the threshold',
                            tooltip=self.tooltip)
                threshold = self.cloud_state.blacklist_threshold
                for cloud_type in self.cloud_state.blacklist:
                    if weather_service.get_weather_element_value(
                            cloud_type, time=time) > threshold:
                        return TestResult(
                            False,
                            'Cloud type {} is in the blacklist and above the threshold',
                            cloud_type,
                            tooltip=self.tooltip)
        if self.precipitation is not None:
            precip = self._get_processed_weather_element_value(
                self.precipitation.precipitation_type, weather_service, time)
            if precip == 0:
                if not self.precipitation.zero_is_true:
                    return TestResult(False,
                                      'Must not be 0 precipitation',
                                      tooltip=self.tooltip)
            elif precip not in self.precipitation.range:
                return TestResult(
                    False,
                    'Precipitation outside acceptable range, currently: {}',
                    precip,
                    tooltip=self.tooltip)
        if self.lightning is not None:
            lightning = self._get_processed_weather_element_value(
                WeatherEffectType.LIGHTNING, weather_service, time)
            if lightning == 0:
                if self.lightning:
                    return TestResult(False,
                                      'Must not be 0 lightning',
                                      tooltip=self.tooltip)
            elif not self.lightning:
                return TestResult(False,
                                  'Must be 0 lightning',
                                  tooltip=self.tooltip)
        if self.thunder is not None:
            thunder = self._get_processed_weather_element_value(
                WeatherEffectType.THUNDER, weather_service, time)
            if thunder == 0:
                if self.thunder:
                    return TestResult(False,
                                      'Must not be 0 thundering',
                                      tooltip=self.tooltip)
            elif not self.thunder:
                return TestResult(False,
                                  'Must be 0 thundering',
                                  tooltip=self.tooltip)
        if self.water_freeze is not None:
            freeze = self._get_processed_weather_element_value(
                WeatherEffectType.WATER_FROZEN, weather_service, time)
            if freeze == 0:
                if not self.water_freeze.zero_is_true:
                    return TestResult(False,
                                      'Must not be 0 water freeze',
                                      tooltip=self.tooltip)
            elif freeze not in self.water_freeze.range:
                return TestResult(
                    False,
                    'Water Freeze outside acceptable range, currently: {}',
                    freeze,
                    tooltip=self.tooltip)
        if self.wind is not None:
            wind = self._get_processed_weather_element_value(
                WeatherEffectType.WIND, weather_service, time)
            if wind == 0:
                if not self.wind.zero_is_true:
                    return TestResult(False,
                                      'Must not be 0 wind',
                                      tooltip=self.tooltip)
            elif wind not in self.wind.range:
                return TestResult(
                    False,
                    'Wind outside acceptable range, currently: {}',
                    wind,
                    tooltip=self.tooltip)
        if self.ground_cover is not None:
            cover = self._get_processed_weather_element_value(
                self.ground_cover.cover_type, weather_service, time)
            if cover == 0:
                if not self.ground_cover.zero_is_true:
                    return TestResult(False,
                                      'Must not be 0 ground cover',
                                      tooltip=self.tooltip)
            elif cover not in self.ground_cover.range:
                return TestResult(
                    False,
                    'Ground cover outside acceptable range, currently: {}',
                    cover,
                    tooltip=self.tooltip)
        return TestResult.TRUE
コード例 #5
0
class RecyclingBucksLoot(BaseTargetedLootOperation):
    FACTORY_TUNABLES = {
        'bucks_types':
        TunableList(
            description=
            '\n            The type of Bucks to grant.\n            ',
            tunable=TunableTuple(
                buck_type=TunableEnumEntry(tunable_type=BucksType,
                                           default=BucksType.INVALID),
                buck_multiplier=TunableMultiplier.TunableFactory(
                    description=
                    '\n                    Multipliers to apply only to this buck type when recycling an object.\n                    '
                ))),
        'bucks_multipliers':
        TunableMultiplier.TunableFactory(
            description=
            '\n            Multipliers to apply to all bucks amounts granted by recycling an object.\n            '
        )
    }

    def __init__(self, bucks_types, bucks_multipliers, **kwargs):
        super().__init__(**kwargs)
        self._bucks_types = bucks_types
        self._bucks_multipliers = bucks_multipliers

    def _apply_to_subject_and_target(self, subject, target, resolver):
        bucks_multiplier = self._bucks_multipliers.get_multiplier(resolver)
        for buck_type_tuning in self._bucks_types:
            amount = BucksRecycling.get_recycling_value_for_object(
                buck_type_tuning.buck_type, target)
            if amount == 0:
                continue
            final_multiplier = bucks_multiplier * buck_type_tuning.buck_multiplier.get_multiplier(
                resolver)
            amount *= final_multiplier
            tracker = BucksUtils.get_tracker_for_bucks_type(
                buck_type_tuning.buck_type,
                owner_id=subject.id,
                add_if_none=True)
            if tracker is None:
                logger.error(
                    'Attempting to apply a BucksLoot op to the subject {} of amount {} but they have no tracker for that bucks type {}.',
                    subject, amount, buck_type_tuning.buck_type)
            else:
                result = tracker.try_modify_bucks(buck_type_tuning.buck_type,
                                                  int(amount))
                if not result:
                    logger.error(
                        "Failed to modify the Sim {}'s bucks of type {} by amount {}.",
                        subject, buck_type_tuning.buck_type, self._amount)
        resolver = SingleActorAndObjectResolver(subject, target, self)
        for loot_action in target.recycling_data.recycling_loot:
            loot_action.apply_to_resolver(resolver)

    def _get_subject_household(self, subject):
        if subject.is_sim:
            return subject.household
        elif subject.household_owner_id is not None:
            return services.household_manager().get(subject.household_owner_id)

    def _get_object_inventory(self, obj):
        if obj.is_sim:
            return
        inventoryitem_component = getattr(obj, 'inventoryitem_component', None)
        if inventoryitem_component is not None:
            if inventoryitem_component.inventory_owner is not None:
                inventory = getattr(inventoryitem_component.inventory_owner,
                                    'inventory_component', None)
        return inventory
コード例 #6
0
class SimSpawner:
    SYSTEM_ACCOUNT_ID = 1
    LOCALE_MAPPING = TunableMapping(
        description=
        "\n        A mapping of locale in terms of string to a sim name language in the\n        Language enum. This allows us to use the same random sim name\n        list for multiple locales. You can add new Language enum entries\n        in sims.sim_spawner's Language\n        ",
        key_name='locale_string',
        value_name='language',
        key_type=str,
        value_type=TunableEnumEntry(Language,
                                    Language.ENGLISH,
                                    export_modes=ExportModes.All),
        tuple_name='TunableLocaleMappingTuple',
        export_modes=ExportModes.All)

    class TunableRandomNamesForLanguage(TunableTuple):
        def __init__(self):
            super().__init__(
                description=
                '\n                A list of random names to be used for a specific language.\n                ',
                last_names=TunableList(
                    description=
                    '\n                    A list of the random last names that can be assigned in CAS or\n                    to randomly generated NPCs.\n                    ',
                    tunable=Tunable(
                        description=
                        '\n                        A random last name.\n                        ',
                        tunable_type=str,
                        default=''),
                    unique_entries=True),
                female_last_names=TunableList(
                    description=
                    "\n                    If the specified languages differentiate last names\n                    according to gender, this list has to be non-empty. For\n                    every last name specified in the 'last_names' list, there\n                    must be a corresponding last name in this list.\n                    \n                    Randomly generated NPCs and NPC offspring will select the\n                    corresponding female version if necessary.\n                    ",
                    tunable=Tunable(
                        description=
                        "\n                        The female version of the last name at the corresponding\n                        index in the 'last_name' list.\n                        ",
                        tunable_type=str,
                        default=''),
                    unique_entries=True),
                female_first_names=TunableList(
                    description=
                    '\n                    A list of the random female first names that can be assigned\n                    in CAS or to randomly generated NPCs.\n                    ',
                    tunable=Tunable(
                        description=
                        '\n                        A random female first name.\n                        ',
                        tunable_type=str,
                        default=''),
                    unique_entries=True),
                male_first_names=TunableList(
                    description=
                    '\n                    A list of the random male first names that can be assigned\n                    in CAS or to randomly generated NPCs.\n                    ',
                    tunable=Tunable(
                        description=
                        '\n                        A random male first name.\n                        ',
                        tunable_type=str,
                        default=''),
                    unique_entries=True))

    RANDOM_NAME_TUNING = TunableMapping(
        description=
        "\n        A mapping of sim name language to lists of random family name and first\n        names appropriate for that language. This is used to generate random sim\n        names appropriate for each account's specified locale.\n        ",
        key_name='language',
        value_name='random_name_tuning',
        key_type=TunableEnumEntry(Language,
                                  Language.ENGLISH,
                                  export_modes=ExportModes.All),
        value_type=TunableRandomNamesForLanguage(),
        tuple_name='TunableRandomNameMappingTuple',
        verify_tunable_callback=verify_random_name_tuning,
        export_modes=ExportModes.All)
    SIM_NAME_TYPE_TO_LOCALE_NAMES = TunableMapping(
        description=
        '\n        A mapping of SimNameType to locale-specific names. Normally, Sims pull\n        from Random Name Tuning. But if specified with a SimNameType, they will\n        instead pull from this mapping of names.\n        ',
        key_name='name_type',
        value_name='name_type_random_names',
        key_type=TunableEnumEntry(tunable_type=SimNameType,
                                  default=SimNameType.DEFAULT,
                                  invalid_enums=(SimNameType.DEFAULT, ),
                                  binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableMapping(
            key_name='language',
            value_name='random_name_tuning',
            key_type=TunableEnumEntry(tunable_type=Language,
                                      default=Language.ENGLISH),
            value_type=TunableRandomNamesForLanguage(),
            tuple_name='TunableRandomNameMappingTuple',
            verify_tunable_callback=verify_random_name_tuning),
        tuple_name='TunableNameTypeToRandomNamesMappingTuple',
        export_modes=ExportModes.All)
    SPECIES_TO_NAME_TYPE = TunableMapping(
        description=
        '\n        A mapping of species type to the type of names to use for that species. \n        ',
        key_name='species',
        value_name='species_name_type',
        key_type=TunableEnumEntry(tunable_type=SpeciesExtended,
                                  default=SpeciesExtended.HUMAN,
                                  invalid_enums=(SpeciesExtended.INVALID, ),
                                  binary_type=EnumBinaryExportType.EnumUint32),
        value_type=TunableEnumEntry(
            tunable_type=SimNameType,
            default=SimNameType.DEFAULT,
            binary_type=EnumBinaryExportType.EnumUint32),
        tuple_name='TunableSpeciesToNameTypeMappingTuple',
        export_modes=ExportModes.All)
    NAME_TYPES_WITH_OPTIONAL_NAMES = TunableSet(
        description=
        '\n        A set of name types with optional last names. \n        ',
        tunable=TunableEnumEntry(tunable_type=SimNameType,
                                 default=SimNameType.DEFAULT,
                                 binary_type=EnumBinaryExportType.EnumUint32),
        export_modes=ExportModes.All)

    @classmethod
    def _get_random_name_tuning(cls,
                                language,
                                sim_name_type=SimNameType.DEFAULT):
        language_mapping = SimSpawner.SIM_NAME_TYPE_TO_LOCALE_NAMES.get(
            sim_name_type, SimSpawner.RANDOM_NAME_TUNING)
        tuning = language_mapping.get(language)
        if tuning is None:
            tuning = language_mapping.get(Language.ENGLISH)
        return tuning

    @classmethod
    def get_random_first_name(cls,
                              gender,
                              species=Species.HUMAN,
                              sim_name_type_override=None) -> str:
        species = SpeciesExtended.get_species(species)
        sim_name_type = SimNameType.DEFAULT
        if sim_name_type_override is not None:
            sim_name_type = sim_name_type_override
        elif species in cls.SPECIES_TO_NAME_TYPE:
            sim_name_type = cls.SPECIES_TO_NAME_TYPE[species]
        return cls._get_random_first_name(cls._get_language_for_locale(
            services.get_locale()),
                                          gender == Gender.FEMALE,
                                          sim_name_type=sim_name_type)

    @classmethod
    def _get_random_first_name(cls,
                               language,
                               is_female,
                               sim_name_type=SimNameType.DEFAULT) -> int:
        tuning = cls._get_random_name_tuning(language,
                                             sim_name_type=sim_name_type)
        name_list = tuning.female_first_names if is_female else tuning.male_first_names
        return random.choice(name_list)

    @classmethod
    def _get_random_last_name(cls,
                              language,
                              sim_name_type=SimNameType.DEFAULT) -> int:
        tuning = cls._get_random_name_tuning(language,
                                             sim_name_type=sim_name_type)
        return random.choice(tuning.last_names)

    @classmethod
    def get_last_name(cls, last_name, gender, species=Species.HUMAN) -> str:
        species = SpeciesExtended.get_species(species)
        sim_name_type = SimNameType.DEFAULT
        if species in cls.SPECIES_TO_NAME_TYPE:
            sim_name_type = cls.SPECIES_TO_NAME_TYPE[species]
        return cls._get_family_name_for_gender(cls._get_language_for_locale(
            services.get_locale()),
                                               last_name,
                                               gender == Gender.FEMALE,
                                               sim_name_type=sim_name_type)

    @classmethod
    def _get_family_name_for_gender(cls,
                                    language,
                                    family_name,
                                    is_female,
                                    sim_name_type=SimNameType.DEFAULT) -> str:
        if sim_name_type in cls.NAME_TYPES_WITH_OPTIONAL_NAMES:
            return ''
        tuning = cls._get_random_name_tuning(language,
                                             sim_name_type=sim_name_type)
        if tuning.female_last_names:
            if family_name in tuning.female_last_names:
                if is_female:
                    return family_name
                index = tuning.female_last_names.index(family_name)
                return tuning.last_names[index]
            if family_name in tuning.last_names:
                if not is_female:
                    return family_name
                else:
                    index = tuning.last_names.index(family_name)
                    return tuning.female_last_names[index]
        return family_name

    @classmethod
    def _get_language_for_locale(cls, locale) -> Language:
        language = SimSpawner.LOCALE_MAPPING.get(locale, Language.ENGLISH)
        return language

    @classmethod
    def spawn_sim(cls,
                  sim_info,
                  sim_position: sims4.math.Vector3 = None,
                  sim_location=None,
                  sim_spawner_tags=None,
                  spawn_point_option=None,
                  saved_spawner_tags=None,
                  spawn_action=None,
                  additional_fgl_search_flags=None,
                  from_load=False,
                  is_debug=False,
                  use_fgl=True,
                  spawn_point=None,
                  spawn_at_lot=True,
                  update_skewer=True,
                  **kwargs):
        if not is_debug and not not (disable_spawning_non_selectable_sims
                                     and sim_info.is_selectable):
            return False
        try:
            sim_info.set_zone_on_spawn()
            if sim_info.species in OUTFITS_TO_POPULATE_ON_SPAWN:
                sim_info.generate_unpopulated_outfits(
                    OUTFITS_TO_POPULATE_ON_SPAWN[sim_info.species])
            if not from_load:
                sim_info.spawn_point_option = spawn_point_option if spawn_point_option is not None else SpawnPointOption.SPAWN_ANY_POINT_WITH_CONSTRAINT_TAGS
            services.sim_info_manager().add_sim_info_if_not_in_manager(
                sim_info)
            success = sim_info.create_sim_instance(
                sim_position,
                sim_spawner_tags=sim_spawner_tags,
                saved_spawner_tags=saved_spawner_tags,
                spawn_action=spawn_action,
                sim_location=sim_location,
                additional_fgl_search_flags=additional_fgl_search_flags,
                from_load=from_load,
                use_fgl=use_fgl,
                spawn_point_override=spawn_point,
                spawn_at_lot=spawn_at_lot,
                **kwargs)
            if update_skewer and success and sim_info.is_selectable:
                client = services.client_manager().get_client_by_household_id(
                    sim_info.household.id)
                if client is not None:
                    client.selectable_sims.notify_dirty()
            return success
        except Exception:
            logger.exception(
                'Exception while creating sims, sim_id={}; failed',
                sim_info.id)
            return False

    @classmethod
    def load_sim(cls, sim_id, startup_location=DEFAULT):
        sim_info = services.sim_info_manager().get(sim_id)
        if sim_info is None:
            return False
        if sim_info.is_baby:
            run_baby_spawn_behavior(sim_info)
            return False
        if startup_location is DEFAULT:
            startup_location = sim_info.startup_sim_location
        return cls.spawn_sim(sim_info,
                             sim_location=startup_location,
                             from_load=True)

    @classmethod
    def _get_default_account(cls):
        client = services.client_manager().get_first_client()
        if client is not None:
            account = client.account
            if account is not None:
                return account
        account = services.account_service().get_account_by_id(
            cls.SYSTEM_ACCOUNT_ID)
        if account is not None:
            return account
        account = server.account.Account(cls.SYSTEM_ACCOUNT_ID,
                                         'SystemAccount')
        return account

    @classmethod
    def create_sim_infos(cls,
                         sim_creators,
                         household=None,
                         starting_funds=DEFAULT,
                         tgt_client=None,
                         account=None,
                         generate_deterministic_sim=False,
                         zone_id=None,
                         sim_name_type=SimNameType.DEFAULT,
                         creation_source: str = 'Unknown - create_sim_infos',
                         skip_adding_to_household=False,
                         is_debug=False):
        sim_info_list = []
        if account is None:
            account = cls._get_default_account()
        if household is None:
            if not skip_adding_to_household:
                household = sims.household.Household(
                    account, starting_funds=starting_funds)
        sim_creation_dictionaries = tuple(
            sim_creator.build_creation_dictionary()
            for sim_creator in sim_creators)
        new_sim_data = generate_household(
            sim_creation_dictionaries=sim_creation_dictionaries,
            household_name=household.name,
            generate_deterministic_sim=generate_deterministic_sim)
        zone = services.current_zone()
        world_id = zone.world_id
        if zone_id is None:
            zone_id = zone.id
        elif zone_id != 0:
            world_id = services.get_persistence_service(
            ).get_world_id_from_zone(zone_id)
        language = cls._get_language_for_locale(account.locale)
        family_name = cls._get_random_last_name(language,
                                                sim_name_type=sim_name_type)
        if household.id == 0:
            if not skip_adding_to_household:
                household.id = new_sim_data['id']
                services.household_manager().add(household)
                household.name = family_name
        for (index, sim_data) in enumerate(new_sim_data['sims']):
            sim_proto = serialization.SimData()
            sim_proto.ParseFromString(sim_data)
            first_name = sim_creators[index].first_name
            if not first_name:
                if not sim_creators[index].first_name_key:
                    if not sim_creators[index].full_name_key:
                        if sim_name_type == SimNameType.DEFAULT:
                            first_name = cls.get_random_first_name(
                                sim_proto.gender, sim_proto.extended_species)
                        else:
                            first_name = cls._get_random_first_name(
                                language,
                                sim_proto.gender == Gender.FEMALE,
                                sim_name_type=sim_name_type)
            last_name = sim_creators[index].last_name
            last_name_key = sim_creators[index].last_name_key
            if not last_name:
                if not last_name_key:
                    if last_name_key is not UNSET:
                        if not sim_creators[index].full_name_key:
                            if sim_name_type == SimNameType.DEFAULT:
                                last_name = cls.get_last_name(
                                    family_name, sim_proto.gender,
                                    sim_proto.extended_species)
                            else:
                                last_name = cls._get_family_name_for_gender(
                                    language,
                                    family_name,
                                    sim_proto.gender == Gender.FEMALE,
                                    sim_name_type=sim_name_type)
            sim_proto.first_name = first_name
            sim_proto.last_name = last_name
            sim_proto.first_name_key = sim_creators[index].first_name_key
            if last_name_key is not UNSET:
                sim_proto.last_name_key = last_name_key
            sim_proto.full_name_key = sim_creators[index].full_name_key
            sim_proto.age = sim_creators[index].age
            sim_proto.extended_species = sim_creators[index].species
            sim_proto.breed_name_key = sim_creators[index].breed_name_key
            sim_proto.zone_id = zone_id
            sim_proto.world_id = world_id
            sim_proto.household_id = household.id
            SimInfoCreationSource.save_creation_source(creation_source,
                                                       sim_proto)
            trait_ids = [
                trait.guid64 for trait in sim_creators[index].traits
                if trait.persistable
            ]
            sim_proto.attributes.trait_tracker.trait_ids.extend(trait_ids)
            sim_info = sims.sim_info.SimInfo(sim_id=sim_proto.sim_id,
                                             account=account)
            sim_info.load_sim_info(sim_proto)
            breed_tag = breed_tuning.get_breed_tag_from_tag_set(
                sim_creators[index].tag_set)
            if breed_tag != Tag.INVALID:
                breed_tuning.try_conform_sim_info_to_breed(sim_info, breed_tag)
            if sim_creators[index].resource_key:
                sim_info.load_from_resource(sim_creators[index].resource_key)
                if not sim_info.first_name:
                    sim_info.first_name = sim_proto.first_name
                if not sim_info.last_name:
                    sim_info.last_name = sim_proto.last_name
                if not sim_info.first_name_key:
                    sim_info.first_name_key = sim_proto.first_name_key
                if not sim_info.last_name_key:
                    sim_info.last_name_key = sim_proto.last_name_key
                if not sim_info.full_name_key:
                    sim_info.full_name_key = sim_proto.full_name_key
                if not sim_info.breed_name_key:
                    sim_info.breed_name_key = sim_proto.breed_name_key
            if not skip_adding_to_household:
                sim_info.assign_to_household(household)
                sim_info.save_sim()
                household.add_sim_info(sim_info)
                if tgt_client is not None and household is tgt_client.household:
                    logger.info('Added {} Sims to the current client',
                                len(sim_creators))
                    if tgt_client.active_sim is None:
                        tgt_client.set_next_sim()
                else:
                    logger.info('Added {} Sims to household ID {}.',
                                len(sim_creators), household.id)
            logger.info('Create Sims, sim_number={}; succeeded',
                        len(sim_creators))
            sim_info.push_to_relgraph()
            if gsi_handlers.sim_info_lifetime_handlers.archiver.enabled:
                gsi_handlers.sim_info_lifetime_handlers.archive_sim_info_event(
                    sim_info, 'new sim info')
            services.sim_info_manager().on_sim_info_created()
            sim_info_list.append(sim_info)
        if not skip_adding_to_household:
            household.save_data()
        if is_debug:
            services.get_zone_situation_manager().add_debug_sim_id(sim_info.id)
        return (sim_info_list, household)

    @classmethod
    def create_sims(cls,
                    sim_creators,
                    household=None,
                    tgt_client=None,
                    generate_deterministic_sim=False,
                    sim_position: sims4.math.Vector3 = None,
                    sim_spawner_tags=None,
                    account=None,
                    is_debug=False,
                    skip_offset=False,
                    additional_fgl_search_flags=None,
                    instantiate=True,
                    creation_source: str = 'Unknown - create_sims'):
        (sim_info_list, _) = cls.create_sim_infos(
            sim_creators,
            household=household,
            starting_funds=DEFAULT,
            tgt_client=tgt_client,
            account=account,
            generate_deterministic_sim=generate_deterministic_sim,
            zone_id=0,
            creation_source=creation_source)
        if not instantiate:
            return
        offset = 0.0
        for sim_info in sim_info_list:
            if sim_position is not None:
                sim_position = sims4.math.Vector3(*sim_position)
                sim_position.x += offset
                if not skip_offset:
                    offset = 2.0
                sim_position.y = terrain.get_terrain_height(
                    sim_position.x, sim_position.z)
            if is_debug:
                services.get_zone_situation_manager().add_debug_sim_id(
                    sim_info.id)
            cls.spawn_sim(
                sim_info,
                sim_position,
                sim_spawner_tags=sim_spawner_tags,
                additional_fgl_search_flags=additional_fgl_search_flags,
                is_debug=is_debug)
            client = services.client_manager().get_client_by_household_id(
                sim_info.household_id)
            if client is not None:
                client.add_selectable_sim_info(sim_info)
コード例 #7
0
class SituationDramaNode(BaseDramaNode):
    INSTANCE_TUNABLES = {
        'situation_to_run':
        TunableReference(
            description=
            '\n            The situation that this drama node will try and start.\n            ',
            manager=services.get_instance_manager(
                sims4.resources.Types.SITUATION),
            tuning_group=GroupNames.SITUATION),
        'sender_sim_info_job':
        OptionalTunable(
            description=
            '\n            When enabled, this job will be assigned to sender sim.\n            A validation error will be thrown if sender_sim_info_job is set\n            but not sender_sim_info.\n            ',
            tunable=SituationJob.TunableReference(
                description=
                '\n                The default job for the sender of this drama node.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'host_sim_info_job':
        OptionalTunable(
            description=
            '\n            If enabled, this job will be assigned to the host sim.\n            ',
            tunable=SituationJob.TunableReference(
                description=
                '\n                Situation Job for the host sim of the situation to run.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'notification':
        OptionalTunable(
            description=
            '\n            If enabled this is the notification that will be displayed after\n            the situation is started.\n            ',
            tunable=UiDialogNotification.TunableFactory(
                description=
                '\n                The notification that displays when the situation is started.\n                '
            )),
        'household_milestone':
        OptionalTunable(
            description=
            '\n            If enabled, the household milestone will reset when the situation runs.\n            Only resets if the situation is run successfully. Useful for situations\n            that are spun up when a household milestone is unlocked.\n            ',
            tunable=TunableReference(
                pack_safe=True,
                manager=services.get_instance_manager(
                    sims4.resources.Types.HOUSEHOLD_MILESTONE))),
        'homelot_only':
        Tunable(
            description=
            '\n            If checked, the situation will only be permitted to run if the active\n            sims are on their homelot.\n            ',
            tunable_type=bool,
            default=False),
        'host_sim_participant':
        OptionalTunable(
            description=
            '\n            If enabled, the participant type to use to find who the host Sim\n            of the situation will be.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The participant type to use to find the host Sim.\n                ',
                tunable_type=ParticipantTypeSingleSim,
                default=ParticipantTypeSingleSim.Actor),
            tuning_group=GroupNames.PARTICIPANT),
        'lot':
        OptionalTunable(
            description=
            '\n            If enabled, the Sim will travel to this zone for the situation.\n            ',
            tunable=TunablePackSafeLotDescription(
                description=
                '\n                A reference to the lot description file for this situation. \n                This is used for easier zone ID lookups.\n                '
            )),
        'confirm_dialog':
        OptionalTunable(
            description=
            '\n            If enabled, a dialog will appear when this drama node runs, asking\n            the user if they wish to proceed.\n            ',
            tunable=UiDialogOkCancel.TunableFactory(
                description=
                '\n                The dialog with ok/cancel buttons that will display, asking \n                the user if they want to proceed with the situation on this\n                drama node.\n                '
            )),
        'advance_notice_time':
        TunableTimeSpan(
            description=
            '\n            The amount of time between the alert and the start of the event.\n            ',
            default_minutes=0,
            default_hours=0,
            default_days=0),
        'user_facing':
        Tunable(
            description=
            '\n            If this situation should be user facing or not.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classproperty
    def drama_node_type(cls):
        return DramaNodeType.SITUATION

    @classproperty
    def spawn_sims_during_zone_spin_up(cls):
        return False

    def create_calendar_alert(self):
        calendar_alert = super().create_calendar_alert()
        if self.ui_display_data:
            build_icon_info_msg(
                IconInfoData(icon_resource=self.ui_display_data.icon),
                self.ui_display_data.name, calendar_alert.calendar_icon)
        return calendar_alert

    def load(self, drama_node_proto, schedule_alarm=True):
        success = super().load(drama_node_proto, schedule_alarm=schedule_alarm)
        if success and self.ui_display_type != DramaNodeUiDisplayType.NO_UI:
            services.calendar_service().mark_on_calendar(
                self, self.advance_notice_time())
        return success

    def schedule(self,
                 resolver,
                 specific_time=None,
                 time_modifier=TimeSpan.ZERO,
                 **kwargs):
        success = super().schedule(resolver,
                                   specific_time=specific_time,
                                   time_modifier=time_modifier,
                                   **kwargs)
        if success and self.ui_display_type != DramaNodeUiDisplayType.NO_UI:
            services.calendar_service().mark_on_calendar(
                self, self.advance_notice_time())
        return success

    def get_calendar_sims(self):
        return (self._receiver_sim_info, )

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.sender_sim_info_job is not None and cls.sender_sim_info.type == DramaNodeParticipantOption.DRAMA_PARTICIPANT_OPTION_NONE:
            logger.error(
                'Setting sender sim info job but sender sim info is set to None for {}. Please make sure that sender sim info is set correctly.',
                cls)
        if cls.host_sim_info_job is not None and cls.host_sim_participant is None:
            logger.error(
                'Setting host_sim_info_job but host_sim_participant is None in {}. Please set host_sim_participant to a valid participant.',
                cls,
                owner='nsavalani')

    def _test(self, resolver, skip_run_tests=False):
        homelot_id = services.active_household().home_zone_id
        if self.homelot_only and homelot_id != services.current_zone_id():
            return TestResult(
                False,
                'Cannot run because the current zone is not the home lot.')
        if self.host_sim_participant is not None:
            host_sim_info = self._get_resolver().get_participant(
                self.host_sim_participant)
            if host_sim_info is None:
                return TestResult(
                    False, 'Cannot run because there is no host sim info.')
        return super()._test(resolver, skip_run_tests=skip_run_tests)

    def _run(self):
        if self.confirm_dialog:
            sim_info = self._receiver_sim_info
            resolver = DoubleSimResolver(self._sender_sim_info,
                                         self._receiver_sim_info)
            confirm_dialog = self.confirm_dialog(sim_info, resolver=resolver)
            confirm_dialog.show_dialog(
                on_response=self._on_confirm_dialog_response)
            return DramaNodeRunOutcome.SUCCESS_NODE_INCOMPLETE
        else:
            self._run_situation()
            return DramaNodeRunOutcome.SUCCESS_NODE_COMPLETE

    def _on_confirm_dialog_response(self, dialog):
        if dialog.accepted:
            self._run_situation()
        services.drama_scheduler_service().complete_node(self.uid)

    def _run_situation(self):
        host_sim_id = 0
        host_sim_info = None
        if self.host_sim_participant is not None:
            host_sim_info = self._get_resolver().get_participant(
                self.host_sim_participant)
            host_sim_id = host_sim_info.id
        guest_list = self.situation_to_run.get_predefined_guest_list()
        if guest_list is None:
            guest_list = SituationGuestList(invite_only=True,
                                            host_sim_id=host_sim_id)
        if self._sender_sim_info is not None and self.sender_sim_info_job is not None:
            guest_list.add_guest_info(
                SituationGuestInfo.construct_from_purpose(
                    self._sender_sim_info.id, self.sender_sim_info_job,
                    SituationInvitationPurpose.INVITED))
        if host_sim_info is not None and self.host_sim_info_job is not None:
            guest_list.add_guest_info(
                SituationGuestInfo.construct_from_purpose(
                    host_sim_info.id, self.host_sim_info_job,
                    SituationInvitationPurpose.HOSTING))
        zone_id = 0
        if self.lot is not None:
            lot_id = get_lot_id_from_instance_id(self.lot)
            zone_id = services.get_persistence_service(
            ).resolve_lot_id_into_zone_id(lot_id, ignore_neighborhood_id=True)
        situation_id = services.get_zone_situation_manager().create_situation(
            self.situation_to_run,
            guest_list=guest_list,
            spawn_sims_during_zone_spin_up=self.spawn_sims_during_zone_spin_up,
            user_facing=self.user_facing,
            zone_id=zone_id)
        if self.household_milestone and situation_id:
            active_household_milestone_tracker = services.active_household(
            ).household_milestone_tracker
            active_household_milestone_tracker.reset_milestone(
                self.household_milestone)
        if self.notification is not None:
            target_sim_id = self._sender_sim_info.id if self._sender_sim_info is not None else None
            dialog = self.notification(self._receiver_sim_info,
                                       DoubleSimResolver(
                                           self._sender_sim_info,
                                           self._receiver_sim_info),
                                       target_sim_id=target_sim_id)
            dialog.show_dialog()
        return True
コード例 #8
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)
コード例 #9
0
class Sickness(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SICKNESS)):
    INSTANCE_TUNABLES = {'diagnosis_stat': TunableReference(description='\n            Statistic we are using to track diagnostic progress for this sickness.\n            This is used for the threshold actions checks.\n            ', manager=services.get_instance_manager(Types.STATISTIC)), 'threshold_actions': TunableMapping(description='\n            After passing specific values of the diagnosis stat, perform\n            the appropriate actions.\n            ', key_type=int, value_type=TunableList(description='\n                List of actions to process when this threshold is reached \n                or passed.', tunable=_DiagnosticThresholdActions())), 'display_name': TunableLocalizedStringFactory(description="\n            The sickness's display name. This string is provided with the owning\n            Sim as its only token.\n            ", tuning_group=GroupNames.UI), 'difficulty_rating': TunableRange(description='\n            The difficulty rating for treating this sickness.\n            ', tunable_type=float, tuning_group=GroupNames.UI, default=5, minimum=0, maximum=10), 'symptoms': TunableSet(description='\n            Symptoms associated with this sickness.  When the sickness\n            is applied to a Sim, all symptoms are applied.', tunable=TunableReference(manager=services.get_instance_manager(Types.SICKNESS), class_restrictions=(Symptom,), pack_safe=True)), 'associated_buffs': TunableSet(description='\n            The associated buffs that will be added to the Sim when the sickness\n            is applied, and removed when the sickness is removed.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.BUFF), pack_safe=True)), 'associated_statistics': TunableSet(description="\n            The associated stats that will be added to the Sim when the sickness\n            is applied, and removed when the sickness is removed.\n            \n            These are added at the statistic's default value.\n            ", tunable=TunableReference(manager=services.get_instance_manager(Types.STATISTIC), pack_safe=True)), 'restrictions': TunableTestSet(description='\n            Test set specifying whether or not this sickness can be applied.\n            One set of tests must pass in order for the sickness to be valid.\n            (This is an OR of ANDS.)\n            '), 'weight': TestedSum.TunableFactory(description='\n            Weighted value of this sickness versus other valid sicknesses that\n            are possible for the Sim to apply a sickness to.\n            \n            Tests, if defined here, may adjust the weight in addition \n            to the tuned base value.\n            '), 'difficulty_rating': TunableRange(description='\n            The difficulty rating for treating this sickness.\n            ', tunable_type=float, tuning_group=GroupNames.UI, default=5, minimum=0, maximum=10), 'examination_loots': TunableMapping(description='\n            Mapping of examination result types to loots to apply\n            as a result of the interaction.\n            ', key_type=TunableEnumEntry(tunable_type=DiagnosticActionResultType, default=DiagnosticActionResultType.DEFAULT), value_type=_DiagnosticActionLoots.TunableFactory()), 'treatment_loots': TunableMapping(description='\n            Mapping of treatment result types to loots to apply\n            as a result of the interaction.\n            ', key_type=TunableEnumEntry(tunable_type=DiagnosticActionResultType, default=DiagnosticActionResultType.DEFAULT), value_type=_DiagnosticActionLoots.TunableFactory()), 'available_treatments': TunableSet(description='\n            Treatments that are available for this sickness.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.INTERACTION), pack_safe=True)), 'available_treatment_lists': TunableSet(description='\n            Treatments that are available for this sickness.\n            ', tunable=snippets.TunableAffordanceListReference()), 'correct_treatments': TunableSet(description='\n            Treatments that can cure this sickness.  These sicknesses\n            will never be ruled out as exams are performed.\n            ', tunable=TunableReference(manager=services.get_instance_manager(Types.INTERACTION), pack_safe=True)), 'sickness_tags': TunableTags(description='\n            Tags that help categorize this sickness.\n            ', filter_prefixes=('Sickness',)), 'track_in_history': Tunable(description='\n            If checked, this is tracked in sickness history.\n            ', tunable_type=bool, default=True), 'considered_sick': Tunable(description='\n            Considered as sickness.  Most sickness should have this tuned.\n            Examinations, which are pseudo-sicknesses will have this tuned false.\n            \n            If this is checked, the sickness will pass is_sick tests.\n            ', tunable_type=bool, default=True), 'distribute_manually': Tunable(description='\n            If checked, this is not distributed by the sickness service,\n            and must be done by a game system or loot.\n            ', tunable_type=bool, default=False)}

    @classmethod
    def _can_be_applied(cls, resolver=None, sim_info=None):
        if not resolver and not sim_info:
            raise ValueError('Must specify a Sim info or a resolver')
        if not resolver:
            resolver = SingleSimResolver(sim_info)
        return cls.restrictions.run_tests(resolver)

    @classmethod
    def get_sickness_weight(cls, resolver=None, sim_info=None):
        if not resolver and not sim_info:
            raise ValueError('Must specify a Sim info or a resolver')
        if not cls._can_be_applied(resolver=resolver):
            return 0
        return max(cls.weight.get_modified_value(resolver), 0)

    @classmethod
    def apply_to_sim_info(cls, sim_info, from_load=False):
        if not cls._can_be_applied(resolver=SingleSimResolver(sim_info)):
            return
        for symptom in cls.symptoms:
            symptom.apply_to_sim_info(sim_info)
        for buff in cls.associated_buffs:
            if buff.can_add(sim_info):
                if not sim_info.has_buff(buff):
                    sim_info.add_buff(buff, buff_reason=cls.display_name)
        for stat in cls.associated_statistics:
            if not sim_info.get_tracker(stat).has_statistic(stat):
                sim_info.add_statistic(stat, stat.default_value)
        if not from_load:
            sim_info.sickness_tracker.add_sickness(cls)

    @classmethod
    def remove_from_sim_info(cls, sim_info):
        if sim_info is None:
            return
        for symptom in cls.symptoms:
            symptom.remove_from_sim_info(sim_info)
        for buff in cls.associated_buffs:
            sim_info.remove_buff_by_type(buff)
        for stat in cls.associated_statistics:
            sim_info.remove_statistic(stat)
        sim_info.remove_statistic(cls.diagnosis_stat)
        if sim_info.has_sickness(cls):
            sim_info.sickness_tracker.remove_sickness()

    @classmethod
    def is_available_treatment(cls, affordance):
        return affordance in itertools.chain(cls.available_treatments, *cls.available_treatment_lists)

    @classmethod
    def is_correct_treatment(cls, affordance):
        return affordance in cls.correct_treatments

    @classmethod
    def apply_loots_for_action(cls, action_type, result_type, interaction):
        loots_to_apply = None
        if action_type == SicknessDiagnosticActionType.EXAM:
            if result_type in cls.examination_loots:
                loots_to_apply = cls.examination_loots[result_type]
        elif action_type == SicknessDiagnosticActionType.TREATMENT:
            if result_type in cls.treatment_loots:
                loots_to_apply = cls.treatment_loots[result_type]
        if loots_to_apply is not None:
            loots_to_apply.apply_loots(interaction.get_resolver())
        cls._handle_threshold_actions(interaction.get_resolver())

    @classmethod
    def _handle_threshold_actions(cls, resolver):
        cls.update_diagnosis(resolver.target.sim_info, interaction=resolver.interaction)

    @classmethod
    def update_diagnosis(cls, sim_info, interaction=None):
        diagnostic_progress = sim_info.get_statistic(cls.diagnosis_stat).get_value()
        last_progress = sim_info.sickness_tracker.last_progress
        if diagnostic_progress == last_progress:
            return
        for (threshold, actions) in cls._get_sorted_threshold_actions():
            if threshold <= last_progress:
                continue
            if diagnostic_progress < threshold:
                break
            for action in actions:
                action.perform(sim_info, interaction=interaction)
        sim_info.sickness_record_last_progress(diagnostic_progress)

    @classmethod
    def _get_sorted_threshold_actions(cls):
        return sorted(cls.threshold_actions.items(), key=operator.itemgetter(0))

    @classmethod
    def on_zone_load(cls, sim_info):
        cls.on_sim_info_loaded(sim_info)

    @classmethod
    def on_sim_info_loaded(cls, sim_info):
        if not sim_info.has_sickness_tracking():
            return
        sim_info.sickness_record_last_progress(sim_info.get_statistic(cls.diagnosis_stat).get_value())
        cls.apply_to_sim_info(sim_info, from_load=True)

    @classmethod
    def get_ordered_symptoms(cls):
        ordered_symptoms = []
        for (_, action_list) in cls._get_sorted_threshold_actions():
            for action in action_list:
                if isinstance(action, _DiscoverSymptomThresholdAction):
                    ordered_symptoms.append(action.symptom)
        return ordered_symptoms
コード例 #10
0
class SkillTagThresholdTest(HasTunableSingletonFactory, AutoFactoryInit,
                            event_testing.test_base.BaseTest):
    test_events = (TestEvent.SkillLevelChange, )

    @TunableFactory.factory_option
    def participant_type_override(participant_type_enum,
                                  participant_type_default):
        return {
            'who':
            TunableEnumEntry(
                description=
                '\n            Who or what to apply this test to.\n            ',
                tunable_type=participant_type_enum,
                default=participant_type_default)
        }

    FACTORY_TUNABLES = {
        'who':
        TunableEnumEntry(
            description=
            '\n            Who or what to apply this test to.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor),
        'skill_tag':
        TunableEnumEntry(
            description='\n            What tag to test for.\n            ',
            tunable_type=tag.Tag,
            invalid_enums=(tag.Tag.INVALID, ),
            default=tag.Tag.INVALID),
        'skill_threshold':
        TunableThreshold(
            description=
            '\n            The threshold level to test of each skill.\n            '
        ),
        'skill_quantity':
        Tunable(
            description=
            '\n            The minimum number of skills at or above this level required to pass.\n            ',
            tunable_type=int,
            default=0),
        'test_only_changed_skill':
        Tunable(
            description=
            '\n            If checked then we will only test the skill that actually changed.\n            ',
            tunable_type=bool,
            default=False)
    }

    def get_expected_args(self):
        if self.test_only_changed_skill:
            return {
                'test_targets': self.who,
                'skill': event_testing.test_constants.FROM_EVENT_DATA
            }
        return {'test_targets': self.who}

    @cached_test
    def __call__(self, test_targets=None, skill=None):
        skill_tag = self.skill_tag
        threshold = self.skill_threshold
        quantity = self.skill_quantity
        for target in test_targets:
            if skill_tag is None:
                return TestResult(False, 'Tag not present or failed to load.')
            if target is None:
                logger.error(
                    'Trying to call SkillTagThresholdTest for skill_tag {} which has target as None.',
                    skill_tag)
                return TestResult(False, 'Target({}) does not exist', self.who)
            if skill_tag is tag.Tag.INVALID:
                return TestResult(
                    False, 'Tag test is set to INVALID, aborting test.')
            if threshold.value == 0 or quantity == 0:
                return TestResult(
                    False, 'Threshold or Quantity not set, aborting test.')
            num_passed = 0
            highest_skill_value = 0
            if skill is not None:
                skills_to_check = (skill, )
            else:
                skills_to_check = target.all_skills()
            for stat in skills_to_check:
                if skill_tag in stat.tags:
                    curr_value = 0
                    if not stat.is_initial_value:
                        curr_value = stat.get_user_value()
                    if threshold.compare(curr_value):
                        num_passed += 1
                    elif curr_value > highest_skill_value:
                        highest_skill_value = curr_value
            if not num_passed >= quantity:
                if num_passed == 0 and quantity == 1:
                    return TestResultNumeric(
                        False,
                        'The number of applicable skills: {} was not high enough to pass: {}.',
                        num_passed,
                        quantity,
                        current_value=highest_skill_value,
                        goal_value=threshold.value,
                        is_money=False,
                        tooltip=self.tooltip)
                return TestResultNumeric(
                    False,
                    'The number of applicable skills: {} was not high enough to pass: {}.',
                    num_passed,
                    quantity,
                    current_value=num_passed,
                    goal_value=quantity,
                    is_money=False,
                    tooltip=self.tooltip)
        return TestResult.TRUE

    def validate_tuning_for_objective(self, objective):
        if self.skill_tag is tag.Tag.INVALID and self.skill_threshold.value == 0 and self.skill_quantity == 0:
            logger.error(
                'Invalid tuning in objective {}.  One of the following must be true: Tag must not be INVALID, Threshold Value must be greater than 0, or Quantity must be greater than 0.',
                objective)

    def goal_value(self):
        if self.skill_quantity > 1:
            return self.skill_quantity
        return self.skill_threshold.value
コード例 #11
0
class SkillRangeTest(HasTunableSingletonFactory, AutoFactoryInit,
                     event_testing.test_base.BaseTest):
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumEntry(
            description='\n            The subject of this test.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor),
        'skill':
        Skill.TunablePackSafeReference(
            description=
            '\n            The skill to test against. \n            \n            Should the Sim not have the specified skill, or should the skill not\n            be available because of pack restrictions, this Sim will be\n            considered at level 0.\n            '
        ),
        'skill_range':
        TunableVariant(
            description=
            '\n            A skill range defined by either an interval or a threshold.\n            ',
            interval=SkillInterval.TunableFactory(),
            threshold=SkillThreshold.TunableFactory(),
            default='interval'),
        'use_effective_skill_level':
        Tunable(
            description=
            "\n            If checked, then instead of using the skill's actual level, the test\n            will use the skill's effective level for the purpose of satisfying\n            the specified criteria.\n            ",
            tunable_type=bool,
            needs_tuning=True,
            default=False)
    }
    __slots__ = ('subject', 'skill', 'skill_range',
                 'use_effective_skill_level')

    def get_expected_args(self):
        return {'test_targets': self.subject}

    @property
    def skill_range_min(self):
        return self.skill_range.skill_range_min

    @property
    def skill_range_max(self):
        max_possible_level = self.skill.get_max_skill_value()
        range_max = self.skill_range.skill_range_max
        if range_max > max_possible_level:
            logger.error(
                "SkillRangeTest has a tuned skill range upper bound of {} that is higher than {}'s highest level of {}.",
                self.skill,
                range_max,
                max_possible_level,
                owner='rmccord')
        return range_max

    @cached_test
    def __call__(self, test_targets=()):
        for target in test_targets:
            if self.skill is None:
                skill_value = 0
            else:
                skill_or_skill_type = target.get_statistic(
                    self.skill, add=False) or self.skill
                if self.use_effective_skill_level and target.is_instanced():
                    skill_value = target.get_sim_instance(
                        allow_hidden_flags=ALL_HIDDEN_REASONS
                    ).get_effective_skill_level(skill_or_skill_type)
                else:
                    skill_value = skill_or_skill_type.get_user_value()
            if not self.skill_range(skill_value):
                return TestResult(False,
                                  'skill level not in desired range.',
                                  tooltip=self.tooltip)
            return TestResult.TRUE
        return TestResult(False,
                          'Sim does not have required skill.',
                          tooltip=self.tooltip)
コード例 #12
0
ファイル: jig_group.py プロジェクト: NeonOcean/Environment
class JigGroup(SideGroup):
    INSTANCE_TUNABLES = {
        'jig':
        TunableJigVariant(),
        'participant_slot_map':
        TunableMapping(
            description=
            '\n            The slot index mapping on the jig keyed by participant type.\n            ',
            key_type=TunableEnumEntry(
                description=
                '\n                The participant associated with this jig position.\n                ',
                tunable_type=ParticipantType,
                default=ParticipantType.Actor),
            value_type=Tunable(
                description=
                '\n                The slot index for this participant.\n                ',
                tunable_type=int,
                default=0)),
        'cancel_delay':
        TunableSimMinute(
            description=
            '\n            Amount of time a jig group must be inactive before it will shut\n            down.\n            ',
            default=15),
        'stay_outside':
        Tunable(
            description=
            '\n            Whether the FGL should require the jig to be outside.\n            ',
            tunable_type=bool,
            default=False)
    }
    DEFAULT_SLOT_INDEX_ACTOR = 1
    DEFAULT_SLOT_INDEX_TARGET = 0

    def __init__(self,
                 *args,
                 si=None,
                 target_sim=None,
                 participant_slot_overrides=None,
                 **kwargs):
        super().__init__(*args, si=si, target_sim=target_sim, **kwargs)
        initiating_sim = si.sim
        if initiating_sim is None or target_sim is None:
            logger.error(
                'JigGroup {} cannot init with initial sim {} or target sim {}',
                self.__class__.__name__, initiating_sim, target_sim)
            return
        self._initating_sim_ref = initiating_sim.ref(
        ) if initiating_sim is not None else None
        self._target_sim_ref = target_sim.ref(
        ) if target_sim is not None else None
        self._picked_object_ref = si.picked_object.ref(
        ) if si.picked_object is not None else None
        self.participant_slot_overrides = participant_slot_overrides
        self._sim_routing_slot_map = {}
        self._jig_polygon = None
        self._create_social_geometry()

    @classmethod
    def _can_picked_object_be_jig(cls, picked_object):
        if picked_object is None:
            return False
        if picked_object.slot is None or picked_object.slot == sims4.resources.INVALID_KEY:
            return False
        if picked_object.is_sim:
            return False
        elif picked_object.carryable_component is not None:
            return False
        return True

    @classmethod
    def _get_jig_transforms_gen(cls,
                                initiating_sim,
                                target_sim,
                                picked_object=None,
                                participant_slot_overrides=None):
        slot_map = cls.participant_slot_map if participant_slot_overrides is None else participant_slot_overrides
        actor_slot_index = slot_map.get(ParticipantType.Actor,
                                        cls.DEFAULT_SLOT_INDEX_ACTOR)
        target_slot_index = slot_map.get(ParticipantType.TargetSim,
                                         cls.DEFAULT_SLOT_INDEX_TARGET)
        if cls._can_picked_object_be_jig(picked_object):
            try:
                (actor_transform, target_transform,
                 routing_surface) = get_two_person_transforms_for_jig(
                     picked_object.definition, picked_object.transform,
                     picked_object.routing_surface, actor_slot_index,
                     target_slot_index)
                yield (actor_transform, target_transform, routing_surface, ())
                return
            except RuntimeError:
                pass
        fallback_routing_surface = None
        if initiating_sim.routing_surface != target_sim.routing_surface:
            if initiating_sim.routing_surface.type == routing.SurfaceType.SURFACETYPE_WORLD:
                fallback_routing_surface = initiating_sim.routing_surface
            else:
                fallback_routing_surface = target_sim.routing_surface
        fallback_starting_position = None
        stay_in_connectivity_group = True
        ignore_restrictions = False
        if target_sim is not None:
            if target_sim.routing_surface.type == SurfaceType.SURFACETYPE_POOL:
                fallback_routing_surface = SurfaceIdentifier(
                    target_sim.routing_surface.primary_id,
                    target_sim.routing_surface.secondary_id,
                    SurfaceType.SURFACETYPE_WORLD)
                ocean = services.terrain_service.ocean_object()
                if not ocean is None:
                    if not target_sim.in_pool:
                        if not services.active_lot().is_position_on_lot(
                                target_sim.position):
                            extended_species = target_sim.extended_species
                            age = target_sim.age
                            start_location = ocean.get_nearest_constraint_start_location(
                                extended_species, age, target_sim.position,
                                WaterDepthIntervals.WET)
                            if start_location is not None:
                                fallback_starting_position = start_location.transform.translation
                stay_in_connectivity_group = False
                ignore_restrictions = True
        reference_routing_surface = initiating_sim.routing_surface if target_sim is None else target_sim.routing_surface
        (min_water_depth, max_water_depth
         ) = OceanTuning.make_depth_bounds_safe_for_surface_and_sim(
             reference_routing_surface, initiating_sim)
        if target_sim is not None:
            (min_water_depth, max_water_depth
             ) = OceanTuning.make_depth_bounds_safe_for_surface_and_sim(
                 reference_routing_surface, target_sim, min_water_depth,
                 max_water_depth)
        if fallback_routing_surface is not None:
            (fallback_min_water_depth, fallback_max_water_depth
             ) = OceanTuning.make_depth_bounds_safe_for_surface_and_sim(
                 fallback_routing_surface, initiating_sim)
            if target_sim is not None:
                (fallback_min_water_depth, fallback_max_water_depth
                 ) = OceanTuning.make_depth_bounds_safe_for_surface_and_sim(
                     fallback_routing_surface, target_sim,
                     fallback_min_water_depth, fallback_max_water_depth)
        else:
            fallback_min_water_depth = None
            fallback_max_water_depth = None
        yield from cls.jig.get_transforms_gen(
            initiating_sim,
            target_sim,
            actor_slot_index=actor_slot_index,
            target_slot_index=target_slot_index,
            stay_outside=cls.stay_outside,
            fallback_routing_surface=fallback_routing_surface,
            fallback_min_water_depth=fallback_min_water_depth,
            fallback_max_water_depth=fallback_max_water_depth,
            fallback_starting_position=fallback_starting_position,
            stay_in_connectivity_group=stay_in_connectivity_group,
            ignore_restrictions=ignore_restrictions,
            min_water_depth=min_water_depth,
            max_water_depth=max_water_depth)

    @classmethod
    def make_constraint_default(cls,
                                actor,
                                target_sim,
                                position,
                                routing_surface,
                                participant_type=ParticipantType.Actor,
                                picked_object=None,
                                participant_slot_overrides=None):
        if participant_type not in (ParticipantType.Actor,
                                    ParticipantType.TargetSim):
            return Anywhere()
        all_transforms = []
        for (actor_transform, target_transform, routing_surface,
             _) in cls._get_jig_transforms_gen(
                 actor,
                 target_sim,
                 picked_object=picked_object,
                 participant_slot_overrides=participant_slot_overrides):
            if participant_type == ParticipantType.Actor:
                transform = actor_transform
            else:
                transform = target_transform
            if transform is None:
                continue
            all_transforms.append(
                interactions.constraints.Transform(
                    transform,
                    routing_surface=routing_surface,
                    debug_name='JigGroupConstraint'))
        if not all_transforms:
            return Nowhere('Unable to get constraints from jig.')
        return create_constraint_set(all_transforms)

    @property
    def initiating_sim(self):
        if self._initating_sim_ref is not None:
            return self._initating_sim_ref()

    @property
    def target_sim(self):
        if self._target_sim_ref is not None:
            return self._target_sim_ref()

    @property
    def picked_object(self):
        if self._picked_object_ref is not None:
            return self._picked_object_ref()

    @property
    def group_radius(self):
        if self._jig_polygon is not None:
            return self._jig_polygon.radius()
        return 0

    @property
    def jig_polygon(self):
        return self._jig_polygon

    def _create_social_geometry(self, *args, **kwargs):
        self._sim_transform_map = defaultdict(list)
        self.geometry = None
        for (sim_transform, target_transform, routing_surface,
             locked_params) in self._get_jig_transforms_gen(
                 self.initiating_sim,
                 self.target_sim,
                 picked_object=self.picked_object,
                 participant_slot_overrides=self.participant_slot_overrides):
            if not sim_transform is None:
                if target_transform is None:
                    continue
                self._sim_transform_map[self.initiating_sim].append(
                    (sim_transform, locked_params))
                self._sim_transform_map[self.target_sim].append(
                    (target_transform, ()))
        if not (self._sim_transform_map[self.initiating_sim]
                and self._sim_transform_map[self.target_sim]):
            self._constraint = Nowhere(
                'JigGroup, failed to FGL and place the jig. Sim: {}, Target Sim: {}, Picked Object: {}',
                self.initiating_sim, self.target_sim, self.picked_object)
            return
        target_forward = target_transform.transform_vector(
            sims4.math.FORWARD_AXIS)
        self._set_focus(target_transform.translation,
                        target_forward,
                        routing_surface,
                        refresh_geometry=False)
        self._initialize_constraint(notify=True)

    def _clear_social_geometry(self, *args, **kwargs):
        self._clear_social_polygon_footprint()
        return super()._clear_social_geometry(*args, **kwargs)

    def _create_social_polygon_footprint(self):
        if self.initiating_sim not in self._sim_routing_slot_map:
            return
        if self.target_sim not in self._sim_routing_slot_map:
            return
        self._clear_social_polygon_footprint()
        self._jig_polygon = self.jig.get_footprint_polygon(
            self.initiating_sim, self.target_sim,
            self._sim_routing_slot_map[self.initiating_sim][0],
            self._sim_routing_slot_map[self.target_sim][0],
            self.routing_surface)
        if isinstance(self._jig_polygon, PolygonFootprint):
            self.initiating_sim.routing_context.ignore_footprint_contour(
                self._jig_polygon.footprint_id)
            self.target_sim.routing_context.ignore_footprint_contour(
                self._jig_polygon.footprint_id)
        self.initiating_sim.on_social_geometry_changed()
        self.target_sim.on_social_geometry_changed()

    def _clear_social_polygon_footprint(self):
        if self._jig_polygon is not None:
            if isinstance(self._jig_polygon, PolygonFootprint):
                self.initiating_sim.routing_context.remove_footprint_contour_override(
                    self._jig_polygon.footprint_id)
                self.target_sim.routing_context.remove_footprint_contour_override(
                    self._jig_polygon.footprint_id)
            self._jig_polygon = None

    def _relocate_group_around_focus(self, *args, **kwargs):
        pass

    def setup_asm_default(self, asm, *args, **kwargs):
        (_, locked_params) = self._sim_routing_slot_map.get(
            self.initiating_sim, ((), ()))
        if locked_params:
            asm.update_locked_params(locked_params)
        return super().setup_asm_default(asm, *args, **kwargs)

    def _set_sim_intended_location(self, sim, *, intended_location):
        for (transform, locked_params) in self._sim_transform_map.get(sim, ()):
            if transform_almost_equal_2d(transform,
                                         intended_location.transform):
                self._sim_routing_slot_map[sim] = (transform, locked_params)
                self._create_social_polygon_footprint()
                break

    def get_constraint(self, sim):
        transforms = self._sim_transform_map.get(sim, None)
        if transforms is not None:
            all_transforms = [
                interactions.constraints.Transform(
                    transform,
                    routing_surface=self.routing_surface,
                    create_jig_fn=self._set_sim_intended_location)
                for (transform, _) in transforms
            ]
            return create_constraint_set(all_transforms)
        if sim in self._sim_transform_map:
            return Nowhere(
                "JigGroup, Sim is expected to have a transform but we didn't find a good spot for them. Sim: {}",
                sim)
        return Anywhere()

    def _make_constraint(self, *args, **kwargs):
        all_constraints = [
            self.get_constraint(sim) for sim in self._sim_transform_map
        ]
        if all_constraints:
            self._constraint = create_constraint_set(all_constraints)
        else:
            self._constraint = Anywhere()
        return self._constraint

    _create_adjustment_alarm = socials.group.SocialGroup._create_adjustment_alarm

    def _consider_adjusting_sim(self, sim=None, initial=False):
        if not initial:
            for sim in self:
                sis = self._si_registry.get(sim)
                if sis is not None and any(not si.staging for si in sis):
                    return
                for _ in self.queued_mixers_gen(sim):
                    return
            if self.time_since_interaction().in_minutes() < self.cancel_delay:
                return
            self.shutdown(FinishingType.NATURAL)
コード例 #13
0
class FishingTrapCatchMixerInteraction(FishingCatchMixerInteractionMixin,
                                       MixerInteraction):
    INSTANCE_TUNABLES = {
        'fishing_outcomes':
        TunableTuple(
            description=
            '\n            This is how we play different content depending on fishing results.\n            ',
            catch_fish_outcome_actions=TunableOutcomeActions(
                description=
                '\n                The outcome actions that will be used if a Sim catches a fish.\n                '
            ),
            catch_junk_outcome_actions=TunableOutcomeActions(
                description=
                '\n                The outcome actions that will be used if a Sim catches junk.\n                '
            ),
            catch_treasure_outcome_actions=TunableOutcomeActions(
                description=
                '\n                The outcome actions that will be used if a Sim catches treasure.\n                '
            ),
            catch_nothing_outcome_actions=TunableOutcomeActions(
                description=
                '\n                The outcome actions that will be used if a Sim catches nothing.\n                '
            ),
            tuning_group=GroupNames.CORE),
        'per_item_loots':
        TunableTuple(
            description=
            '\n            These are the loots that are applied for each and every item caught.\n            ',
            each_fish_loot=TunableList(
                description=
                '\n                A list of loots to apply for each fish caught.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    A loot to apply.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.ACTION),
                    class_restrictions=('LootActions', ),
                    pack_safe=True)),
            each_treasure_loot=TunableList(
                description=
                '\n                A list of loots to apply for each treasure item caught.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    A loot to apply.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.ACTION),
                    class_restrictions=('LootActions', ),
                    pack_safe=True)),
            each_junk_loot=TunableList(
                description=
                '\n                A list of loots to apply for each piece of junk in the trap.\n                ',
                tunable=TunableReference(
                    description=
                    '\n                    A loot to apply.\n                    ',
                    manager=services.get_instance_manager(
                        sims4.resources.Types.ACTION),
                    class_restrictions=('LootActions', ),
                    pack_safe=True)),
            tuning_group=GroupNames.CORE),
        'catch_item_notification':
        UiDialogNotification.TunableFactory(
            description=
            '\n            The notification that is displayed when a Sim successfully catches \n            something in the trap.\n            '
        ),
        'fish_header_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The string to be used as the header for the bulleted list of fish that\n            were caught.\n            '
        ),
        'treasure_header_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The string to be used as the header for the bulleted list of treasure\n            that were caught.\n            '
        ),
        'junk_notification_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The string that describes how much junk was caught, if any was caught \n            at all.\n            \n            0 - pieces of treasure that were caught.\n            '
        ),
        'fish_information_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The text of the notification that is displayed when a Sim successfully catches a fish.\n            \n            The localization tokens for the Text field are:\n            {0.String} = Fish Type/Default Name\n            {1.String} = Localized Fish Weight, see FishObject tuning to change the localized string for fish weight\n            {2.String} = Fish Value, in usual simoleon format    \n            '
        ),
        'empty_trap_text':
        TunableLocalizedStringFactory(
            description=
            '\n            The text that appears when the trap is empty.\n            '
        ),
        'bait_stored_info_category':
        TunableEnumEntry(
            description=
            '\n            The Stored Object Info Type for using to retrieve the correct\n            object data from the Store Object Info Component.\n            ',
            tunable_type=StoredObjectType,
            default=StoredObjectType.INVALID)
    }
    REMOVE_INSTANCE_TUNABLES = ('outcome', )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._buff_handle_ids = []

    def get_bait(self):
        if not self.target.has_component(STORED_OBJECT_INFO_COMPONENT):
            return
        stored_object_info_component = self.target.get_component(
            STORED_OBJECT_INFO_COMPONENT)
        bait_id = stored_object_info_component.get_stored_object_info_id(
            self.bait_stored_info_category)
        inventory_manager = services.inventory_manager()
        bait = inventory_manager.get(bait_id)
        return bait

    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

    def _interaction_start(self, _):
        self._add_bait_buffs()

    def _interaction_end(self, _):
        self._remove_bait_buffs()

    def _add_bait_buffs(self):
        bait = self.get_bait()
        if bait:
            for (tag, bait_data) in FishingTuning.BAIT_TAG_DATA_MAP.items():
                if bait.has_tag(tag):
                    self._buff_handle_ids.append(
                        self.sim.add_buff(bait_data.bait_buff))

    def _remove_bait_buffs(self):
        for handle_id in self._buff_handle_ids:
            self.sim.remove_buff(handle_id)
        self._buff_handle_ids = []

    def _build_outcome_sequence(self):
        (min_catch, max_catch) = self._get_min_max_catch()
        if min_catch <= 0:
            return
        actual_catch = random.randint(min_catch, max_catch)
        sim = self.sim
        junk_count = 0
        fish_caught = []
        treasure_caught = []
        weighted_outcomes = self._get_weighted_choices()
        while len(fish_caught) + len(
                treasure_caught) + junk_count < actual_catch:
            outcome_actions = sims4.random.weighted_random_item(
                weighted_outcomes)
            if outcome_actions is self.fishing_outcomes.catch_junk_outcome_actions:
                junk_count += 1
            elif outcome_actions is self.fishing_outcomes.catch_fish_outcome_actions:
                fish = self._get_individual_fish_catch()
                if fish is not None:
                    fish_caught.append(fish)
                    if outcome_actions is self.fishing_outcomes.catch_treasure_outcome_actions:
                        treasure = self._get_individual_treasure_catch()
                        if treasure is not None:
                            treasure_caught.append(treasure)
            elif outcome_actions is self.fishing_outcomes.catch_treasure_outcome_actions:
                treasure = self._get_individual_treasure_catch()
                if treasure is not None:
                    treasure_caught.append(treasure)
        if treasure_caught:
            outcome = InteractionOutcomeSingle(
                self.fishing_outcomes.catch_treasure_outcome_actions)
        elif fish_caught:
            outcome = InteractionOutcomeSingle(
                self.fishing_outcomes.catch_fish_outcome_actions)
        elif junk_count:
            outcome = InteractionOutcomeSingle(
                self.fishing_outcomes.catch_junk_outcome_actions)
        else:
            outcome = InteractionOutcomeSingle(
                self.fishing_outcomes.catch_nothing_outcome_actions)

        def end(_):
            if sim.is_selectable:
                resolver = self.get_resolver()
                fish_objects = []
                treasure_objects = []
                for treasure in treasure_caught:
                    treasure_object = self.create_object_and_add_to_inventory(
                        sim, treasure, False)
                    if treasure_object is not None:
                        self._apply_loots(
                            self.per_item_loots.each_treasure_loot, resolver)
                        treasure_objects.append(treasure_object)
                for fish in fish_caught:
                    fish_object = self.create_object_and_add_to_inventory(
                        sim, fish, True)
                    if fish_object is not None:
                        self._apply_loots(self.per_item_loots.each_fish_loot,
                                          resolver)
                        FishingTuning.add_bait_notebook_entry(
                            self.sim, fish, self.get_bait())
                        fish_objects.append(fish_object)
                for _ in range(junk_count):
                    self._apply_loots(self.per_item_loots.each_junk_loot,
                                      resolver)
                self._trap_catch_notification(fish_objects, treasure_objects,
                                              junk_count)

        return element_utils.build_critical_section_with_finally(
            outcome.build_elements(self, update_global_outcome_result=True),
            end)

    def _get_min_max_catch(self):
        target = self.target
        if target is None:
            logger.error(
                "Trying to determine the min/max catch when there isn't a target."
            )
            return (0, 0)
        fishing_location_component = target.fishing_location_component
        if fishing_location_component is None:
            logger.error(
                "Trying to run a FishingTrapCatchMixerInteraction on {}, which doesn't have a fishing_location_component.",
                target)
            return (0, 0)
        return fishing_location_component.get_trap_range_of_outcomes(
            self.get_bait())

    def _apply_loots(self, loot_list, resolver):
        for loot in loot_list:
            loot.apply_to_resolver(resolver)

    def _trap_catch_notification(self, fish_caught, treasure_caught,
                                 junk_count):
        final_string = None
        if treasure_caught:
            final_string = self._get_treasure_caught_text(treasure_caught)
        if fish_caught:
            fish_text = self._get_fish_caught_text(fish_caught)
            if final_string is None:
                final_string = fish_text
            else:
                final_string = LocalizationHelperTuning.NEW_LINE_LIST_STRUCTURE(
                    final_string, fish_text)
        if junk_count > 0:
            junk_text = self._get_junk_caught_text(junk_count)
            if final_string is None:
                final_string = junk_text
            else:
                final_string = LocalizationHelperTuning.NEW_LINE_LIST_STRUCTURE(
                    final_string, junk_text)
        if final_string is None:
            final_string = self.empty_trap_text()
        dialog = self.catch_item_notification(self.sim.sim_info)
        dialog.show_dialog(additional_tokens=(final_string, ))

    def _get_fish_caught_text(self, fish_caught):
        all_fish_strings = []
        for fish in fish_caught:
            type_loc_string = LocalizationHelperTuning.get_object_name(
                fish.definition)
            value_loc_string = LocalizationHelperTuning.get_money(
                fish.current_value)
            weight_loc_string = fish.get_localized_weight()
            fish_data_text = self.fish_information_text(
                weight_loc_string, type_loc_string, value_loc_string)
            all_fish_strings.append(fish_data_text)
        final_fish_text = LocalizationHelperTuning.get_bulleted_list(
            self.fish_header_text(), *all_fish_strings)
        return final_fish_text

    def _get_treasure_caught_text(self, treasure_caught):
        treasure_string = LocalizationHelperTuning.get_bulleted_list(
            self.treasure_header_text(),
            *(LocalizationHelperTuning.get_object_name(treasure)
              for treasure in treasure_caught))
        return treasure_string

    def _get_junk_caught_text(self, junk_count):
        return self.junk_notification_text(junk_count)
コード例 #14
0
class SocialThrowMixerInteraction(SocialMixerInteraction):
    INSTANCE_TUNABLES = {'throw_impact_data': OptionalTunable(description='\n            If enabled, the object thrown will trigger a reaction at a\n            specific timing of the throw on the target.\n            If disabled, throw will happen but target will not react.\n            ', tunable=TunableTuple(description='\n                Specific tuning defining the target reaction.\n                ', event_id=TunableRange(description='\n                    Id number of the event the ballistic controller will throw\n                    to trigger the reaction.\n                    ', tunable_type=int, default=123, minimum=1, maximum=1000), destroy_event_id=TunableRange(description='\n                    Id number of the event the for the thrown object to be\n                    destroyed.\n                    By default, ballistic controller has an event at 668 at the\n                    end of the throw, unless animation requests it, that\n                    should be used. \n                    ', tunable_type=int, default=668, minimum=1, maximum=1000), event_timing_offset=TunableRange(description='\n                    Offset in seconds when the event_id should trigger with\n                    reference to the ending of the throw.\n                    This means that a value of 0.2 will trigger this event 0.2\n                    seconds before the object thrown hits its target. \n                    ', tunable_type=float, default=0.2, minimum=0.0, maximum=10.0), impact_effect=OptionalTunable(description='\n                    When enabled, effect will play on the thrown object \n                    position at the time given by event_timing_offset.\n                    Effect will not be parented, this is to trigger effects\n                    like a snowball explosion etc.\n                    ', tunable=PlayEffect.TunableFactory(description='\n                        Effect to play.\n                        '), enabled_name='play_effect', disabled_name='no_effect'), asm_state_name=Tunable(description='\n                    State name that will be called on the ASM of the mixer \n                    when the impact happens.\n                    ', tunable_type=str, default=None), impact_offset=TunableMapping(description='\n                    Offsets for the impact position by age of the target Sim.\n                    ', key_type=TunableEnumEntry(description='\n                        Age for which this offset should be applied.\n                        ', tunable_type=Age, default=Age.YOUNGADULT), value_type=TunableVector3(description='\n                        Offset from the impact position where the ballistic\n                        controller should aim the object.\n                        For example, for an impact on a Sims feet, offset should \n                        be (0,0,0), but if we want a miss or another part we \n                        may want to push it higher.\n                        ', default=sims4.math.Vector3.ZERO())))), 'social_group_scoring': OptionalTunable(description='\n            If enabled, the thrower and target of this group will have an \n            additional score to be the next person that generates the social\n            adjustment.\n            The higher the value, the more likely they will move after this\n            mixer has ran.\n            ', tunable=TunableTuple(description='\n                Thrower and target tuning to affect the adjustment scoring.\n                ', thrower_score=TunableRange(description='\n                    Score to be added on the weight the thrower of this\n                    mixer to be more likely to move on the next social\n                    adjustment. \n                    ', tunable_type=int, default=0, minimum=0, maximum=10), receiver_score=TunableRange(description='\n                    Score to be added on the weight the receiver of this\n                    mixer to be more likely to move on the next social\n                    adjustment. \n                    ', tunable_type=int, default=0, minimum=0, maximum=10)))}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._throw_asm = None
        self._finished = False

    @classmethod
    def get_mixer_key_override(cls, target):
        return target.id

    def on_throw_impact(self, event_data):
        if self._throw_asm is None or self._finished:
            return
        if not self._finished and self.throw_impact_data.asm_state_name is not None:
            impact_arb = animation.arb.Arb(additional_blockers={self.sim})
            self._throw_asm.request(self.throw_impact_data.asm_state_name, impact_arb)
            distribute_arb_element(impact_arb, master=self.target)
        self._finished = True

    def on_throw_destroy(self, event_data):
        prop_id = event_data.event_data.get('event_actor_id')
        if prop_id is not None:
            if self.throw_impact_data.impact_effect is not None:
                thrown_obj = services.prop_manager().get(prop_id)
                if thrown_obj is not None:
                    fade_out_vfx = self.throw_impact_data.impact_effect(thrown_obj)
                    fade_out_vfx.start_one_shot()
            self.animation_context.destroy_prop_from_actor_id(prop_id)

    def build_basic_content(self, *args, **kwargs):
        if self.throw_impact_data is not None:
            self.store_event_handler(self.on_throw_impact, handler_id=self.throw_impact_data.event_id)
            self.store_event_handler(self.on_throw_destroy, handler_id=self.throw_impact_data.destroy_event_id)
        if self.social_group_scoring is not None:
            self.super_interaction.social_group.update_adjustment_scoring(self.sim.id, self.social_group_scoring.thrower_score)
            self.super_interaction.social_group.update_adjustment_scoring(self.target.id, self.social_group_scoring.receiver_score)
        return super().build_basic_content(*args, **kwargs)

    def _get_facing_angle(self, actor, target):
        f1 = actor.forward
        f2 = target.position - actor.position
        angle = sims4.math.vector3_angle(f1) - sims4.math.vector3_angle(f2)
        if angle > sims4.math.PI:
            angle = angle - sims4.math.TWO_PI
        elif angle < -sims4.math.PI:
            angle = sims4.math.TWO_PI + angle
        return sims4.math.rad_to_deg(angle)

    def get_asm(self, *args, **kwargs):
        self._throw_asm = super().get_asm(*args, **kwargs)
        if self._finished:
            return self._throw_asm
        position_offset = self.throw_impact_data.impact_offset.get(self.target.sim_info.age)
        if position_offset is None:
            logger.error('Age {} not supported in throw tuning for mixer {}', self.target.sim_info.age, self)
            return self._throw_asm
        self._throw_asm.set_parameter(animation_constants.ASM_THROW_ANGLE, self._get_facing_angle(self.sim, self.target))
        self._throw_asm.set_parameter(animation_constants.ASM_HIT_ANGLE, -self._get_facing_angle(self.target, self.sim))
        target_position = self.target.position + self.target.orientation.transform_vector(position_offset)
        self._throw_asm.set_parameter(animation_constants.ASM_TARGET_TRANSLATION, target_position)
        self._throw_asm.set_parameter(animation_constants.ASM_TARGET_ORIENTATION, self.target.orientation)
        if self.throw_impact_data is not None:
            self._throw_asm.set_parameter(animation_constants.ASM_SCRIPT_EVENT_ID, self.throw_impact_data.event_id)
            self._throw_asm.set_parameter(animation_constants.ASM_SCRIPT_EVENT_PLACEMENT, self.throw_impact_data.event_timing_offset)
        self._throw_asm.set_parameter(animation_constants.ASM_LANDING_SURFACE, 'None')
        return self._throw_asm
コード例 #15
0
 def participant_type_override(participant_type_enum, participant_type_default):
     return {'subject': TunableEnumEntry(description='\n                    Who or what to apply this test to\n                    ', tunable_type=participant_type_enum, default=participant_type_default)}
コード例 #16
0
class VampireNighttimeSituation(WalkbyLimitingTagsMixin,
                                SituationComplexCommon):
    INSTANCE_TUNABLES = {
        'vampire_job':
        sims4.tuning.tunable.TunableTuple(
            situation_job=SituationJob.TunableReference(
                description=
                '\n                A reference to the SituationJob used for the Sim performing the\n                vampire nighttime situation.\n                '
            ),
            arrival_state=_VampireArrivalState.TunableFactory(
                description=
                '\n                The state for telling a Sim to go and to the active lot from\n                the walkby spawn point.\n                '
            ),
            break_in_state=_BreakInState.TunableFactory(
                description=
                '\n                The state for pushing the Sim into the house depending on the\n                active powers that it has.\n                '
            ),
            bite_state=_BiteState.TunableFactory(
                description=
                '\n                The state for forcing the bite interaction on the chosen\n                target Sim.\n                '
            ),
            leave_startled_state=_LeaveStartledState.TunableFactory(
                description=
                '\n                The state for forcing the the vampire to leave.\n                '
            ),
            tuning_group=GroupNames.SITUATION),
        'vampire_discovered_buffs':
        TunableList(
            description=
            "\n            Buff's that will push the vampire to leave the situation since \n            it's been discovered by the household owners or by an anti vampire\n            object.\n            ",
            tunable=TunableBuffReference(
                description=
                '\n                Buff to make the vampire enter its discovered state.\n                ',
                pack_safe=True)),
        'sleep_category_tag':
        TunableEnumWithFilter(
            description=
            '\n            These tag values are used for testing interactions.\n            ',
            tunable_type=Tag,
            default=Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, ),
            filter_prefixes=('Interaction', )),
        'power_buck_type':
        TunableEnumEntry(
            description=
            '\n            Type of buck type for the vampire powers to be enabled for the \n            vampire trying to enter into the visiting household.\n            ',
            tunable_type=BucksType,
            default=BucksType.INVALID),
        'active_powers':
        TunableList(
            description=
            '\n            A list of Perks and buff to add if the perk is unlocked whenever\n            the vampire decides to enter the household.\n            ',
            tunable=TunableTuple(
                description=
                '\n                Tuple of perk and buff powers.\n                ',
                power=TunableReference(manager=services.get_instance_manager(
                    sims4.resources.Types.BUCKS_PERK)),
                buff_to_add=TunableBuffReference(
                    description=
                    '\n                    Temporary buff to add for the specified power to the Sim \n                    while doing the break in.\n                    '
                ))),
        'save_lock_tooltip':
        TunableLocalizedString(
            description=
            '\n            The tooltip/message to show when the player tries to save the game\n            while this situation is running. Save is locked when situation starts.\n            ',
            tuning_group=GroupNames.UI),
        'household_sim_tests':
        TunableTestSet(
            description=
            '\n            Tests to verify the Sims on the household can be valid targets\n            for the nightime visit.\n            '
        )
    }
    REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES

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

    @classmethod
    def _states(cls):
        return (SituationStateData(1,
                                   _VampireArrivalState,
                                   factory=cls.vampire_job.arrival_state),
                SituationStateData(2,
                                   _BreakInState,
                                   factory=cls.vampire_job.break_in_state),
                SituationStateData(3,
                                   _BiteState,
                                   factory=cls.vampire_job.bite_state),
                SituationStateData(4, _LeaveState),
                SituationStateData(
                    5,
                    _LeaveStartledState,
                    factory=cls.vampire_job.leave_startled_state))

    @classmethod
    def _get_tuned_job_and_default_role_state_tuples(cls):
        return [(cls.vampire_job.situation_job, cls.vampire_job.arrival_state)]

    @classmethod
    def default_job(cls):
        return cls.vampire_job.situation_job

    @classmethod
    def get_sims_expected_to_be_in_situation(cls):
        return 1

    def vampire_sim(self):
        sim = next(self.all_sims_in_job_gen(self.default_job()), None)
        return sim

    def _destroy(self):
        for sim in services.active_household().instanced_sims_gen():
            for si in sim.si_state:
                if self.sleep_category_tag in si.affordance.interaction_category_tags:
                    si.remove_liability(UNCANCELABLE_LIABILITY)
        super()._destroy()
        services.get_persistence_service().unlock_save(self)

    def register_selected_sim(self, selected_sim):
        self.selected_sim = selected_sim

    def start_situation(self):
        super().start_situation()
        self._change_state(self.vampire_job.arrival_state())

    def lock_save(self):
        services.get_persistence_service().lock_save(self)

    def get_lock_save_reason(self):
        return self.save_lock_tooltip
コード例 #17
0
ファイル: tunable.py プロジェクト: NeonOcean/Environment
 def __init__(self, **kwargs):
     super().__init__(
         situation=TunableReference(
             description=
             '\n                The Situation to start when this Interaction runs.\n                ',
             manager=services.situation_manager()),
         user_facing=Tunable(
             description=
             '\n                If checked, then the situation will be user facing (have goals, \n                and scoring).\n                \n                If not checked, then situation will not be user facing.\n                \n                This setting does not override the user option to make all\n                situations non-scoring.\n                \n                Example: \n                    Date -> Checked\n                    Invite To -> Not Checked\n                ',
             tunable_type=bool,
             default=True),
         linked_sim_participant=OptionalTunable(
             description=
             '\n                If enabled, this situation will be linked to the specified Sim.\n                ',
             tunable=TunableEnumEntry(tunable_type=ParticipantType,
                                      default=ParticipantType.Actor)),
         invite_participants=TunableMapping(
             description=
             "\n                The map to invite certain participants into the situation as\n                specified job if assigned. Otherwise will invite them as\n                situation's default job.\n                ",
             key_type=TunableEnumEntry(
                 description=
                 '\n                    The participant of who will join the situation.\n                    ',
                 tunable_type=ParticipantType,
                 default=ParticipantType.Actor),
             key_name='participants_to_invite',
             value_type=OptionalTunable(tunable=TunableList(
                 description=
                 '\n                        A list of situation jobs that can be specified.  If a\n                        single job is specified then all Sims will be given\n                        that job.  Otherwise we will loop through all of the\n                        Sims invited and give them jobs in list order.  The\n                        list will begin to be repeated if we run out of jobs.\n                        \n                        NOTE: We cannot guarantee the order of the Sims being\n                        passed in most of the time.  Use this if you want a\n                        distribution of different jobs, but without a guarantee\n                        that Sims will be assigned to each one.\n                        ',
                 tunable=TunableReference(
                     manager=services.situation_job_manager())),
                                        disabled_name='use_default_job',
                                        enabled_name='specify_job'),
             value_name='invite_to_job'),
         invite_actor=Tunable(
             description=
             '\n                If checked, then the actor of this interaction will be invited\n                in the default job. This is the common case.\n                \n                If not checked, then the actor will not be invited. The Tell\n                A Ghost Story interaction spawning a Ghost walkby is an example.\n                \n                If your situation takes care of all the sims that should be in\n                the default job itself (such as auto-invite) it will probably\n                not work if this is checked.\n                ',
             tunable_type=bool,
             default=True),
         actor_init_job=OptionalTunable(
             description=
             '\n                The Situation job actor would be assigned while join the situation.\n                ',
             tunable=TunableReference(
                 manager=services.situation_job_manager()),
             disabled_name='use_default_job',
             enabled_name='specify_job'),
         invite_picked_sims=Tunable(
             description=
             '\n                If checked then any picked sims of this interaction will be\n                invited to the default job.  This is the common case.\n                \n                If not checked, then any picked sims will not be invited.  The\n                Tell A Ghost Story interaction spawning a Ghost walkby is an\n                example.\n                \n                If your situation takes care of all the sims that should be in\n                the default job itself (such as auto-invite) it will probably\n                not work if this is checked.\n                ',
             tunable_type=bool,
             default=True),
         invite_target_sim=Tunable(
             description=
             '\n                If checked then the target sim of this interaction will be\n                invited to the default job.  This is the common case.\n                \n                If not checked, then the target sim will not be invited.  The\n                Tell A Ghost Story interaction spawning a Ghost walkby is an\n                example.\n                \n                If your situation takes care of all the sims that should be in\n                the default job itself (such as auto-invite) it will probably\n                not work if this is checked.\n                ',
             tunable_type=bool,
             default=True),
         target_init_job=OptionalTunable(
             description=
             '\n                The Situation job target would be assigned while join the situation.\n                ',
             tunable=TunableReference(
                 manager=services.situation_job_manager()),
             disabled_name='use_default_job',
             enabled_name='specify_job'),
         invite_household_sims_on_active_lot=Tunable(
             description=
             '\n                If checked then all instanced sims on the active lot will be\n                invited. This is not a common case. An example of this is\n                leaving the hospital after having a baby, bringing both sims\n                home.\n                \n                If not checked, then no additional sims will be invited.\n                \n                If your situation takes care of all the sims that should be in\n                the default job itself (such as auto-invite) it will probably\n                not work if this is checked.\n                ',
             tunable_type=bool,
             default=False),
         situation_default_target=OptionalTunable(
             description=
             '\n                If enabled, the participant of the interaction will be set as\n                the situation target object.\n                ',
             tunable=TunableEnumEntry(
                 description=
                 "\n                    The participant that will be set as the situation's default target\n                    ",
                 tunable_type=ParticipantType,
                 default=ParticipantType.Object)),
         situation_guest_info=OptionalTunable(
             description=
             '\n                By default, situation guest infos are created as an invite.\n                This overrrides that behavior.\n                ',
             tunable=SituationGuestInfoFactory()),
         description='Start a Situation as part of this Interaction.',
         **kwargs)
コード例 #18
0
class RetailManager(BusinessManager):
    NPC_STORE_FOR_SALE_TAG = TunableEnumEntry(
        description=
        '\n        Objects with this tag will be set For Sale when an NPC store is\n        visited.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        pack_safe=True)
    NPC_STORE_MANNEQUIN_TAG = TunableEnumEntry(
        description=
        '\n        Objects with this tag will have their mannequin component outfits\n        restocked any time a premade NPC store is visited.\n        ',
        tunable_type=tag.Tag,
        default=tag.Tag.INVALID,
        pack_safe=True)
    FOR_SALE_VFX = PlayEffect.TunableFactory(
        description=
        '\n        An effect that can be toggled on/off for all objects marked for sale.\n        '
    )
    ITEMS_SENT_TO_HH_INVENTORY_NOTIFICATION = TunableUiDialogNotificationSnippet(
        description=
        "\n            The notification that shows up when items are sent to the household's\n            inventory because the item that these things are slotted to are\n            sold.\n            "
    )
    NPC_STORE_ITEM_COMMODITIES_TO_MAX_ON_OPEN = TunableList(
        description=
        '\n        A list of commodities that should get maxed out on retail objects\n        when an NPC store opens.\n        ',
        tunable=Commodity.TunableReference(
            description='\n            The commodity to max out.\n            ',
            pack_safe=True))

    def __init__(self):
        super().__init__(BusinessType.RETAIL)
        self._objs_with_for_sale_vfx = {}
        self._for_sale_vfx_toggle_value = False

    def on_client_disconnect(self):
        self.remove_for_sale_vfx_from_all_objects()

    def set_advertising_type(self, advertising_type):
        commodity = RetailTuning.ADVERTISING_COMMODITY_MAP.get(
            advertising_type, None)
        if commodity is not None:
            tracker = services.active_lot().commodity_tracker
            tracker.remove_statistic(commodity)
            tracker.add_statistic(commodity)

    def get_advertising_type_for_gsi(self):
        if services.current_zone_id() == self._zone_id:
            tracker = services.active_lot().commodity_tracker
            commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values()
            return str([(c, tracker.get_value(c)) for c in commodities
                        if tracker.has_statistic(c)])
        else:
            return ''

    def get_curb_appeal(self):
        total_curb_appeal = sum(
            obj.retail_component.get_current_curb_appeal()
            for obj in RetailUtils.get_all_retail_objects())
        return total_curb_appeal + self._get_lot_advertising_commodity_sum()

    def _get_lot_advertising_commodity_sum(self):
        return sum(self._get_lot_advertising_commodity_values())

    def _get_lot_advertising_commodity_values(self):
        tracker = services.active_lot().commodity_tracker
        commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values()
        return [
            tracker.get_value(c) for c in commodities
            if tracker.has_statistic(c)
        ]

    def _fixup_placard_if_necessary(self, obj):
        obj_retail_component = obj.retail_component
        if obj_retail_component is not None and obj_retail_component.is_sold:
            obj_retail_component._change_to_placard(play_vfx=False)

    def should_automatically_close(self):
        return self.is_owner_household_active and (
            self._zone_id is not None
            and self._zone_id != services.current_zone_id())

    def _should_make_customer(self, sim_info):
        return False

    def on_protocols_loaded(self):
        self._employee_manager.reload_employee_uniforms()

    def refresh_for_sale_vfx_for_object(self, obj):
        if obj.retail_component is None:
            logger.error(
                'Attempting to toggle for sale vfx on an object {} with no retail component.',
                obj,
                owner='tastle')
            return
        show_vfx = self._for_sale_vfx_toggle_value if obj.retail_component.is_for_sale else False
        self._update_for_sale_vfx_for_object(obj, show_vfx)

    def _update_for_sale_vfx_for_object(self, obj, toggle_value):
        if obj not in self._objs_with_for_sale_vfx and toggle_value:
            self._objs_with_for_sale_vfx[obj] = self.FOR_SALE_VFX(obj)
            self._objs_with_for_sale_vfx[obj].start()
        elif obj in self._objs_with_for_sale_vfx and not toggle_value:
            obj_vfx = self._objs_with_for_sale_vfx.pop(obj)
            obj_vfx.stop()

    def remove_for_sale_vfx_from_all_objects(self):
        self._for_sale_vfx_toggle_value = False
        for obj_vfx in self._objs_with_for_sale_vfx.values():
            obj_vfx.stop()
        self._objs_with_for_sale_vfx.clear()

    def toggle_for_sale_vfx(self):
        self._for_sale_vfx_toggle_value = not self._for_sale_vfx_toggle_value
        for item in RetailUtils.all_retail_objects_gen(
                allow_sold=False, include_inventories=False):
            self._update_for_sale_vfx_for_object(
                item, self._for_sale_vfx_toggle_value)

    def update_retail_objects_commodities(self):
        for retail_obj in RetailUtils.all_retail_objects_gen(allow_sold=False):
            retail_obj.update_component_commodity_flags()

    def _open_business(self):
        super()._open_business()
        if self._owner_household_id is not None:
            self.update_retail_objects_commodities()

    def _close_business(self, play_sound=True):
        if not self._is_open:
            return
        super()._close_business(play_sound)
        if self._owner_household_id is not None:
            self.update_retail_objects_commodities()

    def get_median_item_value(self):
        values = [
            obj.retail_component.get_retail_value()
            for obj in RetailUtils.all_retail_objects_gen()
        ]
        if not values:
            return 0
        values.sort()
        count = len(values)
        midpoint = count // 2
        if count % 2:
            return values[midpoint]
        return (values[midpoint] + values[midpoint - 1]) / 2

    def should_show_no_way_to_make_money_notification(self):
        return not self.has_any_object_for_sale()

    def has_any_object_for_sale(self):
        for retail_obj in RetailUtils.all_retail_objects_gen(
                allow_not_for_sale=True):
            if retail_obj.retail_component.is_for_sale:
                return True
            if retail_obj.is_in_inventory():
                return True
        return False

    def on_zone_load(self):
        super().on_zone_load()
        for obj in RetailUtils.get_all_retail_objects():
            self._fixup_placard_if_necessary(obj)
        if services.current_zone_id() != self._zone_id:
            return
        tracker = services.active_lot().commodity_tracker
        advertising_commodities = RetailTuning.ADVERTISING_COMMODITY_MAP.values(
        )
        for advertising_commodity in advertising_commodities:
            commodity = tracker.get_statistic(advertising_commodity)
            if commodity is not None:
                commodity.decay_enabled = True

    def _open_pure_npc_store(self, is_premade):
        has_retail_obj = False
        for retail_obj in RetailUtils.all_retail_objects_gen(
                allow_not_for_sale=True):
            self._adjust_commodities_if_necessary(retail_obj)
            obj_retail_component = retail_obj.retail_component
            if obj_retail_component.is_for_sale or retail_obj.is_in_inventory(
            ):
                has_retail_obj = True
            else:
                if is_premade:
                    set_for_sale = retail_obj.has_tag(
                        self.NPC_STORE_FOR_SALE_TAG)
                else:
                    set_for_sale = obj_retail_component.is_sold
                if set_for_sale:
                    obj_retail_component.set_for_sale()
                    has_retail_obj = True
                if retail_obj.has_tag(self.NPC_STORE_MANNEQUIN_TAG):
                    self._set_up_mannequin_during_open(retail_obj)
                    has_retail_obj = True
        self.set_open(has_retail_obj)

    @classmethod
    def _set_up_mannequin_during_open(cls, mannequin):
        (current_outfit_type, _) = mannequin.get_current_outfit()
        if current_outfit_type == OutfitCategory.BATHING:
            mannequin.set_current_outfit(mannequin.get_previous_outfit())

    def _open_household_owned_npc_store(self):
        should_open = False
        for retail_obj in RetailUtils.all_retail_objects_gen(
                allow_not_for_sale=True):
            self._adjust_commodities_if_necessary(retail_obj)
            if not should_open:
                if not retail_obj.retail_component.is_not_for_sale:
                    should_open = True
        self.set_open(should_open)

    @classmethod
    def _adjust_commodities_if_necessary(cls, obj):
        for obj_commodity in cls.NPC_STORE_ITEM_COMMODITIES_TO_MAX_ON_OPEN:
            tracker = obj.get_tracker(obj_commodity)
            if tracker is not None:
                if tracker.has_statistic(obj_commodity):
                    tracker.set_max(obj_commodity)

    def show_summary_dialog(self, is_from_close=False):
        RetailSummaryDialog.show_dialog(self, is_from_close=is_from_close)

    def construct_business_message(self, msg):
        super().construct_business_message(msg)
        msg.retail_data = Business_pb2.RetailBusinessDataUpdate()

    def get_lot_name(self):
        zone_data = services.get_persistence_service().get_zone_proto_buff(
            self._zone_id)
        if zone_data is None:
            return ''
        return zone_data.name
コード例 #19
0
class SocialContextTest(HasTunableSingletonFactory, AutoFactoryInit, BaseTest):
    FACTORY_TUNABLES = {
        'participant':
        TunableEnumEntry(
            description=
            '\n            The participant against which to test social context.\n            ',
            tunable_type=ParticipantType,
            default=ParticipantType.Actor),
        'target_subject':
        TunableEnumEntry(
            description=
            "\n            The participant that must be included in participant's social group\n            in order for this test to rely solely on the participant's current\n            STC. If target_subject is not in any of participant's social groups,\n            then the STC test will only consider the prevailing STC between\n            participant and target_subject.\n            ",
            tunable_type=ParticipantType,
            default=ParticipantType.TargetSim),
        'required_set':
        TunableSet(
            description=
            "\n            A set of contexts that are required. If any context is specified,\n            the test will fail if the participant's social context is not one of\n            these entries.\n            ",
            tunable=relationships.relationship_bit.RelationshipBit.
            TunableReference(pack_safe=True)),
        'prohibited_set':
        TunableSet(
            description=
            "\n            A set of contexts that are prohibited. The test will fail if the\n            participant's social context is one of these entries.\n            ",
            tunable=relationships.relationship_bit.RelationshipBit.
            TunableReference(pack_safe=True))
    }

    @staticmethod
    @caches.cached
    def get_overall_short_term_context_bit(*sims):
        positive_stc_tracks = []
        negative_stc_tracks = []
        for (sim_a, sim_b) in itertools.combinations(sims, 2):
            stc_track = sim_a.relationship_tracker.get_relationship_prevailing_short_term_context_track(
                sim_b.id)
            if stc_track is not None:
                if stc_track.get_value() >= 0:
                    positive_stc_tracks.append(stc_track)
                else:
                    negative_stc_tracks.append(stc_track)
        prevailing_stc_tracks = negative_stc_tracks if len(
            negative_stc_tracks) >= len(
                positive_stc_tracks) else positive_stc_tracks
        if prevailing_stc_tracks:
            prevailing_stc_track = None
            prevailing_stc_magnitude = None
            for (_, group) in itertools.groupby(
                    sorted(
                        prevailing_stc_tracks,
                        key=lambda stc_track: stc_track.stat_type.type_id()),
                    key=lambda stc_track: stc_track.stat_type.type_id()):
                group = list(group)
                stc_magnitude = sum(stc_track.get_value()
                                    for stc_track in group) / len(group)
                if not prevailing_stc_track is None:
                    if abs(stc_magnitude) > abs(prevailing_stc_magnitude):
                        prevailing_stc_magnitude = stc_magnitude
                        prevailing_stc_track = group[0].stat_type
                prevailing_stc_magnitude = stc_magnitude
                prevailing_stc_track = group[0].stat_type
            return prevailing_stc_track.get_bit_at_relationship_value(
                prevailing_stc_magnitude)
        else:
            sim = next(iter(sims), None)
            if sim is not None:
                return sim.relationship_tracker.get_default_short_term_context_bit(
                )

    def get_expected_args(self):
        return {'subject': self.participant, 'target': self.target_subject}

    @cached_test
    def __call__(self, subject=(), target=()):
        subject = next(iter(subject), None)
        target = next(iter(target), None)
        if subject is None:
            return TestResult(False, '{} is not a valid participant',
                              self.participant)
        if target is None:
            return TestResult(False, '{} is not a valid participant',
                              self.target_subject)
        sim = subject.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
        if sim is None:
            return TestResult(False, '{} is non-instantiated', subject)
        target_sim = target.get_sim_instance(
            allow_hidden_flags=ALL_HIDDEN_REASONS)
        if target_sim is None:
            return TestResult(False, '{} is non-instantiated', target)
        if sim.is_in_group_with(target_sim):
            social_context = sim.get_social_context()
        else:
            social_context = self.get_overall_short_term_context_bit(
                sim, target_sim)
        if self.required_set and social_context not in self.required_set:
            return TestResult(False,
                              '{} for {} does not match required contexts',
                              social_context,
                              sim,
                              tooltip=self.tooltip)
        if social_context in self.prohibited_set:
            return TestResult(False,
                              '{} for {} is a prohibited context',
                              social_context,
                              sim,
                              tooltip=self.tooltip)
        return TestResult.TRUE
コード例 #20
0
ファイル: situation.py プロジェクト: NeonOcean/Environment
 def __init__(self, description='A single tunable Situation level.', **kwargs):
     super().__init__(medal=TunableEnumEntry(description='\n                The corresponding medal (Tin, Bronze, etc.) associated with this level.\n                ', tunable_type=SituationMedal, default=SituationMedal.TIN), score_delta=TunableRange(description='\n                The amount of score from the previous Situation Level that the\n                player need to acquire before the situation is considered in\n                this Situation Level.\n                ', tunable_type=int, default=30, minimum=0), level_description=TunableLocalizedString(description='\n                Description of situation at level. This message is passed to UI\n                whenever we complete the situation.\n                ', allow_none=True), reward=Reward.TunableReference(description="\n                The Reward received when reaching this level of the Situation.\n                To give a specific SituationJobReward for a specific job, \n                you can tune that information at SituationJob's rewards field.\n                ", allow_none=True), audio_sting_on_end=TunableResourceKey(description='\n                The sound to play when a situation ends at this level.\n                ', resource_types=(sims4.resources.Types.PROPX,), default=None, allow_none=True), icon=TunableResourceKey(description="\n                Icon that is displayed on the situation UI's progress bar when\n                this level has been reached. If left unspecified, icon will\n                default to a generic medal icon appropriate for the level.\n                ", resource_types=sims4.resources.CompoundTypes.IMAGE, default=None, allow_none=True), description=description, **kwargs)
コード例 #21
0
class WaterTerrainObjectCache:
    OBJECT_SQ_DISTANCE_THRESHOLD = TunableThreshold(
        description=
        "\n        The distance threshold between the user's click on water and objects in\n        the world that have WATER_TERRAIN_TAGS. If the user picks water, we\n        find the nearest object in this distance threshold and generate a pie\n        menu.\n        ",
        value=Tunable(
            description=
            '\n            The value of the threshold that the collection is compared\n            against.\n            ',
            tunable_type=float,
            default=100.0),
        default=sims4.math.Threshold(
            0.0, sims4.math.Operator.LESS_OR_EQUAL.function))
    WATER_TERRAIN_TAGS = TunableList(
        description=
        "\n        The Tags on Object Definitions that mark objects for caching near the\n        water. Please make exclusive tags for this, as we don't want to include\n        objects that don't make sense.\n        ",
        tunable=TunableEnumEntry(
            description=
            '\n            A tag that marks an object for caching near the water terrain.\n            ',
            tunable_type=tag.Tag,
            default=tag.Tag.INVALID))

    def __init__(self):
        self._object_cache = []

    def get_nearest_object(self, pick_pos, check_distance=True):
        nearby_objects = []
        for cache_obj in self:
            obj_pos = cache_obj.location.transform.translation
            dist_sq = (pick_pos - obj_pos).magnitude_2d_squared()
            nearby_objects.append((cache_obj, dist_sq))
        nearest_obj = None
        if nearby_objects:
            nearest = min(nearby_objects, key=lambda x: x[1])
            if WaterTerrainObjectCache.OBJECT_SQ_DISTANCE_THRESHOLD.compare(
                    nearest[1]):
                nearest_obj = nearest[0]
        return nearest_obj

    def refresh(self):
        self.clear()
        for obj in services.object_manager().valid_objects():
            self.add_object(obj)

    def can_add_object(self, obj):
        if obj in self._object_cache:
            return False
        definition = obj.definition
        for tag in WaterTerrainObjectCache.WATER_TERRAIN_TAGS:
            if definition.has_build_buy_tag(tag):
                return True
        return False

    def add_object(self, obj):
        if self.can_add_object(obj):
            self._object_cache.append(obj)
            return True
        return False

    def __iter__(self):
        return self._object_cache.__iter__()

    def clear(self):
        self._object_cache = []
コード例 #22
0
ファイル: situation.py プロジェクト: NeonOcean/Environment
class Situation(BaseSituation, HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SITUATION)):

    @staticmethod
    def _verify_situation_level_tuning(instance_class, tunable_name, source, bronze, gold, silver, tin):
        gold_reward = gold.reward
        if gold_reward is not None and gold_reward.reward_description is None:
            logger.error('Situation "{}" has a Gold tier reward that has no Reward Description tuned. Bronze and Silver are optional, but Gold requires a description.', source, owner='asantos')

    INSTANCE_SUBCLASSES_ONLY = True
    NPC_HOSTED_SITUATION_AGE_WEIGHTING = TunableMapping(description='\n        A map of ages to weights when determining which sim in the household\n        will be selected to receive an invitation.\n        ', key_name='age', key_type=TunableEnumEntry(description='\n            The age of a possible invitee that will be mapped to a weight.\n            ', tunable_type=Age, default=Age.ADULT), value_name='weight', value_type=TunableRange(description='\n            The weight a sim of this age will be chosen to have an event run\n            on them.\n            ', tunable_type=int, default=1, minimum=1))
    INSTANCE_TUNABLES = {'category': TunableEnumEntry(description='\n                The Category that the Situation belongs to.\n                ', tunable_type=SituationCategoryUid, default=SituationCategoryUid.DEFAULT, tuning_group=GroupNames.UI), 'load_open_street_situation_with_selectable_sim': Tunable(description='\n            If the situation has selectable sims, set to True to ensure the\n            situation can load from the open street, False otherwise.\n            \n            Note: The Serialization Option also determines save/load strategy.\n            Check with GPE to verify the situation save/load behavior.\n            ', tunable_type=bool, default=False), '_display_name': TunableLocalizedString(description='\n                Display name for situation\n                ', allow_none=True, tuning_group=GroupNames.UI), 'situation_description': TunableLocalizedString(description='\n                Situation Description\n                ', allow_none=True, tuning_group=GroupNames.UI), 'entitlement': OptionalTunable(description='\n            If enabled, this situation is locked by an entitlement. Otherwise,\n            this situation is available to all players.\n            ', tunable=TunableEntitlement(description='\n                Entitlement required to plan this event.\n                ', tuning_group=GroupNames.UI)), '_default_job': TunableReference(description='\n                The default job for Sims in this situation\n                ', manager=services.situation_job_manager(), allow_none=True), '_resident_job': SituationJob.TunableReference(description='\n                The job to assign to members of the host sims household.\n                Make sure to use the in_family filter term in the filter\n                of the job you reference here.\n                It is okay if this tunable is None.\n                ', allow_none=True), '_icon': TunableResourceKey(description='\n                Icon to be displayed in the situation UI.\n                ', resource_types=sims4.resources.CompoundTypes.IMAGE, default=None, allow_none=True, tuning_group=GroupNames.UI), 'calendar_icon': TunableIconAllPacks(description='\n            Icon to be displayed in the calendar UI.\n            ', allow_none=True, tuning_group=GroupNames.UI), 'calendar_alert_description': OptionalTunable(description='\n            If tuned, there will be a calendar alert description.\n            ', tunable=TunableLocalizedString(description='\n                Description that shows up in the calendar alert.\n                ')), '_level_data': TunableTuple(tin=TunableSituationLevel(description='\n                    Tuning for the Tin level of this situation.  This level has\n                    a score delta of 0 as it is considered the default level\n                    of any situation.\n                    ', locked_args={'medal': SituationMedal.TIN, 'score_delta': 0}), bronze=TunableSituationLevel(description='\n                    Tuning for the Bronze level of this situation.\n                    ', locked_args={'medal': SituationMedal.BRONZE}), silver=TunableSituationLevel(description='\n                    Tuning for the Silver level of this situation.\n                    ', locked_args={'medal': SituationMedal.SILVER}), gold=TunableSituationLevel(description='\n                    Tuning for the Gold level of this situation.\n                    ', locked_args={'medal': SituationMedal.GOLD}), description='\n                    Tuning for the different situation levels and rewards that\n                    are associated with them.\n                    ', verify_tunable_callback=_verify_situation_level_tuning), 'job_display_ordering': OptionalTunable(description='\n            An optional list of the jobs in the order you want them displayed\n            in the Plan an Event UI.\n            ', tunable=TunableList(tunable=TunableReference(manager=services.situation_job_manager())), tuning_group=GroupNames.UI), 'recommended_job_object_notification': ui.ui_dialog_notification.UiDialogNotification.TunableFactory(description='\n            The notification that is displayed when one or more recommended objects\n            for a job are missing.\n            ', locked_args={'text': None}), 'recommended_job_object_text': TunableLocalizedStringFactory(description='\n            The text of the notification that is displayed when one or more recommended\n            objects for a job are missing.\n            \n            The localization tokens for the Text field are:\n            {0.String} = bulleted list of strings for the missing objects\n            ', allow_none=True), '_buff': TunableBuffReference(description='\n                Buff that will get added to sim when commodity is at this\n                current state.\n                ', allow_none=True), '_cost': Tunable(description='\n                The cost of this situation\n                ', tunable_type=int, default=0), 'exclusivity': TunableEnumEntry(description='\n            Defines the exclusivity category for the situation which is used to prevent sims assigned\n            to this situation from being assigned to situations from categories excluded by this\n            category and vice versa.\n            ', tunable_type=situations.bouncer.bouncer_types.BouncerExclusivityCategory, default=situations.bouncer.bouncer_types.BouncerExclusivityCategory.NORMAL), 'main_goal': TunableReference(description='The main goal of the situation. e.g. Get Married.', manager=services.get_instance_manager(sims4.resources.Types.SITUATION_GOAL), allow_none=True, tuning_group=GroupNames.GOALS), 'main_goal_audio_sting': TunableResourceKey(description='\n                The sound to play when the main goal of this situation\n                completes.\n                ', default=None, resource_types=(sims4.resources.Types.PROPX,), tuning_group=GroupNames.AUDIO), '_main_goal_visibility_test': OptionalTunable(description='\n                If enabled then the main goal of the situation will not be\n                visible until this test passes.  If the state of this test no\n                longer becomes true then the main gaol will not become\n                invisible again.\n                \n                Ex. A hospital emergency starting triggers the visiblity of the\n                main goal within the active career event situation.\n                \n                IMPORTANT: The nature of this test can cause performance\n                problems.\n                ', tunable=TunableMainGoalVisibilityTestVariant(), tuning_group=GroupNames.GOALS), 'minor_goal_chains': TunableList(description='\n                A list of goal sets, each one starting a chain of goal sets, for selecting minor goals.\n                The list is in priority order, first being the most important.\n                At most one goal will be selected from each chain.\n                ', tunable=situations.situation_goal_set.SituationGoalSet.TunableReference(), tuning_group=GroupNames.GOALS), 'force_invite_only': Tunable(description='\n                If True, the situation is invite only. Otherwise, it is not.\n                For a date situation, this would be set to True.\n                ', tunable_type=bool, default=False), 'creation_ui_option': TunableEnumEntry(description='\n                Determines if the situation can be created from the Plan Event\n                UI triggered from the phone.\n                \n                NOT_AVAILABLE - situation is not available in the creation UI.\n                \n                AVAILABLE - situation is available in the creation UI.\n                \n                DEBUG_AVAILABLE - situation is only available in the UI if\n                you have used the |situations.allow_debug_situations command\n                \n                SPECIFIED_ONLY - situation is available in the creation UI if\n                that UI is tuned to only look at a subset of situations.\n                ', tunable_type=SituationCreationUIOption, default=SituationCreationUIOption.AVAILABLE, tuning_group=GroupNames.UI), 'audio_sting_on_start': TunableResourceKey(description='\n                The sound to play when the Situation starts.\n                ', default=None, resource_types=(sims4.resources.Types.PROPX,), tuning_group=GroupNames.AUDIO), 'background_audio': OptionalTunable(description='\n                If enabled then we will play audio in the background while this\n                user facing situation is running.\n                ', tunable=TunableResourceKey(description='\n                    Audio that will play throughout the situation in the background\n                    and will end at the end of the situation.\n                    ', default=None, resource_types=(sims4.resources.Types.PROPX,)), tuning_group=GroupNames.AUDIO), 'duration': TunableSimMinute(description='\n                How long the situation will last in sim minutes. 0 means forever.\n                ', default=60), 'duration_randomizer': TunableSimMinute(description="\n            A random time between 0 and this tuned time will be added to the\n            situation's duration.\n            ", default=0, minimum=0), 'max_participants': Tunable(description='\n                Maximum number of Sims the player is allowed to invite to this Situation.\n                ', tunable_type=int, default=16, tuning_group=GroupNames.UI), '_initiating_sim_tests': TunableSituationInitiationSet('\n            A set of tests that will be run on a sim attempting to initiate a\n            situation.  If these tests do not pass than this situation will not\n            be able to be chosen from the UI.\n            '), 'targeted_situation': OptionalTunable(description='\n                If enabled, the situation can be used as a targeted situation,\n                such as a Date.\n                ', tunable=TargetedSituationSpecific.TunableFactory()), 'compatible_venues': TunableList(description='\n                In the Plan an Event UI, lots that are these venues will be\n                added to the list of lots on which the player can throw the\n                event. The player can always choose their own lot and lots of\n                their guests.\n                ', tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.VENUE), pack_safe=True, tuning_group=GroupNames.VENUES)), 'venue_region_must_be_compatible': Tunable(description='\n                If enabled, venues will only be considered if they are in a\n                region that is compatible with the current region (regions with\n                at least one shared tag).\n                ', tunable_type=bool, default=False), 'venue_invitation_message': OptionalTunable(description='\n            If enabled, show a dialog when the situation tries to start on a\n            venue.\n            ', tunable=UiDialogOkCancel.TunableFactory(description="\n                The message that will be displayed when this situation tries to\n                start for the venue.\n                \n                Two additional tokens are passed in: the situation's name and\n                the job's name.\n                "), tuning_group=GroupNames.VENUES), 'venue_situation_player_job': TunableReference(description="\n                The job that the player will be put into when they join in a\n                user_facing special situation at a venue.\n                \n                Note: This must be tuned to allow this situation to be in a\n                venue's special event schedule. The job also must be a part of\n                the Situation.\n                ", manager=services.get_instance_manager(sims4.resources.Types.SITUATION_JOB), allow_none=True, tuning_group=GroupNames.VENUES), 'tags': TunableSet(description='\n                Tags for arbitrary groupings of situation types.\n                ', tunable=TunableEnumWithFilter(tunable_type=Tag, filter_prefixes=['situation'], default=Tag.INVALID, pack_safe=True)), '_relationship_between_job_members': TunableList(description="\n                Whenever a sim joins either job_x or job_y, the sim is granted \n                the tuned relationship bit with every sim in the other job. The\n                relationship bits are added and remain as long as the sims are\n                assigned to the tuned pair of jobs.\n                \n                This creates a relationship between the two sims if one does not exist.\n                \n                E.g. Date situation uses this feature to add bits to the sims'\n                relationship in order to drive autonomous behavior during the \n                lifetime of the date. \n                ", tunable=TunableTuple(job_x=SituationJob.TunableReference(), job_y=SituationJob.TunableReference(), relationship_bits_to_add=TunableSet(description='\n                        A set of RelationshipBits to add to relationship between the sims.\n                        ', tunable=RelationshipBit.TunableReference())), tuning_group=GroupNames.TRIGGERS), '_implies_greeted_status': Tunable(description='\n                If checked then a sim, in this situation, on a residential lot\n                they do not own, is consider greeted on that lot.\n                \n                Greeted status related design documents:\n                //depot/Sims4Projects/docs/Design/Gameplay/HouseholdState/Ungreeted_Lot_Behavior_DD.docx\n                //depot/Sims4Projects/docs/Design/Gameplay/Simulation/Active Lot Changing Edge Cases.docx\n                ', tunable_type=bool, default=False), 'screen_slam_no_medal': OptionalTunable(description='\n            Screen slam to show when this situation is completed and no\n            medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_bronze': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            bronze medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_silver': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            silver medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'screen_slam_gold': OptionalTunable(description='\n            Screen slam to show when this situation is completed and a\n            bronze medal is earned.\n            Localization Tokens: Event Name - {0.String}, Medal Awarded - \n            {1.String}\n            ', tunable=ui.screen_slam.TunableScreenSlamSnippet()), 'time_jump': TunableSituationTimeJumpVariant(description='\n            Determine how the situation handles the zone time being different on\n            load than what it was on save. This is primarily useful for\n            commercial venue background situations and career event situations.\n            ', tuning_group=GroupNames.SPECIAL_CASES), 'can_remove_sims_from_work': Tunable(description='\n            If checked then this situation will cause sims to end work early\n            when they are on the guest list. If unchecked, it will not. This\n            option will not affect active career situations or NPC career\n            situations like tending the bar.\n            ', tunable_type=bool, default=True, tuning_group=GroupNames.SPECIAL_CASES), '_survives_active_household_change': Tunable(description='\n            If checked then this situation will load even if the active\n            household has changed since it was saved. It will attempt to\n            restore Sims to their saved jobs. This is primarily useful for\n            commercial venue background situations.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_maintain_sims_consistency': Tunable(description="\n            If checked, Sims in the saved situation that were pushed home \n            because they had been saved in the zone for many Sim hours will \n            be back. Otherwise, we will find replacement.\n            \n            Ex. We don't want to replace Butler with new Sim if previous\n            Butler is no longer in the lot.\n            ", tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_hidden_scoring_override': Tunable(description='\n            If checked then even if this situation has its scoring disabled it\n            still will count score and provide rewards to the player.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.SPECIAL_CASES), '_ensemble': OptionalTunable(description='\n            If enabled then we will keep Sims in a specific ensemble for the\n            duration of the situation.\n            ', tunable=TunableTuple(description='\n                Tunables for putting Sims into ensembles.\n                ', ensemble_type=TunableReference(description='\n                    The type of Ensemble to put the sims into.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE), pack_safe=True), remove_before_add=Tunable(description='\n                    If checked then before we add the Sim to the ensemble we\n                    will remove them from from ensembles of the specified type.\n                    This can be used to force Sims into only an ensemble of\n                    Sims in this situation.\n                    ', tunable_type=bool, default=False), ignore_situation_removal=Tunable(description='\n                    If checked then we will not remove the Sim from the\n                    ensemble of this type when the Sim is removed from the\n                    situation.\n                    ', tunable_type=bool, default=True), ensemble_option=TunableEnumEntry(description='\n                    How we want to add Sims to an ensemble:\n                    ONLY_WITHIN_SITUATION: Put the Sims in this situation into\n                    an ensemble of this type.  Every time a sim is added we\n                    try and do this so if the user destroys the ensemble and\n                    then another Sim is spawned for it the ensemble will be\n                    recreated.\n                    \n                    ADD_TO_ACTIVE_HOUSEHOLD: Every time a Sim is spawned for\n                    this situation they are put into an ensemble with the\n                    instanced active household.  This is useful if you want to\n                    put the Sims in a situation with someone who is not in it. \n                    \n                    ADD_TO_HOST: Every time a Sim is spawned for this situation\n                    they are put into an ensemble with the host of the\n                    situation.  This is useful if you want to put the Sims in\n                    a situation with someone who is not in it.\n                    ', tunable_type=EnsembleOption, default=EnsembleOption.ONLY_WITHIN_SITUATION)), tuning_group=GroupNames.SPECIAL_CASES), 'blocks_super_speed_three': Tunable(description='\n            If enabled, this situation will block any requests to go into super\n            speed 3.\n            ', tunable_type=bool, default=False), 'travel_request_behavior': TunableSituationTravelRequestBehaviorVariant(description='\n            Define how this situation handles incoming travel requests from\n            other situations when running as user-facing.\n            '), 'allowed_in_super_speed_3': Tunable(description="\n            If enabled, this situation will skip the super speed 3 rules and\n            be allowed to trigger at that speed.\n            This will only affect walkby's as they are the only restricted\n            by speed 3.\n            ", tunable_type=bool, default=False), 'should_send_on_lot_home_in_super_speed_3': Tunable(description='\n            If enabled, on_lot sims in this situation will not prevent SS3.  If\n            SS3 is triggered they will be sent home.\n            ', tunable_type=bool, default=False), 'super_speed3_replacement_speed': OptionalTunable(description='\n            If enabled and this situation blocks super speed 3, the situation will attempt to request\n            this speed if it is running when super speed 3 tries to kick in.\n            ', tunable=TunableEnumEntry(tunable_type=ClockSpeedMode, invalid_enums=(ClockSpeedMode.PAUSED, ClockSpeedMode.INTERACTION_STARTUP_SPEED, ClockSpeedMode.SUPER_SPEED3), default=ClockSpeedMode.SPEED3)), 'weight_multipliers': TunableMultiplier.TunableFactory(description="\n            Tunable tested multiplier to apply to any weight this situation\n            might have as part of a Situation Curve. These multipliers will be\n            applied globally anywhere this situation is tuned as part of a\n            situation curve (i.e. Walkby Tuning) so it should only be used in\n            cases where you ALWAYS want this multiplier applied.\n            \n            NOTE: You can still tune more multipliers on the individual walk by\n            instances. The multipliers will all stack together.\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}), 'disallows_curfew_violation': Tunable(description='\n            If this is checked then the Sim is unable to violate curfew while\n            in the situation. If this is not checked then the Sim can vioalte\n            curfew as normal.\n            ', tunable_type=bool, default=False), 'suppress_scoring_progress_bar': Tunable(description='\n            If this is checked, UI will no longer show the scoring progress bar\n            and instead show the situation name in its stead.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), 'highlight_first_incomplete_minor_goal': Tunable(description='\n            If this is checked, we will tell user-facing Situation UI \n            to highlight the first uncompleted minor goal set.\n            \n            Note that gameplay currently does not guard against being able \n            to complete the other goal sets in the situation currently, \n            so the goalsets should be tuned in such a manner \n            that they do not overlap.\n            ', tunable_type=bool, default=False, tuning_group=GroupNames.UI), '_use_spawner_tags_on_travel': Tunable(description='\n            If checked the situation will spawn sims according to its job spawner tags\n            instead of always defaulting to the Arrival spawner.\n            ', tunable_type=bool, default=False)}
    SITUATION_SCORING_REMOVE_INSTANCE_TUNABLES = ('main_goal', '_main_goal_visibility_test', 'minor_goal_chains', 'main_goal_audio_sting', 'highlight_first_incomplete_minor_goal', 'suppress_scoring_progress_bar', '_level_data', 'screen_slam_gold', 'screen_slam_silver', 'screen_slam_bronze', 'screen_slam_no_medal')
    SITUATION_START_FROM_UI_REMOVE_INSTANCE_TUNABLES = ('_cost', 'compatible_venues', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'max_participants', '_initiating_sim_tests', '_icon', 'entitlement', 'job_display_ordering')
    SITUATION_USER_FACING_REMOVE_INSTANCE_TUNABLES = ('_display_name', 'travel_request_behavior', 'recommended_job_object_notification', 'recommended_job_object_text', 'situation_description')
    NON_USER_FACING_REMOVE_INSTANCE_TUNABLES = ('_buff', 'targeted_situation', '_resident_job', '_relationship_between_job_members', 'audio_sting_on_start', 'background_audio', 'force_invite_only') + SITUATION_SCORING_REMOVE_INSTANCE_TUNABLES + SITUATION_START_FROM_UI_REMOVE_INSTANCE_TUNABLES + SITUATION_USER_FACING_REMOVE_INSTANCE_TUNABLES
    SITUATION_EVENT_REMOVE_INSTANCE_TUNABLES = ('_buff', '_cost', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', '_main_goal_visibility_test', 'minor_goal_chains', 'highlight_first_incomplete_minor_goal', 'suppress_scoring_progress_bar', 'max_participants', '_initiating_sim_tests', '_icon', 'targeted_situation', '_resident_job', 'situation_description', 'job_display_ordering', 'entitlement', '_relationship_between_job_members', 'main_goal_audio_sting', 'audio_sting_on_start', 'background_audio', '_level_data', '_display_name', 'screen_slam_gold', 'screen_slam_silver', 'screen_slam_bronze', 'screen_slam_no_medal', 'force_invite_only', 'recommended_job_object_notification', 'recommended_job_object_text', 'travel_request_behavior')
    situation_level_data = None
    SituationLevel = collections.namedtuple('SituationLevel', ['min_score_threshold', 'level_data'])

    @classmethod
    def _tuning_loaded_callback(cls):
        cls.situation_level_data = []
        current_score = cls._level_data.tin.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.tin))
        current_score += cls._level_data.bronze.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.bronze))
        current_score += cls._level_data.silver.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.silver))
        current_score += cls._level_data.gold.score_delta
        cls.situation_level_data.append(Situation.SituationLevel(current_score, cls._level_data.gold))

    @classmethod
    def _verify_tuning_callback(cls):
        if cls._resident_job is not None and cls._resident_job.filter is None:
            logger.error('Resident Job: {} has no filter,', cls._resident_job, owner='manus')
        if cls.targeted_situation is not None and (cls.targeted_situation.target_job is None or cls.targeted_situation.actor_job is None):
            logger.error('target_job and actor_job are required if targeted_situation is enabled.', owner='manus')
        tuned_jobs = frozenset(cls.get_tuned_jobs())
        for job_relationships in cls.relationship_between_job_members:
            if job_relationships.job_x not in tuned_jobs:
                logger.error('job_x: {} has relationship tuning but is not functionally used in situation {}.', job_relationships.job_x, cls, owner='manus')
            if job_relationships.job_y not in tuned_jobs:
                logger.error('job_y: {} has relationship tuning but is not functionally used in situation {}.', job_relationships.job_y, cls, owner='manus')
            if len(job_relationships.relationship_bits_to_add) == 0:
                logger.error("relationship_bits_to_add cannot be empty for situation {}'s job pairs {} and {}.", cls, job_relationships.job_x, job_relationships.job_y, owner='manus')
            else:
                for bit in job_relationships.relationship_bits_to_add:
                    if bit is None:
                        logger.error("relationship_bits_to_add cannot contain empty bit for situation {}'s job pairs {} and {}.", cls, job_relationships.job_x, job_relationships.job_y, owner='manus')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._duration_alarm_handle = None
        self._goal_tracker = None
        self._dynamic_goals = self._seed.extra_kwargs.get('dynamic_goals', None)

    @classproperty
    def allow_user_facing_goals(cls):
        return cls.main_goal is not None or len(cls.minor_goal_chains) > 0

    @classmethod
    def level_data_gen(cls):
        for level in cls.situation_level_data:
            yield level

    @classmethod
    def fake_perform_job(cls):
        pass

    @classmethod
    def get_level_data(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].level_data

    @classmethod
    def get_level_min_threshold(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].min_score_threshold

    @classmethod
    def get_level_icon(cls, medal:SituationMedal=SituationMedal.TIN):
        if cls.situation_level_data is None:
            return
        return cls.situation_level_data[medal].level_data.icon

    @classmethod
    def get_possible_zone_ids_for_situation(cls, host_sim_info=None, guest_ids=None):
        possible_zones = []
        venue_manager = services.get_instance_manager(sims4.resources.Types.VENUE)
        venue_service = services.current_zone().venue_service
        for venue_tuning in cls.compatible_venues:
            if venue_tuning.is_residential:
                if host_sim_info is not None:
                    home_zone_id = host_sim_info.household.home_zone_id
                    home_venue_tuning = venue_manager.get(build_buy.get_current_venue(home_zone_id))
                    if home_venue_tuning.is_residential:
                        possible_zones.append(home_zone_id)
                if guest_ids is not None:
                    for guest_id in guest_ids:
                        guest_id = int(guest_id)
                        guest_info = services.sim_info_manager().get(guest_id)
                        if guest_info is not None:
                            guest_zone_id = guest_info.household.home_zone_id
                            if guest_zone_id is not None and guest_zone_id and guest_zone_id not in possible_zones:
                                guest_venue_tuning = venue_manager.get(build_buy.get_current_venue(guest_zone_id))
                                if guest_venue_tuning.is_residential:
                                    possible_zones.append(guest_zone_id)
                            travel_group = guest_info.travel_group
                            if travel_group is not None:
                                travel_group_zone_id = travel_group.zone_id
                                if travel_group_zone_id is not None and travel_group_zone_id and travel_group_zone_id not in possible_zones:
                                    possible_zones.append(travel_group_zone_id)
                    else:
                        possible_zones.extend(venue_service.get_zones_for_venue_type_gen(venue_tuning))
            else:
                possible_zones.extend(venue_service.get_zones_for_venue_type_gen(venue_tuning))
        return possible_zones

    @classmethod
    def default_job(cls):
        return cls._default_job

    @classmethod
    def resident_job(cls):
        return cls._resident_job

    @classmethod
    def get_prepopulated_job_for_sims(cls, sim, target_sim_id=None):
        if target_sim_id and cls.targeted_situation is not None:
            sim_info = services.sim_info_manager().get(target_sim_id)
            if sim_info is None:
                return
            else:
                prepopulated = [(sim.id, cls.targeted_situation.actor_job.guid64), (target_sim_id, cls.targeted_situation.target_job.guid64)]
                return prepopulated

    def _display_role_objects_notification(self, sim, bullets):
        text = self.recommended_job_object_text(bullets)
        notification = self.recommended_job_object_notification(sim, text=lambda *_, **__: text)
        notification.show_dialog()

    @property
    def pie_menu_icon(self):
        return self._pie_menu_icon

    @classproperty
    def display_name(self):
        return self._display_name

    @property
    def description(self):
        return self.situation_description

    @classproperty
    def icon(self):
        return self._icon

    @property
    def start_audio_sting(self):
        return self.audio_sting_on_start

    @property
    def audio_background(self):
        return self.background_audio

    def get_target_object(self):
        pass

    def get_created_object(self):
        pass

    @property
    def end_audio_sting(self):
        current_level = self.get_level()
        level_data = self.get_level_data(current_level)
        if level_data is not None and level_data.audio_sting_on_end is not None:
            return level_data.audio_sting_on_end
        else:
            return

    @classproperty
    def relationship_between_job_members(cls):
        return cls._relationship_between_job_members

    @classproperty
    def implies_greeted_status(cls):
        return cls._implies_greeted_status

    @classmethod
    def cost(cls):
        return cls._cost

    @classproperty
    def survives_active_household_change(cls):
        return cls._survives_active_household_change

    @classproperty
    def maintain_sims_consistency(cls):
        return cls._maintain_sims_consistency

    def _get_duration(self):
        if self._seed.duration_override is not None:
            return self._seed.duration_override
        return self.duration + random.randint(0, self.duration_randomizer)

    def _get_remaining_time(self):
        if self._duration_alarm_handle is None:
            return
        return self._duration_alarm_handle.get_remaining_time()

    def _get_remaining_time_for_gsi(self):
        return self._get_remaining_time()

    def _get_remaining_time_in_minutes(self):
        time_span = self._get_remaining_time()
        if time_span is None:
            return 0
        return time_span.in_minutes()

    def _get_goal_tracker(self):
        return self._goal_tracker

    def _save_custom(self, seed):
        super()._save_custom(seed)
        if self._goal_tracker is not None:
            self._goal_tracker.save_to_seed(seed)

    def start_situation(self):
        super().start_situation()
        self._set_duration_alarm()
        if self.is_user_facing:
            if self.should_track_score:
                if self._dynamic_goals is None:
                    self._goal_tracker = situations.situation_goal_tracker.SituationGoalTracker(self)
                else:
                    self._goal_tracker = situations.dynamic_situation_goal_tracker.DynamicSituationGoalTracker(self)

    def _load_situation_states_and_phases(self):
        super()._load_situation_states_and_phases()
        self._set_duration_alarm()
        if not self._seed.goal_tracker_seedling:
            return
        if self._seed.goal_tracker_seedling.goal_tracker_type == GoalTrackerType.STANDARD_GOAL_TRACKER:
            self._goal_tracker = situations.situation_goal_tracker.SituationGoalTracker(self)
        elif self._seed.goal_tracker_seedling.goal_tracker_type == GoalTrackerType.DYNAMIC_GOAL_TRACKER:
            self._goal_tracker = situations.dynamic_situation_goal_tracker.DynamicSituationGoalTracker(self)

    def change_duration(self, duration):
        if not self.is_running:
            logger.error("Trying to change the duration of a situation {} that's not running.", self)
        self._set_duration_alarm(duration_override=duration)
        if self.is_user_facing:
            self.add_situation_duration_change_op()

    def _set_duration_alarm(self, duration_override=None):
        if duration_override is not None:
            duration = duration_override
        else:
            duration = self._get_duration()
        self.set_end_time(duration)
        if duration > 0:
            if self._duration_alarm_handle is not None:
                alarms.cancel_alarm(self._duration_alarm_handle)
            self._duration_alarm_handle = alarms.add_alarm(self, clock.interval_in_sim_minutes(duration), self._situation_timed_out)

    def _cancel_duration_alarm(self):
        if self.is_user_facing:
            logger.error('Canceling duration alarm for a User-Facing Situation {}', self, owner='rmccord')
        if self._duration_alarm_handle is not None:
            alarms.cancel_alarm(self._duration_alarm_handle)

    def pre_destroy(self):
        pass

    def _destroy(self):
        if self._duration_alarm_handle is not None:
            alarms.cancel_alarm(self._duration_alarm_handle)
        if self._goal_tracker is not None:
            self._goal_tracker.destroy()
            self._goal_tracker = None
        super()._destroy()

    def _situation_timed_out(self, _):
        logger.debug('Situation time expired: {}', self)
        self._self_destruct()

    @classmethod
    def is_situation_available(cls, initiating_sim, target_sim_id=0):
        is_targeted = cls.targeted_situation is not None and cls.targeted_situation.target_job is not None
        if is_targeted and target_sim_id:
            if not cls.targeted_situation.target_job.can_sim_be_given_job(target_sim_id, initiating_sim.sim_info):
                return TestResult(False)
        elif (target_sim_id == 0) != (is_targeted == False):
            return TestResult(False)
        single_sim_resolver = event_testing.resolver.SingleSimResolver(initiating_sim.sim_info)
        return cls._initiating_sim_tests.run_tests(single_sim_resolver)

    @classmethod
    def get_predefined_guest_list(cls):
        pass

    @classmethod
    def is_venue_location_valid(cls, zone_id):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        return services.current_zone().venue_service.is_zone_valid_for_venue_type(zone_id, cls.compatible_venues, compatible_region=compatible_region)

    @classmethod
    def get_venue_location(cls):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        (zone_id, _) = services.current_zone().venue_service.get_zone_and_venue_type_for_venue_types(cls.compatible_venues, compatible_region=compatible_region)
        return zone_id

    @classmethod
    def has_venue_location(cls):
        compatible_region = services.current_region() if cls.venue_region_must_be_compatible else None
        return services.current_zone().venue_service.has_zone_for_venue_type(cls.compatible_venues, compatible_region=compatible_region)

    @classproperty
    def main_goal_visibility_test(cls):
        return cls._main_goal_visibility_test

    @classproperty
    def _ensemble_data(cls):
        return cls._ensemble

    @property
    def should_track_score(self):
        return self.scoring_enabled or self._hidden_scoring_override

    @property
    def should_give_rewards(self):
        return self.scoring_enabled or self._hidden_scoring_override

    def is_in_joinable_state(self):
        return True

    @property
    def custom_event_keys(self):
        return [type(self)] + list(self.tags)

    @classproperty
    def use_spawner_tags_on_travel(cls):
        return cls._use_spawner_tags_on_travel
コード例 #23
0
class DynamicSpawnPointElement(SubclassableGeneratorElement, HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'description': '\n            This Element will create a Dynamic Spawn Point which is registered\n            to a particular participant within the interaction. It will be\n            added to the zone and available for use by any Sims who want to\n            spawn.\n            ', 'tags': TunableSet(description="\n            A set of tags to add to the dynamic spawn point when it's created.\n            This is how we can use this spawn point to spawn particular Sims\n            without interfering with walkbys and other standard Sims that are\n            spawned.\n            ", tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=tag.SPAWN_PREFIX)), 'participant': TunableEnumEntry(description='\n            The Participant of the interaction that we want the spawn point to be near.\n            ', tunable_type=ParticipantType, default=ParticipantType.Actor), 'attach_to_active_lot': Tunable(description='\n            If checked, the spawn point will be attached to the active lot.\n            This helps Sims who are looking to visit the current lot find a\n            spawn point nearby.\n            ', tunable_type=bool, default=False), 'distance_to_participant': Tunable(description='\n            The Distance from the participant that Sims should spawn.\n            ', tunable_type=float, default=7.0), 'allow_spawning_on_non_world_routing_surfaces': Tunable(description='\n            If checked, this spawn point can be generated on routing surfaces\n            of any type. If unchecked, it can only be generated on world\n            routing surfaces.\n            \n            If this tunable is unchecked and the participant is not on a world\n            routing surface, the spawn point will be generated with the world\n            surface type on the same level as the participant.\n            ', tunable_type=bool, default=False)}

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.interaction = interaction
        self.sequence = sequence
        self.spawn_point = None

    def _run_gen(self, timeline):
        result = yield from element_utils.run_child(timeline, build_critical_section_with_finally(self.start, self.sequence, self.stop))
        return result
        yield

    def start(self, *_, **__):
        zone = services.current_zone()
        lot_id = 0 if not self.attach_to_active_lot else zone.lot.lot_id
        self.spawn_point = DynamicInteractionSpawnPoint(self.interaction, self.participant, self.distance_to_participant, self.tags, lot_id, zone.id, self.allow_spawning_on_non_world_routing_surfaces)
        services.current_zone().add_dynamic_spawn_point(self.spawn_point)

    def stop(self, *_, **__):
        services.current_zone().remove_dynamic_spawn_point(self.spawn_point)
        self.spawn_point = None
コード例 #24
0
class DramaNodePickerInteraction(PickerSuperInteraction):
    INSTANCE_TUNABLES = {'buckets': OptionalTunable(description='\n            If enabled, we only return nodes from these buckets.\n            Drama nodes with no buckets are rejected. The order in which\n            buckets are tuned here will determine the order in which buckets\n            are shown in the picker. Gigs from the first bucket will appear\n            at the top of the picker and so on.\n            ', tunable=TunableList(tunable=TunableEnumEntry(description='\n                    Bucket to test against.\n                    ', tunable_type=DramaNodeScoringBucket, default=DramaNodeScoringBucket.DEFAULT), unique_entries=True)), 'loot_when_empty': OptionalTunable(description="\n            If enabled, we run this loot when picker is empty and don't display\n            the empty picker.\n            If disabled, picker will appear empty.\n            ", tunable=TunableList(description='\n                Loot applied if the picker is going to be empty.\n                ', tunable=LootActions.TunableReference(pack_safe=True))), 'use_only_scheduled': Tunable(description='\n            If checked, this picker will only consider drama nodes that have\n            been scheduled by the drama scheduler service. This is usually the\n            desired behavior except in special circumstances like debugging.\n            ', tunable_type=bool, default=True), 'disable_row_if_visibily_tests_fail': Tunable(description="\n            If checked, we will grey out any row if the corresponding drama\n            node failed its visibility testing. If not checked, the row won't\n            be shown.\n            ", tunable_type=bool, default=False), 'run_visibility_tests': Tunable(description='\n            If checked, This picker will run visibility tests on a drama node\n            to decide whether it should be shown. Otherwise, all picker drama\n            nodes will be available.\n            ', tunable_type=bool, default=True)}

    def _run_interaction_gen(self, timeline):
        self._show_picker_dialog(self.target, target_sim=self.target)
        return True
        yield

    def _show_picker_dialog(self, owner, **kwargs):
        if self.use_pie_menu():
            return
        dialog = self._create_dialog(owner, **kwargs)
        if self.loot_when_empty is not None and len(dialog.picker_rows) == 0:
            resolver = SingleSimResolver(owner.sim_info)
            for loot in self.loot_when_empty:
                loot.apply_to_resolver(resolver)
            else:
                dialog.show_dialog()
        else:
            for picker_row in dialog.picker_rows:
                self.send_telemetry(TELEMETRY_HOOK_DRAMA_PICKER_NODE_PRESENTED, picker_row.tag, picker_row.is_enable)
            dialog.show_dialog()

    def send_telemetry(self, hook_tag, drama_node, is_enabled=None):
        with telemetry_helper.begin_hook(telemetry_writer, hook_tag, sim_info=self.sim.sim_info) as hook:
            hook.write_int(TELEMETRY_PICKER_INSTANCE_ID, self.aop_id)
            hook.write_int(TELEMETRY_PICKER_DRAMA_NODE_ID, drama_node.guid64)
            if is_enabled is not None:
                hook.write_bool(TELEMETRY_PICKER_DRAMA_NODE_ENABLED, is_enabled)

    @flexmethod
    def picker_rows_gen(cls, inst, target, context, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        if inst_or_cls.use_only_scheduled:
            drama_nodes = iter(services.drama_scheduler_service().all_nodes_gen())
        else:
            drama_node_manager = services.get_instance_manager(sims4.resources.Types.DRAMA_NODE)
            drama_nodes = (drama_node() for drama_node in drama_node_manager.get_ordered_types())
        if inst_or_cls.buckets:
            picker_rows_by_bucket = defaultdict(list)
        else:
            picker_rows = list()
        for drama_node in drama_nodes:
            if drama_node.drama_node_type == DramaNodeType.PICKER:
                if inst_or_cls.buckets and drama_node.scoring and drama_node.scoring.bucket not in inst_or_cls.buckets:
                    continue
                result = drama_node.create_picker_row(owner=target, run_visibility_tests=inst_or_cls.run_visibility_tests, disable_row_if_visibily_tests_fail=inst_or_cls.disable_row_if_visibily_tests_fail)
                if result is not None:
                    if inst_or_cls.buckets:
                        picker_rows_by_bucket[drama_node.scoring.bucket].append(result)
                    else:
                        picker_rows.append(result)
        if inst_or_cls.buckets:
            for bucket in inst_or_cls.buckets:
                yield from picker_rows_by_bucket[bucket]
        else:
            yield from picker_rows

    def on_choice_selected(self, choice_tag, **kwargs):
        if choice_tag is None:
            return
        self.send_telemetry(TELEMETRY_HOOK_DRAMA_PICKER_NODE_SELECTED, choice_tag)
        choice_tag.on_picker_choice(owner=self.sim.sim_info)
コード例 #25
0
 def __init__(self):
     super().__init__(
         commodities=TunableSet(
             TunableReference(
                 services.statistic_manager(),
                 description='The type of commodity to search for.'),
             description=
             'List of commodities to run parameterized autonomy against after running this interaction.'
         ),
         static_commodities=TunableSet(
             TunableReference(
                 services.static_commodity_manager(),
                 description='The type of static commodity to search for.'),
             description=
             'List of static commodities to run parameterized autonomy against after running this interaction.'
         ),
         same_target_only=Tunable(
             bool,
             False,
             description=
             'If checked, only interactions on the same target as this interaction will be considered.'
         ),
         retain_priority=Tunable(
             bool,
             True,
             needs_tuning=True,
             description=
             'If checked, this autonomy request is run at the same priority level as the interaction creating it.  If unchecked, the interaction chosen will run at low priority.'
         ),
         consider_same_target=Tunable(
             bool,
             True,
             description=
             'If checked, parameterized autonomy will consider interactions on the current Target.'
         ),
         retain_carry_target=Tunable(
             bool,
             True,
             description=
             "If checked, the interactions considered for autonomy will retain this interaction's carry target. It is useful to uncheck this if the desired autonomous interactions need not to consider carry, e.g. the Grim Reaper finding arbitrary interactions while in an interaction holding his scythe as a carry target."
         ),
         randomization_override=OptionalTunable(
             description=
             '\n                    If enabled then the parameterized autonomy will run with\n                    an overwritten autonomy randomization settings.\n                    ',
             tunable=TunableEnumEntry(
                 description=
                 '\n                        The autonomy randomization setting that will be used.\n                        ',
                 tunable_type=AutonomyRandomization,
                 default=AutonomyRandomization.UNDEFINED)),
         radius_to_consider=Tunable(
             description=
             '\n                    The radius around the sim that targets must be in to be valid for Parameterized \n                    Autonomy.  Anything outside this radius will be ignored.  A radius of 0 is considered\n                    infinite.\n                    ',
             tunable_type=float,
             default=0),
         consider_scores_of_zero=Tunable(
             description=
             '\n                    The autonomy request will consider scores of zero.  This allows sims to to choose things they \n                    might not desire.\n                    ',
             tunable_type=bool,
             default=False),
         description=
         'Commodities and StaticCommodities will be combined, so interactions must support at least one commodity from both lists.'
     )
コード例 #26
0
class BroadcasterEffectAffordance(_BroadcasterEffectTestedOneShot):
    FACTORY_TUNABLES = {
        'affordances':
        TunableList(
            description=
            '\n            A list of affordances to choose from to push as a result of the\n            broadcaster.\n            ',
            tunable=TunableTuple(
                description=
                '\n                A tuple of affordance to push and weight for how likely the\n                affordance is to be picked.\n                ',
                affordance=TunableReference(
                    description=
                    '\n                    The affordance to push on Sims affected by the broadcaster.\n                    ',
                    manager=services.affordance_manager(),
                    pack_safe=True),
                weight=TunableRange(
                    description=
                    '\n                    How likely this affordance is to be picked.\n                    ',
                    tunable_type=int,
                    minimum=1,
                    default=1))),
        'affordance_target':
        OptionalTunable(
            description=
            '\n            If enabled, the pushed interaction will target a specified\n            participant.\n            ',
            tunable=TunableEnumEntry(
                description=
                '\n                The participant to be targeted by the pushed interaction.\n                ',
                tunable_type=ParticipantType,
                default=ParticipantType.Object),
            enabled_by_default=True),
        'affordance_priority':
        TunableEnumEntry(
            description=
            '\n            The priority at which the specified affordance is to be pushed.\n            ',
            tunable_type=Priority,
            default=Priority.Low),
        'affordance_run_priority':
        OptionalTunable(
            description=
            "\n            If enabled, specify the priority at which the affordance runs. This\n            may be different than 'affordance_priority'. For example. you might\n            want an affordance to push at high priority such that it cancels\n            existing interactions, but it runs at a lower priority such that it\n            can be more easily canceled.\n            ",
            tunable=TunableEnumEntry(
                description=
                '\n                The run priority for the specified affordance.\n                ',
                tunable_type=Priority,
                default=Priority.Low)),
        'affordance_must_run_next':
        Tunable(
            description=
            "\n            If set, the affordance will be inserted at the beginning of the\n            Sim's queue.\n            ",
            tunable_type=bool,
            default=False),
        'actor_can_violate_privacy_from_owning_interaction':
        Tunable(
            description=
            '\n            If enabled, the actor of the pushed affordance will be allowed to\n            violate the privacy region from the owning interaction. If\n            disabled, the actor of the pushed affordance will not be able to\n            violate the privacy region created by the owning interaction.\n            ',
            tunable_type=bool,
            default=True)
    }

    def register_static_callbacks(self,
                                  broadcaster_request_owner,
                                  object_tuning_id=DEFAULT):
        register_privacy_callback = getattr(
            broadcaster_request_owner,
            'register_sim_can_violate_privacy_callback', None)
        if register_privacy_callback is not None:
            register_privacy_callback(self._on_privacy_violation,
                                      object_tuning_id=object_tuning_id)

    def _on_privacy_violation(self, interaction, sim):
        if self.actor_can_violate_privacy_from_owning_interaction:
            (affordance_target, context) = self._get_target_and_context(
                interaction.get_resolver(), sim)
            for entry in self.affordances:
                if not sim.test_super_affordance(entry.affordance,
                                                 affordance_target, context):
                    return False
            return True
        return False

    def _get_target_and_context(self, resolver, affected_object):
        affordance_target = resolver.get_participant(
            self.affordance_target
        ) if self.affordance_target is not None else None
        if affordance_target is not None:
            if affordance_target.is_sim:
                affordance_target = affordance_target.get_sim_instance()
        insert_strategy = QueueInsertStrategy.NEXT if self.affordance_must_run_next else QueueInsertStrategy.LAST
        context = InteractionContext(affected_object,
                                     InteractionContext.SOURCE_SCRIPT,
                                     self.affordance_priority,
                                     run_priority=self.affordance_run_priority,
                                     insert_strategy=insert_strategy)
        return (affordance_target, context)

    def _select_and_push_affordance(self, affected_object, target, context):
        weighted_options = [(entry.weight, entry.affordance)
                            for entry in self.affordances]
        if not weighted_options:
            return
        affordance = sims4.random.weighted_random_item(weighted_options)
        affected_object.push_super_affordance(affordance, target, context)

    def _apply_broadcaster_effect(self, broadcaster, affected_object):
        if not affected_object.is_sim:
            return
        if broadcaster.interaction is not None:
            participants = broadcaster.interaction.get_participants(
                ParticipantType.AllSims)
            if affected_object in participants:
                return
        (affordance_target, context) = self._get_target_and_context(
            broadcaster.get_resolver(affected_object), affected_object)
        self._select_and_push_affordance(affected_object, affordance_target,
                                         context)
コード例 #27
0
class SimInfoSpawnerTags:
    SIM_SPAWNER_TAGS = TunableList(
        description=
        '\n        A list of tags for Sims to spawn when traveling and moving on/off lot.\n        Note: Tags are resolved in order until a spawn point has been found that\n        contains the tag.\n        ',
        tunable=TunableEnumEntry(tunable_type=Tag, default=Tag.INVALID))
コード例 #28
0
class RecordTrendsElement(elements.ParentElement, ObjectCreationMixin,
                          HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'subject':
        TunableEnumEntry(
            description=
            '\n            The subject we want to record trends from.\n            ',
            tunable_type=ParticipantTypeSingleSim,
            default=ParticipantTypeSingleSim.Actor),
        'continuation':
        TunableContinuation(
            description=
            '\n            The continuation to push if we recorded a trend.\n            '
        ),
        'celebrity_tests':
        OptionalTunable(
            description=
            '\n            If enabled, we will run these tests and attempt to apply the\n            celebrity trend if they pass.\n            ',
            tunable=TunableTestSet(
                description=
                '\n                The tests to determine whether or not we should apply the celebrity\n                trend to the video recorded by this interaction.\n                '
            )),
        'locked_args': {
            'creation_data': None
        }
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(*args, **kwargs)
        self.interaction = interaction
        self.creation_data = _TrendsCreationData()
        self.sequence = sequence
        self.resolver = interaction.get_resolver()
        self._recorded_sim = None
        self._registered_events = []

    def unregister_trend_events(self):
        if self._registered_events:
            event_manager = services.get_event_manager()
            event_manager.unregister(self, self._registered_events)
            self._registered_events.clear()

    def handle_event(self, sim_info, event_type, resolver, *_, **__):
        if self.creation_data.has_recorded_trend_tag or sim_info.sim_id != self._recorded_sim.id:
            return
        if event_type == TestEvent.SkillValueChange:
            skill = resolver.event_kwargs['skill']
            if skill.trend_tag is not None:
                self.creation_data.record_trend_tag(skill.trend_tag)
        if self.creation_data.has_recorded_trend_tag:
            self.unregister_trend_events()

    def _record_static_trends(self):
        if self.celebrity_tests is not None and self.celebrity_tests.run_tests(
                self.resolver):
            self.creation_data.record_trend_tag(TrendTuning.CELEBRITY_TREND)
            return
        elif self._recorded_sim.age == Age.CHILD or self._recorded_sim.age == Age.TODDLER:
            self.creation_data.record_trend_tag(
                TrendTuning.TODDLER_CHILD_TREND)
            return

    def _start_recording(self, _):
        self._recorded_sim = self.interaction.get_participant(self.subject)
        if self._recorded_sim is None:
            logger.error('Subject is None for {} on {}.', self.subject,
                         self.interaction)
        self._record_static_trends()
        if not self.creation_data.has_recorded_trend_tag:
            event_manager = services.get_event_manager()
            event_manager.register_single_event(self,
                                                TestEvent.SkillValueChange)
            self._registered_events.append(TestEvent.SkillValueChange)

    def _stop_recording(self, _):
        if services.current_zone().is_zone_shutting_down:
            return
        self.unregister_trend_events()
        created_object = self.create_object(self.resolver)
        if created_object is None:
            logger.error('Failed to create trend recording {} on {}',
                         self.creation_data.recorded_trend_tag,
                         self.interaction)
            return
        self.interaction.context.create_target_override = created_object
        self.interaction.push_tunable_continuation(self.continuation)

    def _run(self, timeline):
        sequence = [self._start_recording, self.sequence, self._stop_recording]
        child_element = build_element(sequence,
                                      critical=CleanupType.OnCancelOrException)
        return timeline.run_child(child_element)
コード例 #29
0
class Street(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.street_manager()):
    WORLD_DESCRIPTION_TUNING_MAP = TunableMapping(description='\n        A mapping between Catalog world description and street tuning instance.\n        This way we can find out what world description the current zone\n        belongs to at runtime then grab its street tuning instance.\n        ', key_type=TunableWorldDescription(description='\n            Catalog-side World Description.\n            ', pack_safe=True), value_type=TunableReference(description="\n            Street Tuning instance. This is retrieved at runtime based on what\n            the active zone's world description is.\n            ", pack_safe=True, manager=services.street_manager()), key_name='WorldDescription', value_name='Street')
    INSTANCE_TUNABLES = {'open_street_director': TunablePackSafeReference(description='\n            The Scheduling Open Street Director to use for this world file.\n            This open street director will be able to load object layers and\n            spin up situations.\n            ', manager=services.get_instance_manager(sims4.resources.Types.OPEN_STREET_DIRECTOR)), 'travel_lot': OptionalTunable(description='\n            If enabled then this street will have a specific lot that it will\n            want to travel to when we travel to this "street."\n            ', tunable=TunableLotDescription(description='\n                The specific lot that we will travel to when asked to travel to\n                this street.\n                ')), 'townie_demographics': TunableTuple(description='\n            Townie population demographics for the street.\n            ', target_population=OptionalTunable(description='\n                If enabled, Sims created for other purposes will passively be\n                assigned to live on this street, gaining the filter features.\n                Sims are assigned out in round robin fashion up until all\n                streets have reached their target, after which those streets\n                will be assigned Sims in round robin fashion past their target.\n                \n                If disabled, this street will not passively be assigned townies\n                unless the Lives On Street filter explicitly requires the\n                Sim to be on the street.\n                ', tunable=TunableRange(description="\n                    The ideal number of townies that live on the street.\n                    \n                    0 is valid if you don't want Sims to live on this street\n                    while other streets haven't met their target population.\n                    ", tunable_type=int, default=1, minimum=0)), filter_features=TunableList(description='\n                Sims created as townies living on this street, they will gain\n                one set of features in this list. Features are applied as\n                Sim creation tags and additional filter terms to use.\n                ', tunable=TunableTuple(description='\n                    ', filter_terms=TunableList(description='\n                        Filter terms to inject into the filter.\n                        ', tunable=FilterTermVariant(conform_optional=True)), sim_creator_tags=TunableReference(description="\n                        Tags to inject into the filter's Sim template.\n                        ", manager=services.get_instance_manager(sims4.resources.Types.TAG_SET), allow_none=True, class_restrictions=('TunableTagSet',)), sim_name_type=TunableEnumEntry(description='\n                        What type of name the sim should have.\n                        ', tunable_type=SimNameType, default=SimNameType.DEFAULT), weight=TunableRange(description='\n                        Weighted chance.\n                        ', tunable_type=float, default=1, minimum=0)))), 'valid_conditional_layers': TunableSet(description='\n            A list of all of the conditional_layers on this Street.\n            ', tunable=TunableReference(description='\n                A reference to a conditional layer that exists on this Street.\n                ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER), pack_safe=True)), 'tested_conditional_layers': TunableList(description='\n            The list of conditional layers that will load in to the the street\n            if its tests pass.\n            \n            NOTE: Only a subset of tests are registered to listen for updates.\n            Check with your GPE if you expect to have the conditional layers update\n            with test events. Otherwise, they will only be tested on zone-load.\n            ', tunable=TunableTuple(description='\n                A list of all of the conditional_layers to load into this Street.\n                ', conditional_layer=TunableReference(description='\n                    A reference to a conditional layer that exists on this Street.\n                    ', manager=services.get_instance_manager(sims4.resources.Types.CONDITIONAL_LAYER), pack_safe=True), tests=TunableGlobalTestList(description='\n                    The tests that must pass in order for this conditional layer\n                    to be loaded in. If tests are empty, the conditional layer\n                    is considered valid.\n                    '), process_after_event_handled=Tunable(description='\n                     When True, conditional layer requests triggered by the test\n                     events will be ordered to prioritize client-layer requests\n                     first, then gameplay-layers.\n                     ', tunable_type=bool, default=False), test_on_managed_world_edit_mode_load=Tunable(description='\n                    By default, conditional layers are not tested and started when entering\n                    from Managed World Edit Mode.  Those layers which are enabled via this\n                    option will be tested and started even in Managed World Edit Mode.\n                    Generally these should be Client Only layers, but no such restriction is\n                    enforced.\n                    ', tunable_type=bool, default=False))), 'beaches': TunableList(description='\n            List of locations to place beaches.\n            ', tunable=TunableTuple(description='\n                Beach creation data.\n                ', position=TunableVector3(description='\n                    The position to create the beach at.\n                    ', default=TunableVector3.DEFAULT_ZERO), forward=TunableVector2(description='\n                    The forward vector of the beach object.\n                    ', default=Vector2.Y_AXIS())), unique_entries=True), 'civic_policy': StreetProvider.TunableFactory(description='\n            Tuning to control the civic policy voting and enactment process for\n            a street.\n            '), 'initial_street_eco_footprint_override': OptionalTunable(description='\n            If enabled, overrides the initial value of the street eco footprint\n            statistic.\n            ', tunable=Tunable(description='\n                The initial value of the street eco footprint statistic.\n                ', tunable_type=float, default=0))}
    ZONE_IDS_BY_STREET = None
    street_to_lot_id_to_zone_ids = {}

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

    @classmethod
    def get_lot_to_travel_to(cls):
        if cls is services.current_street():
            return
        import world.lot
        return world.lot.get_lot_id_from_instance_id(cls.travel_lot)

    @classmethod
    def has_conditional_layer(cls, conditional_layer):
        return conditional_layer in cls.valid_conditional_layers

    @classmethod
    def clear_caches(cls):
        Street.street_to_lot_id_to_zone_ids.clear()
        Street.ZONE_IDS_BY_STREET = None
コード例 #30
0
class TeleportTuning:
    TELEPORT_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping from a a teleport style to the animation, xevt and vfx data\n        that the Sim will use when a teleport is triggered.\n        ',
        key_type=TunableEnumEntry(
            description='\n            Teleport style.\n            ',
            tunable_type=TeleportStyle,
            default=TeleportStyle.NONE,
            pack_safe=True,
            invalid_enums=(TeleportStyle.NONE, )),
        value_type=TunableTuple(
            description=
            '\n            Animation and vfx data data to be used when the teleport is \n            triggered.\n            ',
            animation_outcomes=TunableList(
                description=
                '\n                One of these animations will be played when the teleport\n                happens, and weights + modifiers can be used to determine\n                exactly which animation is played based on tests.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A pairing of animation and weights that determine which\n                    animation is played when using this teleport style.  Any\n                    tests in the multipliers will be using the context from\n                    the interaction that plays the teleportStyle.\n                    ',
                    animation=TunableAnimationReference(
                        description=
                        '\n                        Reference of the animation to be played when the teleport is\n                        triggered.\n                        ',
                        pack_safe=True,
                        callback=None),
                    weight=TunableMultiplier.TunableFactory(
                        description=
                        '\n                        A tunable list of tests and multipliers to apply to the \n                        weight of the animation that is selected for the teleport.\n                        '
                    ))),
            start_teleport_vfx_xevt=Tunable(
                description=
                '\n                Xevent when the Sim starts teleporting to play the fade out\n                VFX.\n                ',
                tunable_type=int,
                default=100),
            start_teleport_fade_sim_xevt=Tunable(
                description=
                '\n                Xevent when the sim starts teleporting to start the fading\n                of the Sim.\n                ',
                tunable_type=int,
                default=100),
            fade_out_effect=OptionalTunable(
                description=
                '\n                If enabled, play an additional VFX on the specified \n                fade_out_xevt when fading out the Sim.\n                ',
                tunable=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim fades out before actual\n                    changing its position.\n                    This effect will not be parented to the Sim, but instead will\n                    play on the bone position without attachment.  This will\n                    guarantee the VFX will not become invisible as the Sim \n                    disappears.\n                    i.e. Vampire bat teleport spawns VFX on the Sims position\n                    '
                ),
                enabled_name='play_effect',
                disabled_name='no_effect'),
            tested_fade_out_effect=TunableTestedList(
                description=
                '\n                A list of possible fade out effects to play tested against\n                the Sim that is teleporting.\n                ',
                tunable_type=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim fades out before actual\n                    changing its position.\n                    This effect will not be parented to the Sim, but instead will\n                    play on the bone position without attachment.  This will\n                    guarantee the VFX will not become invisible as the Sim \n                    disappears.\n                    i.e. Vampire bat teleport spawns VFX on the Sims position\n                    '
                )),
            teleport_xevt=Tunable(
                description=
                '\n                Xevent where the teleport should happen.\n                ',
                tunable_type=int,
                default=100),
            teleport_effect=OptionalTunable(
                description=
                '\n                If enabled, play an additional VFX on the specified \n                teleport_xevt when the teleport (actual movement of the \n                position of the Sim) happens.\n                ',
                tunable=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim is teleported.\n                    '
                ),
                enabled_name='play_effect',
                disabled_name='no_effect'),
            teleport_min_distance=TunableDistanceSquared(
                description=
                '\n                Minimum distance between the Sim and its target to trigger\n                a teleport.  If the distance is lower than this value, the\n                Sim will run a normal route.\n                ',
                default=5.0),
            teleport_cost=OptionalTunable(
                description=
                '\n                If enabled, the teleport will have an statistic cost every\n                time its triggered. \n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Cost and statistic to charge for a teleport event.\n                    ',
                    teleport_statistic=TunableReference(
                        description=
                        '\n                        The statistic we are operating on when a teleport \n                        happens.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.STATISTIC),
                        pack_safe=True),
                    cost=TunableRange(
                        description=
                        '\n                        On teleport, subtract the teleport_statistic by this\n                        amount. \n                        ',
                        tunable_type=int,
                        default=1,
                        minimum=0),
                    cost_is_additive=Tunable(
                        description=
                        '\n                        If checked, the cost is additive.  Rather than deducting the cost, it will be added to\n                        the specified teleport statistic.  Additionally, cost will be checked against the max value\n                        of the statistic rather than the minimum value when determining if the cost is affordable\n                        ',
                        tunable_type=bool,
                        default=False)),
                disabled_name='no_teleport_cost',
                enabled_name='specify_cost'),
            fade_duration=TunableSimMinute(
                description=
                '\n                Default fade time (in sim minutes) for the fading of the Sim\n                to happen.',
                default=0.5)))

    @classmethod
    def get_teleport_data(cls, teleport_type):
        return cls.TELEPORT_DATA_MAPPING.get(teleport_type)