def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._picker_dialogs = {} self._text_listeners = CallableList() self.existing_text = None self.ingredient_check = None self._changes_made = False
def __init__(self, sim_info): self._sim_info = sim_info self._current_away_action = None self._on_away_action_started = CallableList() self._on_away_action_ended = CallableList() self.add_on_away_action_started_callback(self._resend_away_action) self.add_on_away_action_ended_callback(self._resend_away_action)
def __init__(self, owner, resolver=None, *args, **kwargs): super().__init__(*args, **kwargs) self._owner = owner.ref() self._resolver = resolver self._additional_responses = {} self.response = None self._timestamp = None self._listeners = CallableList()
def __init__(self, sim_info): super().__init__() self._sim_info = sim_info self._relationships = {} self._suppress_client_updates = False self.spouse_sim_id = None self._relationship_multipliers = {} self._create_relationship_callbacks = CallableList()
def __init__(self, lot_id, zone_id, routing_surface=None): self.center = None self.lot_id = lot_id self._tags = set() if routing_surface is None: routing_surface = routing.SurfaceIdentifier( zone_id, 0, routing.SURFACETYPE_WORLD) self._routing_surface = routing_surface self._valid_slots = 0 self._on_spawn_points_changed = CallableList()
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._crafting_cache = CraftingObjectCache() self._sim_spawn_conditions = collections.defaultdict(set) self._client_connect_callbacks = CallableList() self._portal_cache = WeakSet() self._portal_added_callbacks = CallableList() self._portal_removed_callbacks = CallableList() self._front_door_candidates_changed_callback = CallableList() self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots
def __init__(self, path, type_enum, use_guid_for_ref=False): self._tuned_classes = {} self._callback_helper = {} self._verify_tunable_callback_helper = {} self._class_templates = [] self.PATH = path self.TYPE = type_enum self._load_all_complete = False self._load_all_complete_callbacks = CallableList() self._use_guid_for_ref = use_guid_for_ref
class SpawnPoint: __qualname__ = 'SpawnPoint' ARRIVAL_SPAWN_POINT_TAG = TunableEnumEntry(description='\n The Tag associated with Spawn Points at the front of the lot.\n ', tunable_type=Tag, default=Tag.INVALID) VISITOR_ARRIVAL_SPAWN_POINT_TAG = TunableEnumEntry(description='\n The Tag associated with Spawn Points nearby the lot for visitors.\n ', tunable_type=Tag, default=Tag.INVALID) def __init__(self, lot_id, zone_id, routing_surface=None): self.center = None self.lot_id = lot_id self._tags = set() if routing_surface is None: routing_surface = routing.SurfaceIdentifier(zone_id, 0, routing.SURFACETYPE_WORLD) self._routing_surface = routing_surface self._valid_slots = 0 self._on_spawn_points_changed = CallableList() def __str__(self): return 'Name:{:20} Lot:{:15} Center:{:45} Tags:{}'.format(self.get_name(), self.lot_id, self.center, self.get_tags()) @property def routing_surface(self): return self._routing_surface def get_name(self): raise NotImplementedError def get_tags(self): return self._tags def has_tag(self, tag): if tag is not None: return tag in self.get_tags() return False def next_spawn_spot(self): raise NotImplementedError def get_slot_pos(self, index=None): raise NotImplementedError @property def valid_slots(self): return self._valid_slots def reset_valid_slots(self): self._valid_slots = 0 def register_spawn_point_changed_callback(self, callback): self._on_spawn_points_changed.append(callback) def unregister_spawn_point_changed_callback(self, callback): self._on_spawn_points_changed.remove(callback) def get_slot_positions(self): raise NotImplementedError
def __init__(self, owner): super().__init__(owner) self._crafting_process = None self._use_base_recipe = False self.object_mutated_listeners = CallableList() self._servings_statistic_tracker_handle = None self._quality_change_callback_added = False self._spoil_listener_handle = None self._is_final_product = False self._last_spoiled_time = None self._spoil_timer_state_value = CraftingTuning.SPOILED_STATE_VALUE
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._alarm_handle = None self._processing_task = None self._on_update_callbacks = CallableList() self._pending_broadcasters = [] self._active_broadcasters = [] self._cluster_requests = {} self._object_cache = None self._pending_update = False self._quadtrees = defaultdict(sims4.geometry.QuadTree)
def __init__(self, sim_info=None, situation=None, goal_id=0, count=0, **kwargs): self._sim_info = sim_info self._situation = situation self.id = goal_id self._on_goal_completed_callbacks = CallableList() self._completed_time = None self._count = count
class UiDialogBase: def __init__ (self, *args, **kwargs): super().__init__(*args, **kwargs) self.response = None self._listeners = CallableList() def add_listener (self, listener_callback): self._listeners.append(listener_callback) def distribute_dialog (self, dialog_type, dialog_msg, immediate = False): distributor = Distributor.instance() distributor.add_event(dialog_type, dialog_msg, immediate = immediate) def get_phone_ring_type (self): return PhoneRingType.NO_RING @property def responses (self): return tuple() def has_responses (self): return self.responses or self._additional_responses def on_response_received (self): pass def respond (self, response: int) -> bool: try: self.response = response self._listeners(self) return True finally: self.on_response_received() return False def show_dialog (self, on_response = None, **kwargs): if on_response is not None: self.add_listener(on_response) pythonutils.try_highwater_gc() services.ui_dialog_service().dialog_show(self, self.get_phone_ring_type(), **kwargs) def do_auto_respond (self, auto_response = DEFAULT): if auto_response is not DEFAULT: response = auto_response elif ButtonType.DIALOG_RESPONSE_CANCEL in self.responses: response = ButtonType.DIALOG_RESPONSE_CANCEL elif ButtonType.DIALOG_RESPONSE_OK in self.responses: response = ButtonType.DIALOG_RESPONSE_OK else: response = ButtonType.DIALOG_RESPONSE_CLOSED services.ui_dialog_service().dialog_respond(self.dialog_id, response)
def __init__(self, path, type_enum, use_guid_for_ref=False, base_game_only=False, require_reference=False): self._tuned_classes = {} self._remapped_keys = {} self._callback_helper = {} self._verify_tunable_callback_helper = {} self._class_templates = [] self.PATH = path self.TYPE = type_enum self._load_all_complete = False self._load_all_complete_callbacks = CallableList() self._use_guid_for_ref = use_guid_for_ref self._base_game_only = base_game_only self._require_reference = require_reference
def __init__(self): super().__init__() self._save_locks = [] self._read_write_locked = False self._save_game_data_proto = serialization.SaveGameData() self.save_timeline = None self._unlocked_callbacks = CallableList() self.once_per_session_telemetry_sent = False self.save_error_code = persistence_error_types.ErrorCodes.NO_ERROR self._zone_data_pb_cache = {} self._world_id_to_region_id_cache = {} self._sim_data_pb_cache = None self._household_pb_cache = None self._world_ids = frozenset()
def __init__(self, zone_id, save_slot_data_id): self.id = zone_id self.neighborhood_id = 0 self.open_street_id = 0 self.lot = Lot(zone_id) self.entitlement_unlock_handlers = {} self._spawner_data = {} self._dynamic_spawn_points = {} self._zone_state = zone_types.ZoneState.ZONE_INIT self._zone_state_callbacks = {} self.all_transition_controllers = weakref.WeakSet() self.navmesh_change_callbacks = CallableListPreventingRecursion() self.wall_contour_update_callbacks = CallableListPreventingRecursion() self.foundation_and_level_height_update_callbacks = CallableListPreventingRecursion( ) self.navmesh_id = None self.object_count = 0 self.is_in_build_buy = False self.objects_to_fixup_post_bb = None self._save_slot_data_id = save_slot_data_id self._royalty_alarm_manager = RoyaltyAlarmManager() self.current_navmesh_fence_id = 1 self._first_visit_to_zone = None self._active_lot_arrival_spawn_point = None self._time_of_last_open_street_save = None for key in zone_types.ZoneState: while key != zone_types.ZoneState.ZONE_INIT: self._zone_state_callbacks[key] = CallableList() self._client = None self._tick_metrics = None
def __init__(self, sim): self._super_interactions = set() self._removing_interactions = set() self._resetting = False self._sim = sim.ref() if sim else None self._watchers = {} self._constraints = {} self.on_changed = CallableList()
def __init__(self, *args, manager_id=0, **kwargs): super().__init__(*args, **kwargs) self.id = manager_id self._objects = {} self._objects_to_be_removed = [] self._registered_callbacks = {} for key in CallbackTypes: self._registered_callbacks[key] = CallableList()
def _register_home_street_change_callback(callbacks, street, callback): if street is None or callback is None: logger.error( 'Attempt to register an incomplete callback: {} for street {}', callback, street) return if street not in callbacks: callbacks[street] = CallableList() callbacks[street].append(callback)
def __init__(self, owner): super().__init__(owner) self._crafting_process = None self._use_base_recipe = False self.object_mutated_listeners = CallableList() self._servings_statistic_tracker_handle = None self._quality_change_callback_added = False self._spoil_listener_handle = None self._is_final_product = False
def __init__(self, lot_id, zone_id, routing_surface=None): self.center = None self.lot_id = lot_id self._tags = set() if routing_surface is None: routing_surface = routing.SurfaceIdentifier(zone_id, 0, routing.SURFACETYPE_WORLD) self._routing_surface = routing_surface self._valid_slots = 0 self._on_spawn_points_changed = CallableList()
def __init__(self, session_id, account, household_id): self.id = session_id self.manager = None self._account = account self._household_id = household_id self._choice_menu = None self._interaction_parameters = {} self.active = True self.zone_id = sims4.zone_utils.get_zone_id() self._selectable_sims = SelectableSims(self) self._active_sim_info = None self._active_sim_changed = CallableList() self.ui_objects = weakref.WeakSet() self.primitives = () self._live_drag_objects = [] self._live_drag_start_system = LiveDragLocation.INVALID self._live_drag_is_stack = False self._live_drag_sell_dialog_active = False
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 detach(self, *detaching_objects): global _nested_arb_detach_callbacks if self.master not in detaching_objects: for detaching_object in detaching_objects: self._attached_actors.remove(detaching_object) super().detach(*detaching_objects) return if _nested_arb_depth > 0: if _nested_arb_detach_callbacks is None: _nested_arb_detach_callbacks = CallableList() super_self = super() _nested_arb_detach_callbacks.append( lambda: super_self.detach(*self._attached_actors)) return True if _nested_arb_detach_callbacks is not None: cl = _nested_arb_detach_callbacks _nested_arb_detach_callbacks = None cl() super().detach(*self._attached_actors)
def register_sim_removed_callback(self, street, callback): if street is None or callback is None: logger.error( 'Attempted to register an incomplete callback: {} for street {}', callback, street) return if street not in self._street_sim_removed_callbacks: self._street_sim_removed_callbacks[street] = CallableList() callback_list = self._street_sim_removed_callbacks[street] if callback not in callback_list: callback_list.register(callback)
def __init__(self, sim_info=None, situation=None, goal_id=0, count=0, locked=False, completed_time=None, secondary_sim_info=None, **kwargs): self._sim_info = sim_info self._secondary_sim_info = secondary_sim_info self._situation = situation self.id = goal_id self._on_goal_completed_callbacks = CallableList() self._completed_time = completed_time self._count = count self._locked = locked self._score_override = None self._goal_status_override = None self._setup = False
def __init__(self, asm_key, context, posture_manifest_overrides=None): super().__init__(asm_key) self.context = context self._posture_manifest_overrides = posture_manifest_overrides self._prop_overrides = {} self._prop_state_values = {} self._vfx_overrides = {} self._sound_overrides = {} self._actors = {} self._virtual_actors = defaultdict(set) self._virtual_actor_relationships = {} self._on_state_changed_events = CallableList()
def __init__(self, owner): super().__init__(owner) self._active_buffs = {} self._get_next_handle_id = UniqueIdGenerator() self._success_chance_modification = 0 self._active_mood = self.DEFAULT_MOOD self._active_mood_intensity = 0 self._active_mood_buff_handle = None self.on_mood_changed = CallableList() self.on_mood_changed.append(self._publish_mood_update) self.on_mood_changed.append(self._send_mood_changed_event) self.load_in_progress = False self.on_buff_added = CallableList() self.on_buff_removed = CallableList() self.buff_update_alarms = {} if self._active_mood is None: logger.error('No default mood tuned in buff_component.py') elif self._active_mood.buffs: initial_buff_ref = self._active_mood.buffs[0] if initial_buff_ref and initial_buff_ref.buff_type: self._active_mood_buff_handle = self.add_buff(initial_buff_ref.buff_type)
def __init__(self, owner, **kwargs): super().__init__(owner, **kwargs) self.owner = owner walkstyle_behavior = self.get_walkstyle_behavior() self._walkstyle_requests = [WalkStyleRequest(self.owner, walkstyle=walkstyle_behavior.default_walkstyle, priority=-1)] self._walk_style_handles = {} self.wading_buff_handle = None self.last_route_has_wading_nodes = False self._routing_stage_event_callbacks = defaultdict(CallableList) if owner.is_sim: owner.remove_component(objects.components.types.FOOTPRINT_COMPONENT) self._path_plan_context_map = {} self.on_slot = None self.stand_slot_reservation_removed_callbacks = CallableList() self._active_follow_path_weakref = None self.on_follow_path = CallableList() self.on_plan_path = CallableList() self.on_intended_location_changed = CallableList() self._current_path = None self._routing_slave_data = [] self._routing_master_ref = None self._default_agent_radius = None self._route_event_context = RouteEventContext() self._route_interaction = None self._animation_context = None self._initial_carry_targets = None self._route_event_provider_requests = None
def __init__(self, *args, sim_id: int = 0, gender: Gender = Gender.MALE, age: Age = Age.ADULT, species: SpeciesExtended = SpeciesExtended.HUMAN, first_name: str = '', last_name: str = '', breed_name: str = '', full_name_key=0, breed_name_key=0, physique: str = '', skin_tone=1, **kwargs): super().__init__(*args, **kwargs) self.sim_id = sim_id or id_generator.generate_object_id() self.primitives = distributor.ops.DistributionSet(self) self.manager = None self._base = BaseSimInfo(sim_id, first_name, last_name, breed_name, full_name_key, breed_name_key, age, gender, species, skin_tone, physique) self._base.voice_pitch = 0.0 self._base.voice_actor = 0 self._base.voice_effect = 0 self._base.facial_attributes = '' self._base.custom_texture = 0 self._base.pelt_layers = '' self._species = SpeciesExtended.get_species(species) self.on_base_characteristic_changed = CallableList() self.on_outfit_changed = CallableList() self.on_outfit_generated = CallableList() self._set_current_outfit_without_distribution( (OutfitCategory.EVERYDAY, 0)) self._previous_outfit = (OutfitCategory.EVERYDAY, 0) self._preload_outfit_list = [] self.on_preload_outfits_changed = CallableList() self.appearance_tracker = AppearanceTracker(self) self.visible_to_client = False
def __init__(self, asm_key, context, posture_manifest_overrides=None): super().__init__(asm_key) self.context = context self._posture_manifest_overrides = posture_manifest_overrides self._prop_overrides = {} self._alt_prop_definitions = {} self._prop_state_values = {} self._vfx_overrides = {} self._sound_overrides = {} self._actors = {} self._virtual_actors = defaultdict(set) self._virtual_actor_relationships = {} self._on_state_changed_events = CallableList() self._boundary_condition_dirty = asm_key in sims4.resources.localwork_no_groupid
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._sim_infos_saved_in_zone = [] self._sim_infos_saved_in_plex_group = [] self._sim_infos_saved_in_open_street = [] self._sims_traveled_to_zone = [] self._sim_infos_injected_into_zone = [] self._sim_info_to_spin_up_action = None self._startup_time = None self._sim_ids_to_skip_preroll = set() self.on_sim_info_removed = CallableList() self._firemeter = None self._sim_info_telemetry_manager = SimInfoTelemetryManager() self._start_all_sims_opted_out_of_fame = False self._sim_info_cap_override = None
def __init__(self, sim_id, rabbit_hole_id=None, starting_phase=RabbitHolePhase.STARTING, picked_skill=None): self.rabbit_hole_id = rabbit_hole_id or id_generator.generate_object_id( ) self.sim_id = sim_id self.alarm_handle = None self.callbacks = CallableList() self.linked_rabbit_holes = [] self.picked_skill = picked_skill self.ignore_travel_cancel_callbacks = False self.current_phase = starting_phase self._selected_affordance = None self.time_remaining_on_load = None
def __init__(self): self._persisted_background_event_id = None self._persisted_special_event_id = None self._special_event_start_alarm = None self._source_venue = None self._active_venue = None self._zone_director = None self._requested_zone_directors = [] self._prior_zone_director_proto = None self._open_street_director_requests = [] self._prior_open_street_director_proto = None self.build_buy_edit_mode = False self.on_venue_type_changed = CallableList() self._venue_start_time = None self._university_housing_household_validation_alarm = None self._university_housing_kick_out_completed = False
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._crafting_cache = CraftingObjectCache() self._sim_spawn_conditions = collections.defaultdict(set) self._water_terrain_object_cache = WaterTerrainObjectCache() self._client_connect_callbacks = CallableList() self._portal_cache = WeakSet() self._portal_added_callbacks = CallableList() self._portal_removed_callbacks = CallableList() self._front_door_candidates_changed_callback = CallableList() self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots self._tag_to_object_list = defaultdict(set) self._whim_set_cache = Counter() self._posture_providing_object_cache = None self._objects_to_ignore_portal_validation_cache = []
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._reservations = WeakKeyDictionary() self._reservations_multi = WeakKeyDictionary() self._use_list_changed_callbacks = CallableList()
class UseListMixin: __qualname__ = 'UseListMixin' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._reservations = WeakKeyDictionary() self._reservations_multi = WeakKeyDictionary() self._use_list_changed_callbacks = CallableList() @property def using_sim(self): if not self._reservations: return return next(self._reservations.keys()) @property def in_use(self): if self._reservations: return True return False def in_use_by(self, sim, owner=None): owners = self._reservations.get(sim) if not owners: return False return owner is None or owner in owners def _may_reserve_obj(self, sim, affordance, context): if self.in_use_by(sim): return True if self.children: for child in self.children: while not child.may_reserve(sim, multi=False, affordance=affordance, context=context): return False return not self._reservations def _may_reserve_part(self, sim, affordance, context, check_overlapping_parts=True): part_list = self.part_owner.parts for part in part_list: if part is self: pass using_sim = part.using_sim while not using_sim is None: if using_sim is sim: pass for si in using_sim.si_state: reserve_object_tests = si.object_reservation_tests while reserve_object_tests: reserve_result = reserve_object_tests.run_tests(si.get_resolver(target=sim)) if not reserve_result: return reserve_result if using_sim.queue.transition_controller is not None: transitioning_interaction = using_sim.queue.transition_controller.interaction if transitioning_interaction.is_super: reserve_object_tests = transitioning_interaction.object_reservation_tests if reserve_object_tests: target_sim = sim if transitioning_interaction.sim is not sim else using_sim reserve_result = reserve_object_tests.run_tests(transitioning_interaction.get_resolver(target=target_sim)) if not reserve_result: return reserve_result while affordance is not None and affordance.is_super: if context is None: logger.error('Attempt to call may_reserve() with an affordance but no context!', owner='maxr') reserve_object_tests = affordance.object_reservation_tests if reserve_object_tests: reserve_result = reserve_object_tests.run_tests(affordance.get_resolver(target=using_sim, context=context)) if not reserve_result: return reserve_result if affordance is not None and not self.supports_affordance(affordance): return TestResult(False, '{} does not support {}'.format(self, affordance)) reserve_result = self._may_reserve_obj(sim, affordance=affordance, context=context) if not reserve_result: return reserve_result if check_overlapping_parts: for overlapping_part in self.get_overlapping_parts(): if overlapping_part is self: pass reserve_result = overlapping_part._may_reserve_part(sim, None, None, check_overlapping_parts=False) while not reserve_result: return reserve_result return TestResult(True, 'Passed all reservation tests.') def may_reserve(self, sim, multi=False, affordance=None, context=None): if self.parts: logger.callstack('Cannot reserve an object that has parts: {}, for {} running {} - multi: {}', self, sim, affordance, multi, level=sims4.log.LEVEL_ERROR) return False if multi: return True if self.is_part: result = self._may_reserve_part(sim, affordance, context) else: result = self._may_reserve_obj(sim, affordance, context) return result def reserve(self, sim, owner, multi=False): use_list = self._reservations_multi if multi else self._reservations sim_list = setdefault_callable(use_list, sim, WeakSet) sim_list.add(owner) self._use_list_changed_callbacks(user=sim, added=True) def release(self, sim, owner, multi=False): use_list = self._reservations_multi if multi else self._reservations sim_list = use_list.get(sim) sim_list.remove(owner) if not sim_list: del use_list[sim] self._use_list_changed_callbacks(user=sim, added=False) self._destroy_if_necessary() def make_transient(self): self.transient = True self._destroy_if_necessary() def _destroy_if_necessary(self): if not self._reservations_multi and not self._reservations and self.transient: if self.is_part: self.part_owner.schedule_destroy_asap(source=self, cause='Destroying unused transient part.') else: self.schedule_destroy_asap(source=self, cause='Destroying unused transient object.') def usable_by_transition_controller(self, transition_controller): if transition_controller is not None: required_sims = transition_controller.interaction.required_sims() if self.is_part: all_overlapping_parts = self.get_overlapping_parts() all_overlapping_parts.append(self) for part in all_overlapping_parts: for user in part.get_users(): while user not in required_sims: return False return True for required_sim in required_sims: while self.in_use_by(required_sim): return True return False def get_users(self, sims_only=False, include_multi=True): targets = (self,) if not self.parts else self.parts if include_multi: return {sim for target in targets for sim in itertools.chain(target._reservations, target._reservations_multi) if not sims_only or sim.is_sim} return {sim for target in targets for sim in target._reservations if not sims_only or sim.is_sim} def on_reset_get_interdependent_reset_records(self, reset_reason, reset_records): super().on_reset_get_interdependent_reset_records(reset_reason, reset_records) relevant_sims = self.get_users(sims_only=True) for sim in relevant_sims: if self.reset_reason() == ResetReason.BEING_DESTROYED: reset_records.append(ResetRecord(sim, ResetReason.RESET_EXPECTED, self, 'In use list of object being destroyed.')) body_target_part_owner = sim.posture_state.body.target if body_target_part_owner is not None and body_target_part_owner.is_part: body_target_part_owner = body_target_part_owner.part_owner transition_controller = sim.queue.transition_controller while body_target_part_owner is self or transition_controller is None or not transition_controller.will_derail_if_given_object_is_reset(self): reset_records.append(ResetRecord(sim, ResetReason.RESET_EXPECTED, self, 'Transitioning To or In.')) def register_on_use_list_changed(self, callback): self._use_list_changed_callbacks.append(callback) def unregister_on_use_list_changed(self, callback): if callback in self._use_list_changed_callbacks: self._use_list_changed_callbacks.remove(callback) def _print_in_use(self): if self._reservations: for sim in self._reservations: logger.debug(' Reservation: {}', sim) if self._reservations_multi: for sim in self._reservations_multi: logger.debug(' Reservation_Multi: {}', sim)
class UiDialog(HasTunableFactory, AutoFactoryInit): __qualname__ = 'UiDialog' DIALOG_MSG_TYPE = Consts_pb2.MSG_UI_DIALOG_SHOW FACTORY_TUNABLES = {'title': OptionalTunable(description='\n If enabled, this dialog will include title text.\n ', tunable=TunableLocalizedStringFactory(description="\n The dialog's title.\n ")), 'text': TunableLocalizedStringFactoryVariant(description="\n The dialog's text.\n "), 'text_tokens': OptionalTunable(description='\n If enabled, define text tokens to be used to localized text.\n ', tunable=LocalizationTokens.TunableFactory(description='\n Define the text tokens that are available to all text fields in\n the dialog, such as title, text, responses, default and initial\n text values, tooltips, etc.\n '), disabled_value=DEFAULT), 'icon': OptionalTunable(description='\n If enabled, specify an icon to be displayed.\n ', tunable=TunableIconVariant(), needs_tuning=True), 'secondary_icon': OptionalTunable(description='\n If enabled, specify a secondary icon to be displayed. Only certain\n dialog types may support this field.\n ', tunable=TunableIconVariant(), needs_tuning=True), 'phone_ring_type': TunableEnumEntry(description='\n The phone ring type of this dialog. If tuned to anything other\n than None this dialog will only appear after clicking on the phone.\n ', tunable_type=PhoneRingType, needs_tuning=True, default=PhoneRingType.NO_RING), 'audio_sting': OptionalTunable(description='\n If enabled, play an audio sting when the dialog is shown.\n ', tunable=TunablePlayAudio()), 'ui_responses': TunableList(description='\n A list of buttons that are mapped to UI commands.\n ', tunable=get_defualt_ui_dialog_response()), 'dialog_options': TunableEnumFlags(description='\n Options to apply to the dialog.\n ', enum_type=UiDialogOption, allow_no_flags=True, default=UiDialogOption.DISABLE_CLOSE_BUTTON)} def __init__(self, owner, resolver=None, *args, **kwargs): super().__init__(*args, **kwargs) self._owner = owner.ref() self._resolver = resolver self._additional_responses = {} self.response = None self._timestamp = None self._listeners = CallableList() @property def accepted(self) -> bool: return self.response is not None and self.response != ButtonType.DIALOG_RESPONSE_CLOSED @property def responses(self): return tuple() @property def owner(self): return self._owner() @property def dialog_type(self): return self._dialog_type def add_listener(self, listener_callback): self._listeners.append(listener_callback) def set_responses(self, responses): self._additional_responses = tuple(responses) def has_responses(self): return self.responses or self._additional_responses def _get_responses_gen(self): yield self.responses yield self._additional_responses yield self.ui_responses def respond(self, response) -> bool: try: self.response = response self._listeners(self) return True finally: self.on_response_received() return False def update(self) -> bool: return True def show_dialog(self, on_response=None, **kwargs): if self.audio_sting is not None: play_tunable_audio(self.audio_sting, None) if on_response is not None: self.add_listener(on_response) pythonutils.try_highwater_gc() services.ui_dialog_service().dialog_show(self, self.phone_ring_type, **kwargs) def distribute_dialog(self, dialog_type, dialog_msg): distributor = Distributor.instance() distributor.add_event(dialog_type, dialog_msg) def _build_localized_string_msg(self, string, *additional_tokens): if string is None: logger.callstack('_build_localized_string_msg received None for the string to build. This is probably not intended.', owner='tingyul') return tokens = () if self._resolver is not None: if self.text_tokens is DEFAULT: tokens = self._resolver.get_localization_tokens() elif self.text_tokens is not None: tokens = self.text_tokens.get_tokens(self._resolver) return string(*tokens + additional_tokens) def _build_response_arg(self, response, response_msg, tutorial_id=None, additional_tokens=(), **kwargs): response_msg.choice_id = response.dialog_response_id response_msg.ui_request = response.ui_request if response.text is not None: response_msg.text = self._build_localized_string_msg(response.text, *additional_tokens) if tutorial_id is not None: response_msg.tutorial_args.tutorial_id = tutorial_id def build_msg(self, additional_tokens=(), icon_override=DEFAULT, secondary_icon_override=DEFAULT, **kwargs): msg = Dialog_pb2.UiDialogMessage() msg.dialog_id = self.dialog_id msg.owner_id = self.owner.id msg.dialog_type = Dialog_pb2.UiDialogMessage.DEFAULT if self.title is not None: msg.title = self._build_localized_string_msg(self.title, *additional_tokens) msg.text = self._build_localized_string_msg(self.text, *additional_tokens) if icon_override is DEFAULT: if self.icon is not None: icon_info = self.icon(self._resolver) key = icon_info[0] if key is not None: msg.icon.type = key.type msg.icon.group = key.group msg.icon.instance = key.instance build_icon_info_msg(icon_info, None, msg.icon_info) elif icon_override is not None: build_icon_info_msg(icon_override, None, msg.icon_info) if secondary_icon_override is DEFAULT: if self.secondary_icon is not None: icon_info = self.secondary_icon(self._resolver) build_icon_info_msg(icon_info, None, msg.secondary_icon_info) elif secondary_icon_override is not None: build_icon_info_msg(secondary_icon_override, None, msg.secondary_icon_info) msg.dialog_options = self.dialog_options responses = [] responses.extend(self._get_responses_gen()) responses.sort(key=lambda response: response.sort_order) for response in responses: response_msg = msg.choices.add() self._build_response_arg(response, response_msg, additional_tokens=additional_tokens, **kwargs) return msg def on_response_received(self): pass def do_auto_respond(self): if ButtonType.DIALOG_RESPONSE_CANCEL in self.responses: response = ButtonType.DIALOG_RESPONSE_CANCEL elif ButtonType.DIALOG_RESPONSE_OK in self.responses: response = ButtonType.DIALOG_RESPONSE_OK else: response = ButtonType.DIALOG_RESPONSE_CLOSED services.ui_dialog_service().dialog_respond(self.dialog_id, response)
class Client: __qualname__ = 'Client' _interaction_source = interactions.context.InteractionContext.SOURCE_PIE_MENU _interaction_priority = interactions.priority.Priority.High def __init__(self, session_id, account, household_id): self.id = session_id self.manager = None self._account = account self._household_id = household_id self._choice_menu = None self._interaction_parameters = {} self.active = True self.zone_id = sims4.zone_utils.get_zone_id() self._selectable_sims = SelectableSims(self) self._active_sim_info = None self._active_sim_changed = CallableList() self.ui_objects = weakref.WeakSet() self.primitives = () self._live_drag_objects = [] self._live_drag_start_system = LiveDragLocation.INVALID self._live_drag_is_stack = False self._live_drag_sell_dialog_active = False def __repr__(self): return '<Client {0:#x}>'.format(self.id) @property def account(self): return self._account @distributor.fields.Field(op=distributor.ops.UpdateClientActiveSim) def active_sim_info(self): return self._active_sim_info resend_active_sim_info = active_sim_info.get_resend() @active_sim_info.setter def active_sim_info(self, sim_info): self._set_active_sim_without_field_distribution(sim_info) @property def active_sim(self): if self.active_sim_info is not None: return self.active_sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS) @active_sim.setter def active_sim(self, sim): self.active_sim_info = sim.sim_info def _set_active_sim_without_field_distribution(self, sim_info): if self._active_sim_info is not None and self._active_sim_info is sim_info: return current_sim = self._active_sim_info.get_sim_instance() if self._active_sim_info is not None else None new_sim = sim_info.get_sim_instance() if sim_info is not None else None if sim_info is not None: self._active_sim_info = sim_info sim_info.household.on_active_sim_changed(sim_info) else: self._active_sim_info = None self.notify_active_sim_changed(current_sim, new_sim) @property def choice_menu(self): return self._choice_menu @property def interaction_source(self): return self._interaction_source @interaction_source.setter def interaction_source(self, value): if value is None: del self._interaction_source else: self._interaction_source = value @property def interaction_priority(self): return self._interaction_priority @interaction_priority.setter def interaction_priority(self, value): if value is None: del self._interaction_priority else: self._interaction_priority = value @property def household_id(self): return self._household_id @property def household(self): household_manager = services.household_manager() if household_manager is not None: return household_manager.get(self._household_id) @property def selectable_sims(self): return self._selectable_sims def create_interaction_context(self, sim, **kwargs): context = interactions.context.InteractionContext(sim, self.interaction_source, self.interaction_priority, client=self, **kwargs) return context @property def live_drag_objects(self): return self._live_drag_objects def get_interaction_parameters(self): return self._interaction_parameters def set_interaction_parameters(self, **kwargs): self._interaction_parameters = kwargs def set_choices(self, new_choices): self._choice_menu = new_choices def select_interaction(self, choice_id, revision): if self.choice_menu is not None and revision == self.choice_menu.revision: choice_menu = self.choice_menu self._choice_menu = None self.set_interaction_parameters() try: return choice_menu.select(choice_id) except: if choice_menu.context.sim is not None: choice_menu.context.sim.reset(ResetReason.RESET_ON_ERROR, cause='Exception while selecting interaction from the pie menu.') raise def get_create_op(self, *args, **kwargs): return distributor.ops.ClientCreate(self, is_active=True, *args, **kwargs) def get_delete_op(self): return distributor.ops.ClientDelete() def get_create_after_objs(self): active = self.active_sim if active is not None: yield active household = self.household if household is not None: yield household @property def valid_for_distribution(self): return True def refresh_achievement_data(self): active_sim_info = None if self.active_sim is not None: active_sim_info = self.active_sim.sim_info self.account.achievement_tracker.refresh_progress(active_sim_info) def send_message(self, msg_id, msg): if self.active: omega.send(self.id, msg_id, msg.SerializeToString()) else: logger.warn('Message sent to client {} after it has already disconnected.', self) def send_serialized_message(self, msg_id, msg): if self.active: omega.send(self.id, msg_id, msg) else: logger.warn('Serialized message sent to client {} after it has already disconnected.', self) def set_next_sim(self): sim_info = self._selectable_sims.get_next_selectable(self._active_sim_info) if sim_info is self.active_sim_info: return False return self.set_active_sim_info(sim_info) def set_next_sim_or_none(self, only_if_this_active_sim_info=None): if only_if_this_active_sim_info is not None and self._active_sim_info is not only_if_this_active_sim_info: return sim_info = self._selectable_sims.get_next_selectable(self._active_sim_info) if sim_info is None: return self.set_active_sim_info(None) if sim_info is self._active_sim_info: return self.set_active_sim_info(None) return self.set_active_sim_info(sim_info) def set_active_sim_by_id(self, sim_id): if self.active_sim_info is not None and self.active_sim_info.id == sim_id: return False for sim_info in self._selectable_sims: while sim_info.sim_id == sim_id: if not sim_info.is_enabled_in_skewer: return False return self.set_active_sim_info(sim_info) return False def set_active_sim(self, sim): return self.set_active_sim_info(sim.sim_info) def set_active_sim_info(self, sim_info): with telemetry_helper.begin_hook(writer, TELEMETRY_HOOK_ACTIVE_SIM_CHANGED, sim=sim_info): pass self.active_sim_info = sim_info return self._active_sim_info is not None def add_selectable_sim_info(self, sim_info, send_relationship_update=True): self._selectable_sims.add_selectable_sim_info(sim_info, send_relationship_update=send_relationship_update) if self.active_sim_info is None: self.set_next_sim() self.household.refresh_aging_updates(sim_info) def add_selectable_sim_by_id(self, sim_id): sim_info = services.sim_info_manager().get(sim_id) if sim_info is not None: self.add_selectable_sim_info(sim_info) def remove_selectable_sim_info(self, sim_info): self._selectable_sims.remove_selectable_sim_info(sim_info) if self.active_sim_info is None: self.set_next_sim() self.household.refresh_aging_updates(sim_info) def remove_selectable_sim_by_id(self, sim_id): if len(self._selectable_sims) <= 1: return False sim_info = services.sim_info_manager().get(sim_id) if sim_info is not None: self.remove_selectable_sim_info(sim_info) return True def make_all_sims_selectable(self): self.clear_selectable_sims() for sim_info in services.sim_info_manager().objects: self._selectable_sims.add_selectable_sim_info(sim_info) self.set_next_sim() def clear_selectable_sims(self): self.active_sim_info = None self._selectable_sims.clear_selectable_sims() def register_active_sim_changed(self, callback): if callback not in self._active_sim_changed: self._active_sim_changed.append(callback) def unregister_active_sim_changed(self, callback): if callback in self._active_sim_changed: self._active_sim_changed.remove(callback) def on_sim_added_to_skewer(self, sim_info, send_relationship_update=True): if send_relationship_update: sim_info.relationship_tracker.send_relationship_info() sim_info.relationship_tracker.enable_selectable_sim_track_decay() sim_info.on_sim_added_to_skewer() sim_info.commodity_tracker.send_commodity_progress_update() sim_info.career_tracker.on_sim_added_to_skewer() sim_info.send_whim_bucks_update(SetWhimBucks.LOAD) sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS) if sim is not None: sim.ui_manager.refresh_ui_data() services.autonomy_service().logging_sims.add(sim) sim.family_funds.empty_sim_personal_funds(sim) sim_info.aspiration_tracker.force_send_data_update() sim_info.aspiration_tracker.initialize_aspiration() sim_info.aspiration_tracker.set_update_alarm() sim_info.career_tracker.activate_career_aspirations() def on_sim_removed_from_skewer(self, sim_info, update_relationship_tracker=True): if update_relationship_tracker: if sim_info.is_child and sim_info.is_dead: sim_info.relationship_tracker.destroy_all_relationships() else: sim_info.relationship_tracker.enable_selectable_sim_track_decay(False) sim_info.aspiration_tracker.clear_update_alarm() sim = sim_info.get_sim_instance() if sim is not None: autonomy_service = services.autonomy_service() if autonomy_service is not None: autonomy_service.logging_sims.discard(sim) def clean_and_send_remaining_relationship_info(self): for sim_info in self.selectable_sims: sim_info.relationship_tracker.clean_and_send_remaining_relationship_info() def cancel_live_drag_on_objects(self): for obj in self._live_drag_objects: obj.live_drag_component.cancel_live_dragging() self._live_drag_objects = [] def _get_stack_items_from_drag_object(self, drag_object, remove=False, is_stack=False): if drag_object.inventoryitem_component is None: return (False, None) previous_inventory = drag_object.inventoryitem_component.get_inventory() if previous_inventory is None: return (False, None) stack_id = drag_object.inventoryitem_component.get_stack_id() if remove: success = previous_inventory.try_remove_object_by_id(drag_object.id, force_remove_stack=is_stack) else: success = True stack_items = previous_inventory.get_stack_items(stack_id) return (success, stack_items) def remove_drag_object_and_get_next_item(self, drag_object): next_object_id = None (success, stack_items) = self._get_stack_items_from_drag_object(drag_object, remove=True) if success and stack_items: next_object_id = stack_items[0].id return (success, next_object_id) def get_live_drag_object_value(self, drag_object, is_stack=False): (_, stack_items) = self._get_stack_items_from_drag_object(drag_object, remove=False, is_stack=is_stack) value = 0 if is_stack and stack_items: for item in stack_items: value += item.current_value*item.stack_count() else: value = drag_object.current_value return value def start_live_drag(self, live_drag_object, start_system, is_stack): self._live_drag_start_system = start_system success = True if is_stack: inventoryitem_component = live_drag_object.inventoryitem_component stack_id = inventoryitem_component.get_stack_id() current_inventory = inventoryitem_component.get_inventory() stack_items = current_inventory.get_stack_items(stack_id) else: stack_items = [live_drag_object] for item in stack_items: live_drag_component = live_drag_object.live_drag_component live_drag_component = item.live_drag_component if live_drag_component is None: logger_live_drag.error('Live Drag Start called on an object with no Live Drag Component. Object: {}'.format(item)) self.send_live_drag_cancel(live_drag_object.id) return if item.in_use and not item.in_use_by(self) or not live_drag_component.can_live_drag: logger_live_drag.warn('Live Drag Start called on an object that is in use. Object: {}'.format(item)) self.send_live_drag_cancel(item.id) return success = live_drag_component.start_live_dragging(self, start_system) if not success: break self._live_drag_objects.append(item) if not success: self.cancel_live_drag_on_objects() self.send_live_drag_cancel(live_drag_object.id, LiveDragLocation.INVALID) self._live_drag_is_stack = is_stack if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled: gsi_handlers.live_drag_handlers.archive_live_drag('Start', 'Operation', LiveDragLocation.GAMEPLAY_SCRIPT, start_system, live_drag_object_id=live_drag_object.id) if live_drag_object.live_drag_component.active_household_has_sell_permission: sell_value = self.get_live_drag_object_value(live_drag_object, self._live_drag_is_stack) if live_drag_object.definition.get_is_deletable() else -1 else: sell_value = -1 (valid_drop_object_ids, valid_stack_id) = live_drag_component.get_valid_drop_object_ids() op = distributor.ops.LiveDragStart(live_drag_object.id, start_system, valid_drop_object_ids, valid_stack_id, sell_value) distributor_system = Distributor.instance() distributor_system.add_op_with_no_owner(op) def end_live_drag(self, source_object, target_object=None, end_system=LiveDragLocation.INVALID, location=None): live_drag_component = source_object.live_drag_component if live_drag_component is None: logger_live_drag.error('Live Drag End called on an object with no Live Drag Component. Object: {}'.format(source_object)) self.send_live_drag_cancel(source_object.id, end_system) return if source_object not in self._live_drag_objects: logger_live_drag.warn('Live Drag End called on an object not being Live Dragged. Object: {}'.format(source_object)) self.send_live_drag_cancel(source_object.id, end_system) return source_object_id = source_object.id self.cancel_live_drag_on_objects() next_object_id = None success = False if target_object is not None: live_drag_target_component = target_object.live_drag_target_component if live_drag_target_component is not None: (success, next_object_id) = live_drag_target_component.drop_live_drag_object(source_object, self._live_drag_is_stack) else: logger_live_drag.error('Live Drag Target Component missing on object: {} is now required on all drop targets.'.format(target_object)) success = False else: success = True if location is not None: source_object.set_location(location) inventory_item = source_object.inventoryitem_component if inventory_item is not None and inventory_item.is_in_inventory(): (success, next_object_id) = self.remove_drag_object_and_get_next_item(source_object) if success: if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled: gsi_handlers.live_drag_handlers.archive_live_drag('End', 'Operation', LiveDragLocation.GAMEPLAY_SCRIPT, end_system, live_drag_object_id=source_object_id, live_drag_target=target_object) if not self._live_drag_is_stack: next_object_id = None op = distributor.ops.LiveDragEnd(source_object_id, self._live_drag_start_system, end_system, next_object_id) distributor_system = Distributor.instance() distributor_system.add_op_with_no_owner(op) self._live_drag_objects = [] self._live_drag_start_system = LiveDragLocation.INVALID self._live_drag_is_stack = False else: self.send_live_drag_cancel(source_object_id, end_system) def cancel_live_drag(self, live_drag_object, end_system=LiveDragLocation.INVALID): live_drag_component = live_drag_object.live_drag_component if live_drag_component is None: logger_live_drag.warn('Live Drag Cancel called on an object with no Live Drag Component. Object: {}'.format(live_drag_object)) self.send_live_drag_cancel(live_drag_object.id) return if live_drag_component.live_drag_state == LiveDragState.NOT_LIVE_DRAGGING: logger_live_drag.warn('Live Drag Cancel called on an object not being Live Dragged. Object: {}'.format(live_drag_object)) else: self.cancel_live_drag_on_objects() self.send_live_drag_cancel(live_drag_object.id, end_system) def sell_live_drag_object(self, live_drag_object, end_system=LiveDragLocation.INVALID): live_drag_component = live_drag_object.live_drag_component if live_drag_component is None or not live_drag_object.definition.get_is_deletable(): logger_live_drag.error("Live Drag Sell called on object with no Live Drag Component or can't be deleted. Object: {}".format(live_drag_object)) self.send_live_drag_cancel(live_drag_object.id, end_system) return def sell_response(dialog): if not dialog.accepted: return value = int(self.get_live_drag_object_value(live_drag_object, self._live_drag_is_stack)) live_drag_component.cancel_live_dragging(should_reset=False) object_tags = set() if self._live_drag_is_stack: (_, stack_items) = self._get_stack_items_from_drag_object(live_drag_object, remove=True, is_stack=True) for item in stack_items: item.current_value = 0 item.set_stack_count(0) object_tags.update(item.get_tags()) item.destroy(source=item, cause='Selling stack of live drag objects.') else: object_tags.update(live_drag_object.get_tags()) if live_drag_object.is_in_inventory(): self.remove_drag_object_and_get_next_item(live_drag_object) else: live_drag_object.remove_from_client() object_tags = frozenset(object_tags) live_drag_object.current_value = 0 live_drag_object.destroy(source=live_drag_object, cause='Selling live drag object.') services.active_household().funds.add(value, Consts_pb2.TELEMETRY_OBJECT_SELL, self.active_sim, tags=object_tags) self._live_drag_objects = [] self._live_drag_start_system = LiveDragLocation.INVALID self._live_drag_is_stack = False self._live_drag_sell_dialog_active = False if self._live_drag_is_stack: dialog = LiveDragTuning.LIVE_DRAG_SELL_STACK_DIALOG(owner=live_drag_object) else: dialog = LiveDragTuning.LIVE_DRAG_SELL_DIALOG(owner=live_drag_object) dialog.show_dialog(on_response=sell_response) self._live_drag_sell_dialog_active = True def send_live_drag_cancel(self, live_drag_object_id, live_drag_end_system=LiveDragLocation.INVALID): if gsi_handlers.live_drag_handlers.live_drag_archiver.enabled: gsi_handlers.live_drag_handlers.archive_live_drag('Cancel', 'Operation', LiveDragLocation.GAMEPLAY_SCRIPT, live_drag_end_system, live_drag_object_id=live_drag_object_id) op = distributor.ops.LiveDragCancel(live_drag_object_id, self._live_drag_start_system, live_drag_end_system) distributor_system = Distributor.instance() distributor_system.add_op_with_no_owner(op) if not self._live_drag_sell_dialog_active: self._live_drag_objects = [] self._live_drag_start_system = LiveDragLocation.INVALID self._live_drag_is_stack = False def on_add(self): if self._account is not None: self._account.register_client(self) for sim_info in self._selectable_sims: self.on_sim_added_to_skewer(sim_info) distributor = Distributor.instance() distributor.add_object(self) distributor.add_client(self) self.send_selectable_sims_update() self.selectable_sims.add_watcher(self, self.send_selectable_sims_update) def on_remove(self): if self.active_sim is not None: self._set_active_sim_without_field_distribution(None) if self._account is not None: self._account.unregister_client(self) for sim_info in self._selectable_sims: self.on_sim_removed_from_skewer(sim_info, update_relationship_tracker=False) self.selectable_sims.remove_watcher(self) distributor = Distributor.instance() distributor.remove_client(self) self._selectable_sims = None self.active = False def get_objects_in_view_gen(self): for manager in services.client_object_managers(): for obj in manager.get_all(): yield obj def notify_active_sim_changed(self, old_sim, new_sim): self._active_sim_changed(old_sim, new_sim) def _get_selector_visual_type(self, sim_info): if sim_info.is_baby: return (Sims_pb2.SimPB.BABY, None) for career in sim_info.career_tracker.careers.values(): if career.currently_at_work: return (Sims_pb2.SimPB.AT_WORK, career.career_category) while career.is_late: return (Sims_pb2.SimPB.LATE_FOR_WORK, career.career_category) sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS) if sim is not None and sim.has_hidden_flags(HiddenReasonFlag.RABBIT_HOLE): return (Sims_pb2.SimPB.OTHER, None) return (Sims_pb2.SimPB.NORMAL, None) def send_selectable_sims_update(self): msg = Sims_pb2.UpdateSelectableSims() for sim_info in self._selectable_sims: with ProtocolBufferRollback(msg.sims) as new_sim: new_sim.id = sim_info.sim_id new_sim.at_work = sim_info.career_tracker.currently_at_work new_sim.is_selectable = sim_info.is_enabled_in_skewer (selector_visual_type, career_category) = self._get_selector_visual_type(sim_info) new_sim.selector_visual_type = selector_visual_type if career_category is not None: new_sim.career_category = career_category while not sim_info.is_instanced(allow_hidden_flags=ALL_HIDDEN_REASONS): new_sim.instance_info.zone_id = sim_info.zone_id new_sim.instance_info.world_id = sim_info.world_id new_sim.firstname = sim_info.first_name new_sim.lastname = sim_info.last_name zone_data_proto = services.get_persistence_service().get_zone_proto_buff(sim_info.zone_id) while zone_data_proto is not None: new_sim.instance_info.zone_name = zone_data_proto.name distributor = Distributor.instance() distributor.add_op_with_no_owner(GenericProtocolBufferOp(Operation.SELECTABLE_SIMS_UPDATE, msg)) @property def is_sim(self): return False
class BuffComponent(objects.components.Component, component_name=objects.components.types.BUFF_COMPONENT): __qualname__ = 'BuffComponent' DEFAULT_MOOD = TunableReference(services.mood_manager(), description='The default initial mood.') UPDATE_INTENSITY_BUFFER = TunableRange(description="\n A buffer that prevents a mood from becoming active unless its intensity\n is greater than the current active mood's intensity plus this amount.\n \n For example, if this tunable is 1, and the Sim is in a Flirty mood with\n intensity 2, then a different mood would become the active mood only if\n its intensity is 3+.\n \n If the predominant mood has an intensity that is less than the active\n mood's intensity, that mood will become the active mood.\n ", tunable_type=int, default=1, minimum=0) EXCLUSIVE_SET = TunableList(description='\n A list of buff groups to determine which buffs are exclusive from each\n other within the same group. A buff cannot exist in more than one exclusive group.\n \n The following rule of exclusivity for a group:\n 1. Higher weight will always be added and remove any lower weight buffs\n 2. Lower weight buff will not be added if a higher weight already exist in component\n 3. Same weight buff will always be added and remove any buff with same weight.\n \n Example: Group 1:\n Buff1 with weight of 5 \n Buff2 with weight of 1\n Buff3 with weight of 1\n Group 2:\n Buff4 with weight of 6\n \n If sim has Buff1, trying to add Buff2 or Buff3 will not be added.\n If sim has Buff2, trying to add Buff3 will remove Buff2 and add Buff3\n If sim has Buff2, trying to add Buff1 will remove Buff 2 and add Buff3\n If sim has Buff4, trying to add Buff1, Buff2, or Buff3 will be added and Buff4 will stay \n on component \n ', tunable=TunableList(tunable=TunableTuple(buff_type=TunableReference(description='\n Buff in exclusive group\n ', manager=services.get_instance_manager(sims4.resources.Types.BUFF)), weight=Tunable(description='\n weight to determine if this buff should be added and\n remove other buffs in the exclusive group or not added at all.\n \n Example: Buff1 with weight of 5 \n Buff2 with weight of 1\n Buff3 with weight of 1\n \n If sim has Buff1, trying to add Buff2 or Buff3 will not be added.\n If sim has Buff2, trying to add Buff3 will remove Buff2 and add Buff3\n if sim has Buff2, trying to add Buff1 will remove Buff 2 and add Buff3\n ', tunable_type=int, default=1)))) def __init__(self, owner): super().__init__(owner) self._active_buffs = {} self._get_next_handle_id = UniqueIdGenerator() self._success_chance_modification = 0 self._active_mood = self.DEFAULT_MOOD self._active_mood_intensity = 0 self._active_mood_buff_handle = None self.on_mood_changed = CallableList() self.on_mood_changed.append(self._publish_mood_update) self.on_mood_changed.append(self._send_mood_changed_event) self.load_in_progress = False self.on_buff_added = CallableList() self.on_buff_removed = CallableList() self.buff_update_alarms = {} if self._active_mood is None: logger.error('No default mood tuned in buff_component.py') elif self._active_mood.buffs: initial_buff_ref = self._active_mood.buffs[0] if initial_buff_ref and initial_buff_ref.buff_type: self._active_mood_buff_handle = self.add_buff(initial_buff_ref.buff_type) def __iter__(self): return self._active_buffs.values().__iter__() def __len__(self): return len(self._active_buffs) def on_sim_ready_to_simulate(self): for buff in self: buff.on_sim_ready_to_simulate() self._publish_mood_update() def on_sim_removed(self, *args, **kwargs): for buff in self: buff.on_sim_removed(*args, **kwargs) def clean_up(self): for (buff_type, buff_entry) in tuple(self._active_buffs.items()): self.remove_auto_update(buff_type) buff_entry.clean_up() self._active_buffs.clear() self.on_mood_changed.clear() self.on_buff_added.clear() self.on_buff_removed.clear() @objects.components.componentmethod def add_buff_from_op(self, buff_type, buff_reason=None): (can_add, _) = self._can_add_buff_type(buff_type) if not can_add: return False buff_commodity = buff_type.commodity if buff_commodity is not None: if not buff_type.refresh_on_add and self.has_buff(buff_type): return False tracker = self.owner.get_tracker(buff_commodity) if buff_commodity.convergence_value == buff_commodity.max_value: tracker.set_min(buff_commodity) else: tracker.set_max(buff_commodity) self.set_buff_reason(buff_type, buff_reason, use_replacement=True) else: self.add_buff(buff_type, buff_reason=buff_reason) return True @objects.components.componentmethod def add_buff(self, buff_type, buff_reason=None, update_mood=True, commodity_guid=None, replacing_buff=None, timeout_string=None, transition_into_buff_id=0, change_rate=None, immediate=False): replacement_buff_type = self._get_replacement_buff_type(buff_type) if replacement_buff_type is not None: return self.owner.add_buff(replacement_buff_type, buff_reason=buff_reason, update_mood=update_mood, commodity_guid=commodity_guid, replacing_buff=buff_type, timeout_string=timeout_string, transition_into_buff_id=transition_into_buff_id, change_rate=change_rate, immediate=immediate) (can_add, conflicting_buff_type) = self._can_add_buff_type(buff_type) if not can_add: return buff = self._active_buffs.get(buff_type) if buff is None: buff = buff_type(self.owner, commodity_guid, replacing_buff, transition_into_buff_id) self._active_buffs[buff_type] = buff buff.on_add(self.load_in_progress) self._update_chance_modifier() if update_mood: self._update_current_mood() if self.owner.household is not None: services.get_event_manager().process_event(test_events.TestEvent.BuffBeganEvent, sim_info=self.owner, sim_id=self.owner.sim_id, buff=buff_type) self.register_auto_update(self.owner, buff_type) self.on_buff_added(buff_type) handle_id = self._get_next_handle_id() buff.add_handle(handle_id, buff_reason=buff_reason) self.send_buff_update_msg(buff, True, change_rate=change_rate, immediate=immediate) if conflicting_buff_type is not None: self.remove_buff_by_type(conflicting_buff_type) return handle_id def _get_replacement_buff_type(self, buff_type): if buff_type.trait_replacement_buffs is not None: trait_tracker = self.owner.trait_tracker for (trait, replacement_buff_type) in buff_type.trait_replacement_buffs.items(): while trait_tracker.has_trait(trait): return replacement_buff_type def register_auto_update(self, sim_info_in, buff_type_in): if buff_type_in in self.buff_update_alarms: self.remove_auto_update(buff_type_in) if sim_info_in.is_selectable and buff_type_in.visible: self.buff_update_alarms[buff_type_in] = alarms.add_alarm(self, create_time_span(minutes=15), lambda _, sim_info=sim_info_in, buff_type=buff_type_in: services.get_event_manager().process_event(test_events.TestEvent.BuffUpdateEvent, sim_info=sim_info, sim_id=sim_info.sim_id, buff=buff_type), True) def remove_auto_update(self, buff_type): if buff_type in self.buff_update_alarms: alarms.cancel_alarm(self.buff_update_alarms[buff_type]) del self.buff_update_alarms[buff_type] @objects.components.componentmethod def remove_buff(self, handle_id, update_mood=True, immediate=False, on_destroy=False): for (buff_type, buff_entry) in self._active_buffs.items(): while handle_id in buff_entry.handle_ids: should_remove = buff_entry.remove_handle(handle_id) if should_remove: del self._active_buffs[buff_type] buff_entry.on_remove(not self.load_in_progress and not on_destroy) if not on_destroy: if update_mood: self._update_current_mood() self._update_chance_modifier() self.send_buff_update_msg(buff_entry, False, immediate=immediate) services.get_event_manager().process_event(test_events.TestEvent.BuffEndedEvent, sim_info=self.owner, sim_id=self.owner.sim_id, buff=buff_type) if buff_type in self.buff_update_alarms: self.remove_auto_update(buff_type) self.on_buff_removed(buff_type) break @objects.components.componentmethod def get_buff_type(self, handle_id): for (buff_type, buff_entry) in self._active_buffs.items(): while handle_id in buff_entry.handle_ids: return buff_type @objects.components.componentmethod def has_buff(self, buff_type): return buff_type in self._active_buffs @objects.components.componentmethod def get_active_buff_types(self): return self._active_buffs.keys() @objects.components.componentmethod def get_buff_reason(self, handle_id): for buff_entry in self._active_buffs.values(): while handle_id in buff_entry.handle_ids: return buff_entry.buff_reason @objects.components.componentmethod def debug_add_buff_by_type(self, buff_type): (can_add, conflicting_buff_type) = self._can_add_buff_type(buff_type) if not can_add: return False if buff_type.commodity is not None: tracker = self.owner.get_tracker(buff_type.commodity) state_index = buff_type.commodity.get_state_index_matches_buff_type(buff_type) if state_index is not None: index = state_index + 1 if index < len(buff_type.commodity.commodity_states): commodity_to_value = buff_type.commodity.commodity_states[index].value - 1 else: commodity_to_value = buff_type.commodity.max_value tracker.set_value(buff_type.commodity, commodity_to_value) else: logger.error('commodity ({}) has no states with buff ({}), Buff will not be added.', buff_type.commodity, buff_type) return False else: self.add_buff(buff_type) if conflicting_buff_type is not None: self.remove_buff_by_type(conflicting_buff_type) return True @objects.components.componentmethod def remove_buff_by_type(self, buff_type, on_destroy=False): buff_entry = self._active_buffs.get(buff_type) self.remove_buff_entry(buff_entry, on_destroy=on_destroy) @objects.components.componentmethod def remove_buff_entry(self, buff_entry, on_destroy=False): if buff_entry is not None: if buff_entry.commodity is not None: tracker = self.owner.get_tracker(buff_entry.commodity) commodity_inst = tracker.get_statistic(buff_entry.commodity) if commodity_inst is not None and commodity_inst.core: if not on_destroy: logger.callstack('Attempting to explicitly remove the buff {}, which is given by a core commodity. This would result in the removal of a core commodity and will be ignored.', buff_entry, owner='tastle', level=sims4.log.LEVEL_ERROR) return tracker.remove_statistic(buff_entry.commodity, on_destroy=on_destroy) elif buff_entry.buff_type in self._active_buffs: buff_entry.on_remove(on_destroy) del self._active_buffs[buff_entry.buff_type] if not on_destroy: self._update_chance_modifier() self._update_current_mood() self.send_buff_update_msg(buff_entry, False) services.get_event_manager().process_event(test_events.TestEvent.BuffEndedEvent, sim_info=self.owner, buff=type(buff_entry), sim_id=self.owner.id) @objects.components.componentmethod def set_buff_reason(self, buff_type, buff_reason, use_replacement=False): if use_replacement: replacement_buff_type = self._get_replacement_buff_type(buff_type) if replacement_buff_type is not None: buff_type = replacement_buff_type buff_entry = self._active_buffs.get(buff_type) if buff_entry is not None and buff_reason is not None: buff_entry.buff_reason = buff_reason self.send_buff_update_msg(buff_entry, True) @objects.components.componentmethod def buff_commodity_changed(self, handle_id, change_rate=None): for (_, buff_entry) in self._active_buffs.items(): while handle_id in buff_entry.handle_ids: if buff_entry.show_timeout: self.send_buff_update_msg(buff_entry, True, change_rate=change_rate) break @objects.components.componentmethod def get_success_chance_modifier(self): return self._success_chance_modification @objects.components.componentmethod def get_actor_scoring_modifier(self, affordance): total = 0 for buff_entry in self._active_buffs.values(): total += buff_entry.effect_modification.get_affordance_scoring_modifier(affordance) return total @objects.components.componentmethod def get_actor_success_modifier(self, affordance): total = 0 for buff_entry in self._active_buffs.values(): total += buff_entry.effect_modification.get_affordance_success_modifier(affordance) return total @objects.components.componentmethod def get_mood(self): return self._active_mood @objects.components.componentmethod def get_mood_animation_param_name(self): param_name = self._active_mood.asm_param_name if param_name is not None: return param_name (mood, _, _) = self._get_largest_mood(predicate=lambda mood: return True if mood.asm_param_name else False) return mood.asm_param_name @objects.components.componentmethod def get_mood_intensity(self): return self._active_mood_intensity @objects.components.componentmethod def get_effective_skill_level(self, skill): if skill.stat_type == skill: skill = self.owner.get_stat_instance(skill) if skill is None: return 0 modifier = 0 for buff_entry in self._active_buffs.values(): modifier += buff_entry.effect_modification.get_effective_skill_modifier(skill) return skill.get_user_value() + modifier @objects.components.componentmethod def effective_skill_modified_buff_gen(self, skill): if skill.stat_type == skill: skill = self.owner.get_stat_instance(skill) for buff_entry in self._active_buffs.values(): modifier = buff_entry.effect_modification.get_effective_skill_modifier(skill) while modifier != 0: yield (buff_entry, modifier) @objects.components.componentmethod def is_appropriate(self, tags): final_appropriateness = Appropriateness.DONT_CARE for buff in self._active_buffs: appropriateness = buff.get_appropriateness(tags) while appropriateness > final_appropriateness: final_appropriateness = appropriateness if final_appropriateness == Appropriateness.NOT_ALLOWED: return False return True def get_additional_create_ops_gen(self): yield GenericProtocolBufferOp(Operation.SIM_MOOD_UPDATE, self._create_mood_update_msg()) for buff in self: while buff.visible: yield GenericProtocolBufferOp(Operation.SIM_BUFF_UPDATE, self._create_buff_update_msg(buff, True)) def _publish_mood_update(self): if self.owner.valid_for_distribution and self.owner.visible_to_client == True: Distributor.instance().add_op(self.owner, GenericProtocolBufferOp(Operation.SIM_MOOD_UPDATE, self._create_mood_update_msg())) def _send_mood_changed_event(self): if not self.owner.is_npc: self.owner.whim_tracker.refresh_emotion_whim() services.get_event_manager().process_event(test_events.TestEvent.MoodChange, sim_info=self.owner) def _create_mood_update_msg(self): mood_msg = Commodities_pb2.MoodUpdate() mood_msg.sim_id = self.owner.id mood_msg.mood_key = self._active_mood.guid64 mood_msg.mood_intensity = self._active_mood_intensity return mood_msg def _create_buff_update_msg(self, buff, equipped, change_rate=None): buff_msg = Sims_pb2.BuffUpdate() buff_msg.buff_id = buff.guid64 buff_msg.sim_id = self.owner.id buff_msg.equipped = equipped if buff.buff_reason is not None: buff_msg.reason = buff.buff_reason if equipped and buff.show_timeout: (timeout, rate_multiplier) = buff.get_timeout_time() buff_msg.timeout = timeout buff_msg.rate_multiplier = rate_multiplier if change_rate is not None: if change_rate == 0: progress_arrow = Sims_pb2.BUFF_PROGRESS_NONE elif change_rate > 0: progress_arrow = Sims_pb2.BUFF_PROGRESS_UP if not buff.flip_arrow_for_progress_update else Sims_pb2.BUFF_PROGRESS_DOWN else: progress_arrow = Sims_pb2.BUFF_PROGRESS_DOWN if not buff.flip_arrow_for_progress_update else Sims_pb2.BUFF_PROGRESS_UP buff_msg.buff_progress = progress_arrow buff_msg.is_mood_buff = buff.is_mood_buff buff_msg.commodity_guid = buff.commodity_guid or 0 if buff.mood_override is not None: buff_msg.mood_type_override = buff.mood_override.guid64 buff_msg.transition_into_buff_id = buff.transition_into_buff_id return buff_msg def send_buff_update_msg(self, buff, equipped, change_rate=None, immediate=False): if not buff.visible: return if self.owner.valid_for_distribution and self.owner.is_sim and self.owner.is_selectable: buff_msg = self._create_buff_update_msg(buff, equipped, change_rate=change_rate) if gsi_handlers.buff_handlers.sim_buff_log_archiver.enabled: gsi_handlers.buff_handlers.archive_buff_message(buff_msg, equipped, change_rate) Distributor.instance().add_op(self.owner, GenericProtocolBufferOp(Operation.SIM_BUFF_UPDATE, buff_msg)) def _can_add_buff_type(self, buff_type): if not buff_type.can_add(self.owner): return (False, None) mood = buff_type.mood_type if mood is not None and mood.excluding_traits is not None and self.owner.trait_tracker.has_any_trait(mood.excluding_traits): return (False, None) if buff_type.exclusive_index is None: return (True, None) for conflicting_buff_type in self._active_buffs: while conflicting_buff_type.exclusive_index == buff_type.exclusive_index: if buff_type.exclusive_weight < conflicting_buff_type.exclusive_weight: return (False, None) return (True, conflicting_buff_type) return (True, None) def _update_chance_modifier(self): positive_success_buff_delta = 0 negative_success_buff_delta = 1 for buff_entry in self._active_buffs.values(): if buff_entry.success_modifier > 0: positive_success_buff_delta += buff_entry.get_success_modifier else: negative_success_buff_delta *= 1 + buff_entry.get_success_modifier self._success_chance_modification = positive_success_buff_delta - (1 - negative_success_buff_delta) def _get_largest_mood(self, predicate=None, buffs_to_ignore=()): weights = {} polarity_to_changeable_buffs = collections.defaultdict(list) polarity_to_largest_mood_and_weight = {} for buff_entry in self._active_buffs.values(): current_mood = buff_entry.mood_type current_weight = buff_entry.mood_weight while not current_mood is None: if current_weight == 0: pass if not (predicate is not None and predicate(current_mood)): pass if buff_entry in buffs_to_ignore: pass current_polarity = current_mood.buff_polarity if buff_entry.is_changeable: polarity_to_changeable_buffs[current_polarity].append(buff_entry) total_current_weight = weights.get(current_mood, 0) total_current_weight += current_weight weights[current_mood] = total_current_weight (largest_mood, largest_weight) = polarity_to_largest_mood_and_weight.get(current_polarity, (None, None)) if largest_mood is None: polarity_to_largest_mood_and_weight[current_polarity] = (current_mood, total_current_weight) else: while total_current_weight > largest_weight: polarity_to_largest_mood_and_weight[current_polarity] = (current_mood, total_current_weight) all_changeable_buffs = [] for (buff_polarity, changeable_buffs) in polarity_to_changeable_buffs.items(): (largest_mood, largest_weight) = polarity_to_largest_mood_and_weight.get(buff_polarity, (None, None)) if largest_mood is not None: for buff_entry in changeable_buffs: if buff_entry.mood_override is not largest_mood: all_changeable_buffs.append((buff_entry, largest_mood)) largest_weight += buff_entry.mood_weight polarity_to_largest_mood_and_weight[buff_polarity] = (largest_mood, largest_weight) else: weights = {} largest_weight = 0 for buff_entry in changeable_buffs: if buff_entry.mood_override is not None: all_changeable_buffs.append((buff_entry, None)) current_mood = buff_entry.mood_type current_weight = buff_entry.mood_weight total_current_weight = weights.get(current_mood, 0) total_current_weight += current_weight weights[current_mood] = total_current_weight while total_current_weight > largest_weight: largest_weight = total_current_weight largest_mood = current_mood while largest_mood is not None and largest_weight != 0: polarity_to_largest_mood_and_weight[buff_polarity] = (largest_mood, largest_weight) largest_weight = 0 largest_mood = self.DEFAULT_MOOD active_mood = self._active_mood if polarity_to_largest_mood_and_weight: (mood, weight) = max(polarity_to_largest_mood_and_weight.values(), key=operator.itemgetter(1)) if weight > largest_weight or weight == largest_weight and mood is active_mood: largest_weight = weight largest_mood = mood return (largest_mood, largest_weight, all_changeable_buffs) def _update_current_mood(self): (largest_mood, largest_weight, changeable_buffs) = self._get_largest_mood() if largest_mood is not None: intensity = self._get_intensity_from_mood(largest_mood, largest_weight) if self._should_update_mood(largest_mood, intensity, changeable_buffs): if self._active_mood_buff_handle is not None: active_mood_buff_handle = self._active_mood_buff_handle self.remove_buff(active_mood_buff_handle, update_mood=False) if active_mood_buff_handle == self._active_mood_buff_handle: self._active_mood_buff_handle = None else: return self._active_mood = largest_mood self._active_mood_intensity = intensity if len(largest_mood.buffs) >= intensity: tuned_buff = largest_mood.buffs[intensity] if tuned_buff is not None and tuned_buff.buff_type is not None: self._active_mood_buff_handle = self.add_buff(tuned_buff.buff_type, update_mood=False) if gsi_handlers.buff_handlers.sim_mood_log_archiver.enabled and self.owner.valid_for_distribution and self.owner.visible_to_client == True: gsi_handlers.buff_handlers.archive_mood_message(self.owner.id, self._active_mood, self._active_mood_intensity, self._active_buffs, changeable_buffs) caches.clear_all_caches() self.on_mood_changed() for (changeable_buff, mood_override) in changeable_buffs: changeable_buff.mood_override = mood_override self.send_buff_update_msg(changeable_buff, True) def _get_intensity_from_mood(self, mood, weight): intensity = 0 for threshold in mood.intensity_thresholds: if weight >= threshold: intensity += 1 else: break return intensity def _should_update_mood(self, mood, intensity, changeable_buffs): active_mood = self._active_mood active_mood_intensity = self._active_mood_intensity if mood is active_mood: return intensity != active_mood_intensity total_weight = sum(buff_entry.mood_weight for buff_entry in self._active_buffs.values() if buff_entry.mood_type is active_mood) active_mood_intensity = self._get_intensity_from_mood(active_mood, total_weight) if changeable_buffs and not self._active_mood.is_changeable: buffs_to_ignore = [changeable_buff for (changeable_buff, _) in changeable_buffs] (largest_mood, largest_weight, _) = self._get_largest_mood(buffs_to_ignore=buffs_to_ignore) new_intensity = self._get_intensity_from_mood(largest_mood, largest_weight) if self._should_update_mood(largest_mood, new_intensity, None): active_mood = largest_mood active_mood_intensity = new_intensity if active_mood.is_changeable and mood.buff_polarity == active_mood.buff_polarity: return True if not intensity or intensity < active_mood_intensity: return True if intensity >= active_mood_intensity + self.UPDATE_INTENSITY_BUFFER: return True if mood is self.DEFAULT_MOOD or active_mood is self.DEFAULT_MOOD: return True return False
class InstanceManager: __qualname__ = 'InstanceManager' def __init__(self, path, type_enum, use_guid_for_ref=False): self._tuned_classes = {} self._callback_helper = {} self._verify_tunable_callback_helper = {} self._class_templates = [] self.PATH = path self.TYPE = type_enum self._load_all_complete = False self._load_all_complete_callbacks = CallableList() self._use_guid_for_ref = use_guid_for_ref def add_on_load_complete(self, callback): if not self._load_all_complete: self._load_all_complete_callbacks.append(callback) else: callback(self) @property def all_instances_loaded(self): return self._load_all_complete def __str__(self): return 'InstanceManager_{}'.format(self.TYPE.name.lower()) def get_changed_files(self): return sims4.core_services.file_change_manager().consume_set(self.TYPE) def reload_by_key(self, key): if not __debug__: raise RuntimeError('[manus] Reloading tuning is not supported for optimized python builds.') cls = self._tuned_classes.get(key) if cls is None: self.get(key) return try: sims4.tuning.serialization.restore_class_instance(cls) (tuning_callbacks, verify_callbacks) = sims4.tuning.serialization.load_from_xml(key, self.TYPE, cls, from_reload=True) if tuning_callbacks: for helper in tuning_callbacks: helper.template.invoke_callback(cls, helper.name, helper.source, helper.value) if verify_callbacks: for helper in verify_callbacks: helper.template.invoke_verify_tunable_callback(cls, helper.name, helper.source, helper.value) if hasattr(cls, TUNING_LOADED_CALLBACK): cls._tuning_loaded_callback() while hasattr(cls, VERIFY_TUNING_CALLBACK): cls._verify_tuning_callback() except: name = sims4.resources.get_name_from_key(key) logger.exception('Failed to reload tuning for {} (key:{}).', name, key, owner='manus') return return reload_dependencies_dict[key] @property def types(self): if not self.all_instances_loaded: logger.warn("Attempt to access instance types on '{}' before all instances are loaded", self) return self._tuned_classes def get_ordered_types(self, only_subclasses_of=object): def key(cls): result = tuple(x.__name__.lower() for x in reversed(cls.__mro__[:-1])) return (len(result), result) result = [c for c in self.types.values() if issubclass(c, only_subclasses_of)] result = sorted(result, key=key) return result @property def use_guid_for_ref(self): return self._use_guid_for_ref def register_class_template(self, template): self._class_templates.append(template) def register_tuned_class(self, instance, resource_key): if resource_key in self._tuned_classes: logger.info('Attempting to re-register class instance {} (Key:{}) with {}.', self._tuned_classes[resource_key], resource_key, self, owner='manus') return self._tuned_classes[resource_key] = instance instance.resource_key = resource_key if self.use_guid_for_ref: instance.guid64 = resource_key.instance def on_start(self): if sims4.core_services.SUPPORT_RELOADING_RESOURCES: file_change_manager = sims4.core_services.file_change_manager() if file_change_manager is not None: file_change_manager.create_set(self.TYPE, self.TYPE) def on_stop(self): if sims4.core_services.SUPPORT_RELOADING_RESOURCES: file_change_manager = sims4.core_services.file_change_manager() if file_change_manager is not None: file_change_manager.remove_set(self.TYPE) def create_class_instances(self): mtg = get_manager() res_id_list = mtg.get_all_res_ids(self.TYPE) logger.info('Creating {:4} tuning class instances managed by {}.', len(res_id_list), self, owner='manus') for (group_id, instance_id) in res_id_list: res_key = sims4.resources.Key(self.TYPE, instance_id, group_id) self._create_class_instance(res_key) def _create_class_instance(self, resource_key): cls = None try: registered_resource_key = sims4.resources.Key(self.TYPE, resource_key.instance) cls = sims4.tuning.serialization.create_class_instance(resource_key, self.TYPE) while cls is not None: self.register_tuned_class(cls, registered_resource_key) except Exception: if registered_resource_key in self._tuned_classes: del self._tuned_classes[registered_resource_key] logger.exception('An error occurred while attempting to create tuning instance: {}. Resource Key: {}.', cls, resource_key, owner='manus') def load_data_into_class_instances(self): logger.info('Loading {:4} tuning class instances managed by {}.', len(self._tuned_classes), self, owner='manus') for (key, cls) in self._tuned_classes.items(): try: (tuning_callback_helpers, verify_tunable_callback_helpers) = sims4.tuning.serialization.load_from_xml(key, self.TYPE, cls) while tuning_callback_helpers: self._callback_helper[cls] = tuning_callback_helpers except Exception: logger.exception('Exception while finalizing tuning for {}.', cls, owner='manus') def invoke_registered_callbacks_gen(self): logger.info('Invoking callbacks for {:4} tuning class instances managed by {}.', len(self._tuned_classes), self, owner='manus') invoke_verifications = False for cls in self._tuned_classes.values(): self._invoke_tunable_callbacks(cls) if invoke_verifications: self._invoke_verify_tunable_callbacks(cls) try: if hasattr(cls, TUNING_LOADED_CALLBACK): cls._tuning_loaded_callback() while invoke_verifications and hasattr(cls, VERIFY_TUNING_CALLBACK): cls._verify_tuning_callback() except Exception: logger.exception('Exception in {}.{}.', cls, TUNING_LOADED_CALLBACK, owner='manus') yield False self._callback_helper = None self._verify_tunable_callback_helper = None self._load_all_complete = True self._load_all_complete_callbacks(self) def _invoke_tunable_callbacks(self, cls): tuning_callbacks = self._callback_helper.get(cls) if tuning_callbacks is None: return for helper in tuning_callbacks: try: helper.template.invoke_callback(cls, helper.name, helper.source, helper.value) except Exception: logger.exception('Exception in a tunable callback for variable {} in instance class {}.', helper.name, cls, owner='manus') def _invoke_verify_tunable_callbacks(self, cls): tuning_callbacks = self._verify_tunable_callback_helper.get(cls) if tuning_callbacks is None: return for helper in tuning_callbacks: try: helper.template.invoke_verify_tunable_callback(cls, helper.name, helper.source, helper.value) except Exception: logger.exception('Exception in a verify tunable callback for variable {} in instance class {}.', helper.name, cls, owner='manus') def get(self, name_or_id_or_key, pack_safe=False): key = sims4.resources.get_resource_key(name_or_id_or_key, self.TYPE) cls = self._tuned_classes.get(key) if cls is None: if not pack_safe: return raise UnavailablePackSafeResourceError return cls def _instantiate(self, target_type): return target_type() def export_descriptions(self, export_path, filter_fn=None): export_path = os.path.join(os.path.dirname(export_path), os.path.basename(self.PATH)) export_path = os.path.join(export_path, 'Descriptions') to_export = {} for cls in sorted(self._class_templates, key=lambda cls: cls.__name__): cls_name = cls.__name__ while filter_fn is None or filter_fn(cls): to_export[cls_name] = cls error_count = 0 logger.info('TDESCs for {}: {}', self.TYPE.name, ', '.join([cls.__name__ for cls in to_export.values()])) for cls in to_export.values(): result = sims4.tuning.serialization.export_class(cls, export_path, self.TYPE) while not result: error_count += 1 return error_count def get_debug_statistics(self): result = [] result.append(('TYPE', str(self.TYPE))) result.append(('PATH', str(self.PATH))) result.append(('UseGuidForReference', str(self._use_guid_for_ref))) result.append(('#TuningFiles', str(len(self._tuned_classes)))) result.append(('#ClassTemplates', str(len(self._class_templates)))) result.append(('LoadAllComplete', str(self._load_all_complete))) result.append(('#LoadAllCompelteCallbacks', str(len(self._load_all_complete_callbacks)))) return result
class AwayActionTracker: __qualname__ = 'AwayActionTracker' def __init__(self, sim_info): self._sim_info = sim_info self._current_away_action = None self._on_away_action_started = CallableList() self._on_away_action_ended = CallableList() self.add_on_away_action_started_callback(self._resend_away_action) self.add_on_away_action_ended_callback(self._resend_away_action) @property def sim_info(self): return self._sim_info @property def current_away_action(self): return self._current_away_action def _resend_away_action(self, _): self.sim_info.resend_current_away_action() def is_sim_info_valid_to_run_away_actions(self): if not self._sim_info.is_selectable: return False if self._sim_info.is_dead: return False if self._sim_info.is_baby: return False return True def _run_current_away_action(self): self._current_away_action.run(self._away_action_exit_condition_callback) self._on_away_action_started(self._current_away_action) def _find_away_action_from_load(self): away_actions_manager = services.get_instance_manager(sims4.resources.Types.AWAY_ACTION) for away_action_cls in away_actions_manager.types.values(): while away_action_cls.should_run_on_load(self.sim_info): return away_action_cls def start(self, on_travel_away=False): if not self.is_sim_info_valid_to_run_away_actions(): logger.error('Attempting to start away action tracker on invalid sim info {}.', self._sim_info, owner='jjacobson') return if self._sim_info.is_instanced(allow_hidden_flags=ALL_HIDDEN_REASONS) and not on_travel_away: if self._current_away_action is not None: if not self._current_away_action.available_when_instanced: self._current_away_action = None return return else: return if self._current_away_action is not None: self._run_current_away_action() return away_action_cls = self._find_away_action_from_load() if away_action_cls is not None: self.create_and_apply_away_action(away_action_cls) return self.reset_to_default_away_action(on_travel_away=on_travel_away) def stop(self): if self._current_away_action is not None: if self._current_away_action.is_running: self._current_away_action.stop() self._on_away_action_ended(self._current_away_action) self._current_away_action = None def clean_up(self): self.remove_on_away_action_started_callback(self._resend_away_action) self.remove_on_away_action_ended_callback(self._resend_away_action) self.stop() def refresh(self, on_travel_away=False): if not self.is_sim_info_valid_to_run_away_actions(): return current_zone = services.current_zone() if not current_zone.is_zone_running and not current_zone.are_sims_hitting_their_marks: return if self._sim_info.zone_id == services.current_zone_id(): self.stop() self._current_away_action = None else: self.start(on_travel_away=on_travel_away) def create_and_apply_away_action(self, away_action_cls, target=None): if not self.is_sim_info_valid_to_run_away_actions(): logger.warn('Attempting to apply away action on invalid sim info {}.', self._sim_info, owner='jjacobson') return self.stop() self._current_away_action = away_action_cls(self, target=target) self._run_current_away_action() def _away_action_exit_condition_callback(self, _): self.reset_to_default_away_action() def reset_to_default_away_action(self, on_travel_away=False): default_away_action = self.sim_info.get_default_away_action(on_travel_away=on_travel_away) if default_away_action is None: self.stop() return self.create_and_apply_away_action(default_away_action) def save_away_action_info_to_proto(self, away_action_tracker_proto): if self._current_away_action is not None: away_action_tracker_proto.away_action.away_action_id = self._current_away_action.guid64 target = self._current_away_action.target if target is not None: away_action_tracker_proto.away_action.target_sim_id = target.id def load_away_action_info_from_proto(self, away_action_tracker_proto): if away_action_tracker_proto.HasField('away_action'): away_action_cls = services.get_instance_manager(sims4.resources.Types.AWAY_ACTION).get(away_action_tracker_proto.away_action.away_action_id) if away_action_tracker_proto.away_action.HasField('target_sim_id'): target = services.sim_info_manager().get(away_action_tracker_proto.away_action.target_sim_id) else: target = None self._current_away_action = away_action_cls(self, target=target) def add_on_away_action_started_callback(self, callback): self._on_away_action_started.append(callback) def remove_on_away_action_started_callback(self, callback): self._on_away_action_started.remove(callback) def add_on_away_action_ended_callback(self, callback): self._on_away_action_ended.append(callback) def remove_on_away_action_ended_callback(self, callback): self._on_away_action_ended.remove(callback)
class BroadcasterService(Service): __qualname__ = 'BroadcasterService' INTERVAL = TunableRealSecond(description='\n The time between broadcaster pulses. A lower number will impact\n performance.\n ', default=5) DEFAULT_QUADTREE_RADIUS = 0.1 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._alarm_handle = None self._processing_task = None self._on_update_callbacks = CallableList() self._pending_broadcasters = [] self._active_broadcasters = [] self._cluster_requests = {} self._object_cache = None self._pending_update = False self._quadtrees = defaultdict(sims4.geometry.QuadTree) def start(self): self._alarm_handle = add_alarm_real_time(self, interval_in_real_seconds(self.INTERVAL), self._on_update, repeating=True, use_sleep_time=False) object_manager = services.object_manager() object_manager.register_callback(CallbackTypes.ON_OBJECT_LOCATION_CHANGED, self._update_object_cache) object_manager.register_callback(CallbackTypes.ON_OBJECT_ADD, self._update_object_cache) services.current_zone().wall_contour_update_callbacks.append(self._update_object_cache) def stop(self): if self._alarm_handle is not None: cancel_alarm(self._alarm_handle) self._alarm_handle = None if self._processing_task is not None: self._processing_task.stop() self._processing_task = None object_manager = services.object_manager() object_manager.unregister_callback(CallbackTypes.ON_OBJECT_LOCATION_CHANGED, self._update_object_cache) object_manager.unregister_callback(CallbackTypes.ON_OBJECT_ADD, self._update_object_cache) services.current_zone().wall_contour_update_callbacks.remove(self._update_object_cache) def add_broadcaster(self, broadcaster): if broadcaster not in self._pending_broadcasters: self._pending_broadcasters.append(broadcaster) self._on_update_callbacks() def remove_broadcaster(self, broadcaster): if broadcaster in self._pending_broadcasters: self._pending_broadcasters.remove(broadcaster) if broadcaster in self._active_broadcasters: self._remove_from_cluster_request(broadcaster) self._remove_broadcaster_from_quadtree(broadcaster) self._active_broadcasters.remove(broadcaster) broadcaster.on_removed() self._on_update_callbacks() def _activate_pending_broadcasters(self): for broadcaster in self._pending_broadcasters: self._active_broadcasters.append(broadcaster) self.update_cluster_request(broadcaster) self._update_object_cache() self._pending_broadcasters.clear() def _add_broadcaster_to_quadtree(self, broadcaster): self._remove_broadcaster_from_quadtree(broadcaster) broadcaster_quadtree = self._quadtrees[broadcaster.routing_surface.secondary_id] broadcaster_bounds = sims4.geometry.QtCircle(sims4.math.Vector2(broadcaster.position.x, broadcaster.position.z), self.DEFAULT_QUADTREE_RADIUS) broadcaster_quadtree.insert(broadcaster, broadcaster_bounds) return broadcaster_quadtree def _remove_broadcaster_from_quadtree(self, broadcaster): broadcaster_quadtree = broadcaster.quadtree if broadcaster_quadtree is not None: broadcaster_quadtree.remove(broadcaster) def update_cluster_request(self, broadcaster): if broadcaster not in self._active_broadcasters: return clustering_request = broadcaster.get_clustering() if clustering_request is None: return self._remove_from_cluster_request(broadcaster) cluster_request_key = (type(broadcaster), broadcaster.routing_surface.secondary_id) if cluster_request_key in self._cluster_requests: cluster_request = self._cluster_requests[cluster_request_key] cluster_request.set_object_dirty(broadcaster) else: cluster_quadtree = self._quadtrees[broadcaster.routing_surface.secondary_id] cluster_request = clustering_request(lambda : self._get_broadcasters_for_cluster_request_gen(*cluster_request_key), quadtree=cluster_quadtree) self._cluster_requests[cluster_request_key] = cluster_request quadtree = self._add_broadcaster_to_quadtree(broadcaster) broadcaster.on_added_to_quadtree_and_cluster_request(quadtree, cluster_request) def _remove_from_cluster_request(self, broadcaster): cluster_request = broadcaster.cluster_request if cluster_request is not None: cluster_request.set_object_dirty(broadcaster) def _update_object_cache(self, obj=None): if obj is None: self._object_cache = None return if self._object_cache is not None: self._object_cache.add(obj) def _is_valid_broadcaster(self, broadcaster): broadcasting_object = broadcaster.broadcasting_object if broadcasting_object is None: return False if broadcasting_object.is_in_inventory(): return False if broadcasting_object.parent is not None and broadcasting_object.parent.is_sim: return False return True def _get_broadcasters_for_cluster_request_gen(self, broadcaster_type, broadcaster_level): for broadcaster in self._active_broadcasters: while broadcaster.guid == broadcaster_type.guid: if broadcaster.should_cluster() and broadcaster.routing_surface.secondary_id == broadcaster_level: yield broadcaster def get_broadcasters_gen(self, inspect_only=False): for (cluster_request_key, cluster_request) in self._cluster_requests.items(): is_cluster_dirty = cluster_request.is_dirty() if is_cluster_dirty: for broadcaster in self._get_broadcasters_for_cluster_request_gen(*cluster_request_key): broadcaster.regenerate_constraint() while not is_cluster_dirty or not inspect_only: while True: for cluster in cluster_request.get_clusters_gen(): broadcaster_iter = cluster.objects_gen() master_broadcaster = next(broadcaster_iter) master_broadcaster.set_linked_broadcasters(list(broadcaster_iter)) yield master_broadcaster for broadcaster in self._active_broadcasters: while not broadcaster.should_cluster(): if self._is_valid_broadcaster(broadcaster): yield broadcaster def get_pending_broadcasters_gen(self): yield self._pending_broadcasters def _get_all_objects_gen(self): if any(broadcaster.allow_objects for broadcaster in self._active_broadcasters): if self._object_cache is None: self._object_cache = WeakSet(services.object_manager().valid_objects()) yield list(self._object_cache) else: self._object_cache = None yield services.sim_info_manager().instanced_sims_gen() def register_callback(self, callback): if callback not in self._on_update_callbacks: self._on_update_callbacks.append(callback) def unregister_callback(self, callback): if callback in self._on_update_callbacks: self._on_update_callbacks.remove(callback) def _on_update(self, _): self._pending_update = True def update(self): if self._pending_update: self._pending_update = False self._update() def _update(self): try: self._activate_pending_broadcasters() current_broadcasters = set(self.get_broadcasters_gen()) for obj in self._get_all_objects_gen(): is_affected = False for broadcaster in current_broadcasters: while broadcaster.can_affect(obj): constraint = broadcaster.get_constraint() if not constraint.valid: pass if constraint.geometry is None or constraint.geometry.contains_point(obj.position) and constraint.routing_surface == obj.routing_surface: broadcaster.apply_broadcaster_effect(obj) is_affected = True while not is_affected: if self._object_cache is not None: self._object_cache.remove(obj) for broadcaster in current_broadcasters: broadcaster.on_processed() finally: self._on_update_callbacks()
class ObjectManager(DistributableObjectManager): __qualname__ = 'ObjectManager' FIREMETER_DISPOSABLE_OBJECT_CAP = Tunable(int, 5, description='Number of disposable objects a lot can have at any given moment.') BED_TAGS = TunableTuple(description='\n Tags to check on an object to determine what type of bed an object is.\n ', beds=TunableSet(description='\n Tags that consider an object as a bed other than double beds.\n ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), double_beds=TunableSet(description='\n Tags that consider an object as a double bed\n ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), kid_beds=TunableSet(description='\n Tags that consider an object as a kid bed\n ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER)), other_sleeping_spots=TunableSet(description='\n Tags that considered sleeping spots.\n ', tunable=TunableEnumWithFilter(tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=BED_PREFIX_FILTER))) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._crafting_cache = CraftingObjectCache() self._sim_spawn_conditions = collections.defaultdict(set) self._client_connect_callbacks = CallableList() self._portal_cache = WeakSet() self._portal_added_callbacks = CallableList() self._portal_removed_callbacks = CallableList() self._front_door_candidates_changed_callback = CallableList() self._all_bed_tags = self.BED_TAGS.beds | self.BED_TAGS.double_beds | self.BED_TAGS.kid_beds | self.BED_TAGS.other_sleeping_spots @property def crafting_cache(self): return self._crafting_cache def portal_cache_gen(self): yield self._portal_cache def on_client_connect(self, client): all_objects = list(self._objects.values()) for game_object in all_objects: game_object.on_client_connect(client) def move_to_inventory(self, obj, inventory_manager): del self._objects[obj.id] obj.manager = inventory_manager inventory_manager._objects[obj.id] = obj if hasattr(obj, 'on_added_to_inventory'): obj.on_added_to_inventory() def add(self, obj, *args, **kwargs): super().add(obj, *args, **kwargs) if obj.objectage_component is None: current_zone = services.current_zone() current_zone.increment_object_count(obj) current_zone.household_manager.increment_household_object_count(obj.get_household_owner_id()) def remove(self, obj, *args, **kwargs): super().remove(obj, *args, **kwargs) if obj.objectage_component is None: current_zone = services.current_zone() current_zone.decrement_object_count(obj) current_zone.household_manager.decrement_household_object_count(obj.get_household_owner_id()) def _should_save_object_on_lot(self, obj): parent = obj.parent if parent is not None and parent.is_sim: if obj.can_go_in_inventory_type(InventoryType.SIM): return False return True def pre_save(self): all_objects = list(self._objects.values()) lot = services.current_zone().lot for (_, inventory) in lot.get_all_object_inventories_gen(): for game_object in inventory: all_objects.append(game_object) for game_object in all_objects: game_object.update_all_commodities() def save(self, object_list=None, zone_data=None, open_street_data=None, **kwargs): if object_list is None: return open_street_objects = file_serialization.ObjectList() total_beds = 0 double_bed_exist = False kid_bed_exist = False alternative_sleeping_spots = 0 for game_object in self._objects.values(): while self._should_save_object_on_lot(game_object): if game_object.persistence_group == objects.persistence_groups.PersistenceGroups.OBJECT: save_result = game_object.save_object(object_list.objects, ItemLocation.ON_LOT, 0) else: if game_object.item_location == ItemLocation.ON_LOT or game_object.item_location == ItemLocation.INVALID_LOCATION: item_location = ItemLocation.FROM_OPEN_STREET else: item_location = game_object.item_location save_result = game_object.save_object(open_street_objects.objects, item_location, 0) if not save_result: pass if zone_data is None: pass def_build_buy_tags = game_object.definition.build_buy_tags if not def_build_buy_tags & self._all_bed_tags: pass if def_build_buy_tags & self.BED_TAGS.double_beds: double_bed_exist = True total_beds += 1 elif def_build_buy_tags & self.BED_TAGS.kid_beds: total_beds += 1 kid_bed_exist = True elif def_build_buy_tags & self.BED_TAGS.other_sleeping_spots: alternative_sleeping_spots += 1 elif def_build_buy_tags & self.BED_TAGS.beds: total_beds += 1 if open_street_data is not None: open_street_data.objects = open_street_objects if zone_data is not None: bed_info_data = gameplay_serialization.ZoneBedInfoData() bed_info_data.num_beds = total_beds bed_info_data.double_bed_exist = double_bed_exist bed_info_data.kid_bed_exist = kid_bed_exist bed_info_data.alternative_sleeping_spots = alternative_sleeping_spots zone_data.gameplay_zone_data.bed_info_data = bed_info_data lot = services.current_zone().lot for (inventory_type, inventory) in lot.get_all_object_inventories_gen(): for game_object in inventory: game_object.save_object(object_list.objects, ItemLocation.OBJECT_INVENTORY, inventory_type) def valid_objects(self): return [obj for obj in self._objects.values() if not obj._hidden_flags] def get_objects_of_type_gen(self, *definitions): for obj in self._objects.values(): while any(obj.definition is d for d in definitions): yield obj def get_objects_with_tag_gen(self, tag): for obj in self._objects.values(): while build_buy.get_object_has_tag(obj.definition.id, tag): yield obj def add_sim_spawn_condition(self, sim_id, callback): for sim in services.sim_info_manager().instanced_sims_gen(): while sim.id == sim_id: logger.error('Sim {} is already in the world, cannot add the spawn condition', sim) return self._sim_spawn_conditions[sim_id].add(callback) def remove_sim_spawn_condition(self, sim_id, callback): if callback not in self._sim_spawn_conditions.get(sim_id, ()): logger.error('Trying to remove sim spawn condition with invalid id-callback pair ({}-{}).', sim_id, callback) return self._sim_spawn_conditions[sim_id].remove(callback) def trigger_sim_spawn_condition(self, sim_id): if sim_id in self._sim_spawn_conditions: for callback in self._sim_spawn_conditions[sim_id]: callback() del self._sim_spawn_conditions[sim_id] def register_portal_added_callback(self, callback): if callback not in self._portal_added_callbacks: self._portal_added_callbacks.append(callback) def unregister_portal_added_callback(self, callback): if callback in self._portal_added_callbacks: self._portal_added_callbacks.remove(callback) def register_portal_removed_callback(self, callback): if callback not in self._portal_removed_callbacks: self._portal_removed_callbacks.append(callback) def unregister_portal_removed_callback(self, callback): if callback in self._portal_removed_callbacks: self._portal_removed_callbacks.remove(callback) def add_portal_to_cache(self, portal): self._portal_cache.add(portal) self._portal_added_callbacks(portal) def remove_portal_from_cache(self, portal): self._portal_cache.remove(portal) self._portal_removed_callbacks(portal) def register_front_door_candidates_changed_callback(self, callback): if callback not in self._front_door_candidates_changed_callback: self._front_door_candidates_changed_callback.append(callback) def unregister_front_door_candidates_changed_callback(self, callback): if callback in self._front_door_candidates_changed_callback: self._front_door_candidates_changed_callback.remove(callback) def on_front_door_candidates_changed(self): self._front_door_candidates_changed_callback() def advertising_objects_gen(self, motives:set=DEFAULT): if not motives: return if motives is DEFAULT: for obj in self.valid_objects(): while obj.commodity_flags: yield obj return for obj in self.valid_objects(): while obj.commodity_flags & motives: yield obj def get_all_objects_with_component_gen(self, component): if component is None: return for obj in self.valid_objects(): if obj.has_component(component.instance_attr): yield obj else: while obj.has_component(component.class_attr): yield obj def on_location_changed(self, obj): self._registered_callbacks[CallbackTypes.ON_OBJECT_LOCATION_CHANGED](obj) @classproperty def supports_parenting(self): return True
class SituationGoal(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.SITUATION_GOAL)): __qualname__ = 'SituationGoal' INSTANCE_SUBCLASSES_ONLY = True IS_TARGETED = False INSTANCE_TUNABLES = {'_display_name': TunableLocalizedStringFactory(description='\n Display name for the Situation Goal. It takes one token, the\n target (0) of this situation goal.\n ', tuning_group=GroupNames.UI), '_icon': TunableResourceKey('PNG:missing_image', description='\n Icon to be displayed for the Situation Goal.\n ', needs_tuning=True, resource_types=sims4.resources.CompoundTypes.IMAGE), '_pre_tests': TunableSituationGoalPreTestSet(description='\n A set of tests on the player sim and environment that all must\n pass for the goal to be given to the player. e.g. Player Sim\n has cooking skill level 7.\n ', tuning_group=GroupNames.TESTS), '_post_tests': TunableSituationGoalPostTestSet(description='\n A set of tests that must all pass when the player satisfies the\n goal_test for the goal to be consider completed. e.g. Player\n has Drunk Buff when Kissing another sim at Night.\n ', tuning_group=GroupNames.TESTS), '_environment_pre_tests': TunableSituationGoalEnvironmentPreTestSet(description='\n A set of sim independent pre tests.\n e.g. There are five desks.\n ', tuning_group=GroupNames.TESTS), 'role_tags': TunableSet(TunableEnumEntry(Tag, Tag.INVALID), description='\n This goal will only be given to Sims in SituationJobs or Role\n States marked with one of these tags.\n '), '_cooldown': TunableSimMinute(description='\n The cooldown of this situation goal. Goals that have been\n completed will not be chosen again for the amount of time that\n is tuned.\n ', default=600, minimum=0), '_iterations': Tunable(description='\n Number of times the player must perform the action to complete the goal\n ', tunable_type=int, default=1), '_score': Tunable(description='\n The number of points received for completing the goal.\n ', tunable_type=int, default=10), '_goal_loot_list': TunableList(description='\n A list of pre-defined loot actions that will applied to every\n sim in the situation when this situation goal is completed.\n \n Do not use this loot list in an attempt to undo changes made by\n the RoleStates to the sim. For example, do not attempt\n to remove buffs or commodities added by the RoleState.\n ', tunable=SituationGoalLootActions.TunableReference()), '_tooltip': TunableLocalizedStringFactory(description='\n Tooltip for this situation goal. It takes one token, the\n actor (0) of this situation goal.'), 'noncancelable': Tunable(description='\n Checking this box will prevent the player from canceling this goal in the whim system.', tunable_type=bool, needs_tuning=True, default=False), 'time_limit': Tunable(description='\n Timeout (in Sim minutes) for Sim to complete this goal. The default state of 0 means\n time is unlimited. If the goal is not completed in time, any tuned penalty loot is applied.', tunable_type=int, default=0), 'penalty_loot_list': TunableList(description='\n A list of pre-defined loot actions that will applied to the Sim who fails\n to complete this goal within the tuned time limit.\n ', tunable=SituationGoalLootActions.TunableReference()), 'goal_completion_notification': OptionalTunable(tunable=UiDialogNotification.TunableFactory(description='\n A TNS that will fire when this situation goal is completed.\n ')), 'audio_sting_on_complete': TunableResourceKey(description='\n The sound to play when this goal is completed.\n ', default=None, resource_types=(sims4.resources.Types.PROPX,), tuning_group=GroupNames.AUDIO), 'goal_completion_modal_dialog': OptionalTunable(tunable=UiDialogOk.TunableFactory(description='\n A modal dialog that will fire when this situation goal is\n completed.\n '))} @classmethod def can_be_given_as_goal(cls, actor, situation, **kwargs): if actor is not None: resolver = event_testing.resolver.DataResolver(actor.sim_info, None) result = cls._pre_tests.run_tests(resolver) if not result: return result environment_test_result = cls._environment_pre_tests.run_tests(Resolver()) if not environment_test_result: return environment_test_result return TestResult.TRUE def __init__(self, sim_info=None, situation=None, goal_id=0, count=0, **kwargs): self._sim_info = sim_info self._situation = situation self.id = goal_id self._on_goal_completed_callbacks = CallableList() self._completed_time = None self._count = count def destroy(self): self.decommision() self._sim_info = None self._situation = None def decommision(self): self._on_goal_completed_callbacks.clear() def create_seedling(self): actor_id = 0 if self._sim_info is None else self._sim_info.sim_id seedling = situations.situation_serialization.GoalSeedling(type(self), actor_id, self._count) return seedling def register_for_on_goal_completed_callback(self, listener): self._on_goal_completed_callbacks.append(listener) def unregister_for_on_goal_completed_callback(self, listener): self._on_goal_completed_callbacks.remove(listener) def get_gsi_name(self): if self._iterations <= 1: return self.__class__.__name__ return '{} {}/{}'.format(self.__class__.__name__, self._count, self._iterations) def _on_goal_completed(self): self._completed_time = services.time_service().sim_now for loots in self._goal_loot_list: for loot in loots.goal_loot_actions: for sim in self._situation.all_sims_in_situation_gen(): loot.apply_to_resolver(sim.get_resolver()) client = services.client_manager().get_first_client() if client is not None: active_sim = client.active_sim resolver = SingleSimResolver(active_sim) if self.goal_completion_notification is not None: notification = self.goal_completion_notification(active_sim, resolver=resolver) notification.show_dialog() if self.goal_completion_modal_dialog is not None: dialog = self.goal_completion_modal_dialog(active_sim, resolver=resolver) dialog.show_dialog() self._on_goal_completed_callbacks(self, True) def _on_iteration_completed(self): self._on_goal_completed_callbacks(self, False) def debug_force_complete(self, target_sim): self._count = self._iterations self._on_goal_completed() def handle_event(self, sim_info, event, resolver): if self._sim_info is not None and self._sim_info is not sim_info: return if self._run_goal_completion_tests(sim_info, event, resolver): if self._count >= self._iterations: self._on_goal_completed() else: self._on_iteration_completed() def _run_goal_completion_tests(self, sim_info, event, resolver): return self._post_tests.run_tests(resolver) def _get_actual_target_sim_info(self): pass def get_required_target_sim_info(self): pass @property def created_time(self): pass @property def completed_time(self): return self._completed_time def is_on_cooldown(self): if self._completed_time is None: return False time_since_last_completion = services.time_service().sim_now - self._completed_time return time_since_last_completion < interval_in_sim_minutes(self._cooldown) def get_localization_tokens(self): if self._sim_info is None: return (self._numerical_token,) required_tgt_sim = self.get_required_target_sim_info() if required_tgt_sim is None: return (self._numerical_token, self._sim_info) return (self._numerical_token, self._sim_info, required_tgt_sim) @property def display_name(self): return self._display_name(*self.get_localization_tokens()) @property def tooltip(self): return self._tooltip(*self.get_localization_tokens()) @property def score(self): return self._score @property def completed_iterations(self): return self._count @property def max_iterations(self): return self._iterations @property def _numerical_token(self): return self.max_iterations