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
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 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)
class ReservationMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._reservation_handlers = () self._on_reservation_handlers_changed = None self._reservation_clobberers = None @property def in_use(self): if self._reservation_handlers: return True return False @property def self_or_part_in_use(self): if self._reservation_handlers: return True elif self.parts: return any(part.in_use for part in self.parts) return False def in_use_by(self, sim, owner=None): for handler in self._reservation_handlers: if handler.sim is not sim: continue if owner is not None and handler.reservation_interaction is not owner: continue return True return False def get_users(self, sims_only=False): users = set(handler.sim for handler in self._reservation_handlers if not sims_only or handler.sim.is_sim) if self.parts: for part in self.parts: users |= part.get_users(sims_only=sims_only) return frozenset(users) def get_reservation_handler(self, sim, **kwargs): reservation_type = ReservationHandlerBasic if not self.parts else ReservationHandlerAllParts return reservation_type(sim, self, **kwargs) def get_use_list_handler(self, sim, **kwargs): return ReservationHandlerUseList(sim, self, **kwargs) def may_reserve(self, reserver, *_, reservation_handler=None, _from_reservation_call=False, **kwargs): if reservation_handler is None: reservation_handler = self.get_reservation_handler(reserver) reserve_result = reservation_handler.may_reserve_internal(**kwargs) if gsi_handlers.sim_handlers_log.sim_reservation_archiver.enabled and reserver.is_sim: reserve_result_str = '{}: {}'.format( 'reserve' if not _from_reservation_call else 'may_reserve', reserve_result) gsi_handlers.sim_handlers_log.archive_sim_reservation( reservation_handler, reserve_result_str) return reserve_result def add_reservation_handler(self, reservation_handler): if isinstance(self._reservation_handlers, tuple): self._reservation_handlers = WeakSet() self._reservation_handlers.add(reservation_handler) if self._on_reservation_handlers_changed: self._on_reservation_handlers_changed(user=reservation_handler.sim, added=True) def get_reservation_handlers(self): return tuple(self._reservation_handlers) def remove_reservation_handler(self, reservation_handler): if not self._reservation_handlers: return self._reservation_handlers.discard(reservation_handler) if self._on_reservation_handlers_changed: self._on_reservation_handlers_changed(user=reservation_handler.sim, added=False) def add_reservation_clobberer(self, reservation_holder, reservation_clobberer): if self._reservation_clobberers is None: self._reservation_clobberers = defaultdict(WeakSet) self._reservation_clobberers[reservation_holder].add( reservation_clobberer) def is_reservation_clobberer(self, reservation_holder, reservation_clobberer): if self._reservation_clobberers is None: return False if reservation_holder not in self._reservation_clobberers: return False return reservation_clobberer in self._reservation_clobberers[ reservation_holder] def remove_reservation_clobberer(self, reservation_holder, reservation_clobberer): if self._reservation_clobberers is None: return if reservation_holder not in self._reservation_clobberers: return self._reservation_clobberers[reservation_holder].discard( reservation_clobberer) if not self._reservation_clobberers[reservation_holder]: del self._reservation_clobberers[reservation_holder] if not self._reservation_clobberers: self._reservation_clobberers = None 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.')) else: body_target_part_owner = sim.posture_state.body.target if body_target_part_owner is not None: if body_target_part_owner.is_part: body_target_part_owner = body_target_part_owner.part_owner transition_controller = sim.queue.transition_controller if not body_target_part_owner is self: if not transition_controller is None: if not transition_controller.will_derail_if_given_object_is_reset( self): reset_records.append( ResetRecord(sim, ResetReason.RESET_EXPECTED, self, 'Transitioning To or In.')) reset_records.append( ResetRecord(sim, ResetReason.RESET_EXPECTED, self, 'Transitioning To or In.')) def usable_by_transition_controller(self, transition_controller): if transition_controller is None: return False required_sims = transition_controller.interaction.required_sims() targets = (self, ) + tuple( self.get_overlapping_parts()) if self.is_part else (self, ) for reservation_handler in itertools.chain.from_iterable( target.get_reservation_handlers() for target in targets): if reservation_handler.sim in required_sims: continue reservation_interaction = reservation_handler.reservation_interaction if reservation_interaction is None: continue if reservation_interaction.priority >= transition_controller.interaction.priority: return False if transition_controller.interaction.priority <= Priority.Low: return False return True def register_on_use_list_changed(self, callback): if self._on_reservation_handlers_changed is None: self._on_reservation_handlers_changed = CallableList() self._on_reservation_handlers_changed.append(callback) def unregister_on_use_list_changed(self, callback): if callback in self._on_reservation_handlers_changed: self._on_reservation_handlers_changed.remove(callback) if not self._on_reservation_handlers_changed: self._on_reservation_handlers_changed = None
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 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 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 = services.current_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 self.objects_moved_via_live_drag = WeakSet() 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 if sim_info is not None: self._active_sim_info = sim_info if sim_info.household is not None: sim_info.household.on_active_sim_changed(sim_info) else: self._active_sim_info = None self.notify_active_sim_changed(current_sim, new_sim_info=sim_info) @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: if self._interaction_source is not Client._interaction_source: 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: if self._interaction_priority is not Client._interaction_priority: 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: if 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.simref is not None: sim = choice_menu.simref() if sim is not None: 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, *args, is_active=True, **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 validate_selectable_sim(self): if self._active_sim_info is None or not self._active_sim_info.is_enabled_in_skewer: self.set_next_sim() 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: self.resend_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: if 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_info=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() is_zone_running = services.current_zone().is_zone_running sim_info.on_sim_added_to_skewer() if not is_zone_running: sim_info.commodity_tracker.start_low_level_simulation() else: services.active_household().distribute_household_data() sim_info.commodity_tracker.send_commodity_progress_update( from_add=True) sim_info.career_tracker.on_sim_added_to_skewer() if sim_info.degree_tracker is not None: sim_info.degree_tracker.on_sim_added_to_skewer() sim_info.send_whim_bucks_update(SetWhimBucks.LOAD) sim_info.resend_trait_ids() sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS) if sim is not None: if is_zone_running: sim.inventory_component.visible_storage.allow_ui = True sim.inventory_component.publish_inventory_items() sim.ui_manager.refresh_ui_data() services.autonomy_service().logging_sims.add(sim) sim_info.start_aspiration_tracker_on_instantiation( force_ui_update=True) if sim_info.whim_tracker is not None: sim_info.whim_tracker.start_whims_tracker() zone_director = services.venue_service().get_zone_director() if zone_director is not None: zone_director.on_sim_added_to_skewer(sim_info) sim_info.trait_tracker.sort_and_send_commodity_list() def on_sim_removed_from_skewer(self, sim_info): 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): relationship_service = services.relationship_service() for sim_info in self.selectable_sims: sim_info.relationship_tracker.clean_and_send_remaining_relationship_info( ) relationship_service.clean_and_send_remaining_object_relationships( sim_info.id) 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() stack_items = previous_inventory.get_stack_items(stack_id) if remove: if is_stack: for item in stack_items: success = previous_inventory.try_remove_object_by_id( item.id, count=item.stack_count()) else: success = previous_inventory.try_remove_object_by_id( drag_object.id, count=1) stack_items = previous_inventory.get_stack_items(stack_id) else: success = previous_inventory.try_remove_object_by_id( drag_object.id, count=1) stack_items = previous_inventory.get_stack_items(stack_id) else: success = True 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: if 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, should_send_start_message: bool = True): 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 not ((not item.in_use or item.in_use_by(self)) and 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 for child_object in live_drag_object.get_all_children_gen(): sell_value += self.get_live_drag_object_value( child_object) if child_object.definition.get_is_deletable( ) else 0 else: sell_value = -1 (valid_drop_object_ids, valid_stack_id) = live_drag_component.get_valid_drop_object_ids() icon_info = create_icon_info_msg(live_drag_object.get_icon_info_data()) if should_send_start_message: op = distributor.ops.LiveDragStart(live_drag_object.id, start_system, valid_drop_object_ids, valid_stack_id, sell_value, icon_info) 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 if end_system == LiveDragLocation.BUILD_BUY: self.objects_moved_via_live_drag.add(source_object) else: self.objects_moved_via_live_drag.discard(source_object) self.cancel_live_drag_on_objects() next_object_id = None success = False inventory_item = source_object.inventoryitem_component 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) elif source_object.parent_object() is target_object: success = True elif source_object.parent_object( ) is None and location is not None: inventory_item = source_object.inventoryitem_component if inventory_item is not None: if inventory_item.is_in_inventory(): (success, next_object_id ) = self.remove_drag_object_and_get_next_item( source_object) source_object.set_location(location) else: logger_live_drag.error( 'Live Drag Target Component missing on object: {} and {} cannot be slotted into it.' .format(target_object, source_object)) success = False elif inventory_item is not None and inventory_item.is_in_inventory(): if inventory_item.can_place_in_world or not inventory_item.inventory_only: if location is not None: source_object.set_location(location) (success, next_object_id ) = self.remove_drag_object_and_get_next_item(source_object) else: success = True if location is not None: source_object.set_location(location) 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): op = distributor.ops.LiveDragEnd(live_drag_object.id, self._live_drag_start_system, end_system, next_stack_object_id=None) distributor_system = Distributor.instance() distributor_system.add_op_with_no_owner(op) if not dialog.accepted: self.cancel_live_drag_on_objects() return value = int( self.get_live_drag_object_value(live_drag_object, self._live_drag_is_stack)) for child_object in live_drag_object.get_all_children_gen(): value += self.get_live_drag_object_value( child_object) if child_object.definition.get_is_deletable( ) else 0 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: live_drag_component = item.live_drag_component live_drag_component.cancel_live_dragging( should_reset=False) item.base_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: live_drag_object.live_drag_component.cancel_live_dragging( should_reset=False) 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.base_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 favorites_tracker = self.active_sim_info.favorites_tracker if favorites_tracker and favorites_tracker.is_favorite_stack( live_drag_object): dialog = LiveDragTuning.LIVE_DRAG_SELL_FAVORITE_DIALOG( owner=live_drag_object) elif 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) 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_info=None): new_sim = new_sim_info.get_sim_instance( ) if new_sim_info is not None else None self._active_sim_changed(old_sim, new_sim) vfx_mask.notify_client_mask_update(new_sim_info) def _get_selector_visual_type(self, sim_info): if sim_info.is_baby: return (Sims_pb2.SimPB.BABY, None) if sim_info.is_toddler and services.daycare_service( ).is_sim_info_at_daycare(sim_info): return (Sims_pb2.SimPB.AT_DAYCARE, None) if sim_info.household.missing_pet_tracker.is_pet_missing(sim_info): return (Sims_pb2.SimPB.PET_MISSING, None) sim = sim_info.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS) for career in sim_info.careers.values(): if career.currently_at_work: if career.is_at_active_event and sim is None: return (Sims_pb2.SimPB.MISSING_ACTIVE_WORK, career.career_category) return (Sims_pb2.SimPB.AT_WORK, career.career_category) if career.is_late: if not career.taking_day_off: return (Sims_pb2.SimPB.LATE_FOR_WORK, career.career_category) if services.get_rabbit_hole_service( ).should_override_selector_visual_type(sim_info.id): return (Sims_pb2.SimPB.OTHER, None) if sim is not None and sim.has_hidden_flags( HiddenReasonFlag.RABBIT_HOLE): return (Sims_pb2.SimPB.OTHER, None) if services.hidden_sim_service().is_hidden(sim_info.id): return (Sims_pb2.SimPB.OTHER, None) tutorial_service = services.get_tutorial_service() if tutorial_service is not None and tutorial_service.is_sim_unselectable( sim_info): 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 if sim_info.career_tracker is None: logger.error( 'CareerTracker is None for selectable Sim {}'.format( sim_info)) else: career = sim_info.career_tracker.get_currently_at_work_career( ) new_sim.at_work = career is not None and not career.is_at_active_event 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 new_sim.can_care_for_toddler_at_home = sim_info.can_care_for_toddler_at_home if 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) if 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)) @constproperty def is_sim(): return False
class InstanceManager: 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 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): raise RuntimeError('[manus] Reloading tuning is not supported for optimized python builds.') registered_resource_key = sims4.resources.Key(self.TYPE, key.instance) cls = self._tuned_classes.get(registered_resource_key) if cls is None: self.get(registered_resource_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() if 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 @property def base_game_only(self): return self._base_game_only @property def require_reference(self): return self._require_reference @property def remapped_keys(self): return self._remapped_keys 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 paths.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 paths.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: cls = sims4.tuning.serialization.create_class_instance(resource_key, self.TYPE) if cls is None: return registered_resource_key = sims4.resources.Key(self.TYPE, resource_key.instance) self.register_tuned_class(cls, registered_resource_key) if resource_key.group: self._remapped_keys[registered_resource_key] = 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 tuple(self._tuned_classes.items()): try: tuned_classes_key = key if key in self._remapped_keys: key = self._remapped_keys[key] (tuning_callback_helpers, verify_tunable_callback_helpers) = sims4.tuning.serialization.load_from_xml(key, self.TYPE, cls) additional_pack = getattr(cls, 'additional_pack', None) if additional_pack is not None: if not is_available_pack(cls.additional_pack): del self._tuned_classes[tuned_classes_key] continue self._callback_helper[cls] = tuning_callback_helpers except Exception: logger.exception('Exception while finalizing tuning for {}.', cls, owner='manus') self._remapped_keys = None def invoke_registered_callbacks_gen(self, registered_callbacks_timing_file=None): logger.info('Invoking callbacks for {:4} tuning class instances managed by {}.', len(self._tuned_classes), self, owner='manus') for cls in self._tuned_classes.values(): start_time = time.time() callback_timing = self._invoke_tunable_callbacks(cls, registered_callbacks_timing_file is not None) invoke_time = time.time() - start_time try: if hasattr(cls, TUNING_LOADED_CALLBACK): cls._tuning_loaded_callback() except Exception: logger.exception('Exception in {}.{}.', cls, TUNING_LOADED_CALLBACK, owner='manus') if registered_callbacks_timing_file is not None: registered_callbacks_timing_file.write('{},{},{},{}\n'.format(cls, time.time() - start_time, invoke_time, ','.join(callback_timing))) yield False self._callback_helper = None self._load_all_complete = True self._load_all_complete_callbacks(self) def _invoke_tunable_callbacks(self, cls, return_call_back_timing=False): callback_timing = [] tuning_callbacks = self._callback_helper.get(cls) if tuning_callbacks is None: return callback_timing for helper in tuning_callbacks: start_time = time.time() try: helper.template.invoke_callback(cls, helper.name, helper.source, helper.value) if return_call_back_timing: callback_timing.append('{}:{}'.format(helper.template.invoke_callback, time.time() - start_time)) except Exception: logger.exception('Exception in a tunable callback for variable {} in instance class {}.', helper.name, cls, owner='manus') return callback_timing def invoke_verify_tuning_callback_gen(self): for cls in self._tuned_classes.values(): self._invoke_verify_tunable_callbacks(cls) try: if hasattr(cls, VERIFY_TUNING_CALLBACK): cls._verify_tuning_callback() except Exception: logger.exception('Exception in {}.{}.', cls, VERIFY_TUNING_CALLBACK, owner='manus') yield False self._verify_tunable_callback_helper = None 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, get_fallback_definition_id=True): 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 sims4.tuning.merged_tuning_manager.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__ if not filter_fn is None: if filter_fn(cls): to_export[cls_name] = 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) if 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 ObjectManager(DistributableObjectManager, GameObjectManagerMixin, AttractorManagerMixin): 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))) HOUSEHOLD_INVENTORY_OBJECT_TAGS = TunableTags( description= '\n List of tags to apply to every household inventory proxy object.\n ' ) INVALID_UNPARENTED_OBJECT_TAGS = TunableTags( description= '\n Objects with these tags should not exist without a parent. An obvious\n case is for transient objects. They should only exist as a carried object,\n thus parented to a sim, when loading into a save game.\n ' ) 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 = [] @classproperty def save_error_code(cls): return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_OBJECT_MANAGER @property def crafting_cache(self): return self._crafting_cache @property def water_terrain_object_cache(self): return self._water_terrain_object_cache def portal_cache_gen(self): yield from 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): logger.assert_raise( isinstance(inventory_manager, InventoryManager), 'Trying to move object to a non-inventory manager: {}', inventory_manager, owner='tingyul') logger.assert_raise( obj.id, 'Attempting to move an object that was never added or has already been removed', owner='tingyul') logger.assert_raise( self._objects.get(obj.id) is obj, 'Attempting to move an object {} that is not in this manager or not the same object {} in manager', obj, self._objects.get(obj.id), owner='tingyul') del self._objects[obj.id] obj.manager = inventory_manager inventory_manager._objects[obj.id] = obj self.remove_object_from_object_tags_cache(obj) self.remove_object_from_posture_providing_cache(obj) def add(self, obj, *args, **kwargs): super().add(obj, *args, **kwargs) self.add_object_to_object_tags_cache(obj) self.add_object_to_posture_providing_cache(obj) def remove(self, obj, *args, **kwargs): super().remove(obj, *args, **kwargs) current_zone = services.current_zone() if not current_zone.is_zone_shutting_down: self.remove_object_from_object_tags_cache(obj) self.remove_object_from_posture_providing_cache(obj) def add_object_to_object_tags_cache(self, obj): self.add_tags_and_object_to_cache(obj.get_tags(), obj) def add_tags_and_object_to_cache(self, tags, obj): if obj.id not in self: logger.error( "Trying to add object to tag cache when the object isn't in the manager: {}", obj, owner='tingyul') return for tag in tags: object_list = self._tag_to_object_list[tag] object_list.add(obj) def remove_object_from_object_tags_cache(self, obj): for tag in obj.get_tags(): if tag not in self._tag_to_object_list: continue object_list = self._tag_to_object_list[tag] if obj not in object_list: continue object_list.remove(obj) if not object_list: del self._tag_to_object_list[tag] def _should_save_object_on_lot(self, obj): parent = obj.parent if parent is not None and parent.is_sim: inventory = parent.inventory_component if inventory.should_save_parented_item_to_inventory(obj): return False else: vehicle_component = obj.vehicle_component if vehicle_component is not None: driver = vehicle_component.driver if driver is not None and driver.is_sim: inventory = driver.inventory_component if inventory.should_save_parented_item_to_inventory( obj): return False else: vehicle_component = obj.vehicle_component if vehicle_component is not None: driver = vehicle_component.driver if driver is not None and driver.is_sim: inventory = driver.inventory_component if inventory.should_save_parented_item_to_inventory(obj): return False return True def add_object_to_posture_providing_cache(self, obj): if not obj.provided_mobile_posture_affordances: return if self._posture_providing_object_cache is None: self._posture_providing_object_cache = set() self._posture_providing_object_cache.add(obj) posture_graph_service = services.posture_graph_service() if not posture_graph_service.has_built_for_zone_spin_up: posture_graph_service.on_mobile_posture_object_added_during_zone_spinup( obj) def remove_object_from_posture_providing_cache(self, obj): if not obj.provided_mobile_posture_affordances: return self._posture_providing_object_cache.remove(obj) if not self._posture_providing_object_cache: self._posture_providing_object_cache = None def get_posture_providing_objects(self): return self._posture_providing_object_cache or () def rebuild_objects_to_ignore_portal_validation_cache(self): self._objects_to_ignore_portal_validation_cache.clear() for obj in self._objects.values(): if not obj.routing_component is not None: if not obj.inventoryitem_component is not None: if obj.live_drag_component is not None: self._objects_to_ignore_portal_validation_cache.append( obj.id) self._objects_to_ignore_portal_validation_cache.append(obj.id) def clear_objects_to_ignore_portal_validation_cache(self): self._objects_to_ignore_portal_validation_cache.clear() def get_objects_to_ignore_portal_validation_cache(self): return self._objects_to_ignore_portal_validation_cache def clear_caches_on_teardown(self): self._tag_to_object_list.clear() self._water_terrain_object_cache.clear() if self._posture_providing_object_cache is not None: self._posture_providing_object_cache.clear() self.clear_objects_to_ignore_portal_validation_cache() build_buy.unregister_build_buy_exit_callback( self._water_terrain_object_cache.refresh) def pre_save(self): all_objects = list(self._objects.values()) lot = services.current_zone().lot for (_, inventory) in lot.get_all_object_inventories_gen( shared_only=True): for game_object in inventory: all_objects.append(game_object) for game_object in all_objects: game_object.update_all_commodities() @staticmethod def save_game_object(game_object, object_list, open_street_objects): save_result = None 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) return save_result def save(self, object_list=None, zone_data=None, open_street_data=None, store_travel_group_placed_objects=False, **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 university_roommate_beds = 0 if store_travel_group_placed_objects: objects_to_save_for_clean_up = [] roommate_bed_tags = set() roommate_service = services.get_roommate_service() if roommate_service is not None: roommate_bed_tags = roommate_service.BED_TAGS for game_object in self._objects.values(): if self._should_save_object_on_lot(game_object): save_result = ObjectManager.save_game_object( game_object, object_list, open_street_objects) if not save_result: continue if zone_data is None: continue if store_travel_group_placed_objects and save_result.owner_id != 0: placement_flags = build_buy.get_object_placement_flags( game_object.definition.id) if build_buy.PlacementFlags.NON_INVENTORYABLE not in placement_flags: objects_to_save_for_clean_up.append(save_result) if not game_object.definition.has_build_buy_tag( *self._all_bed_tags): continue if game_object.definition.has_build_buy_tag( *self.BED_TAGS.double_beds): double_bed_exist = True total_beds += 1 elif game_object.definition.has_build_buy_tag( *self.BED_TAGS.kid_beds): total_beds += 1 kid_bed_exist = True elif game_object.definition.has_build_buy_tag( *self.BED_TAGS.other_sleeping_spots): alternative_sleeping_spots += 1 elif game_object.definition.has_build_buy_tag( *self.BED_TAGS.beds): total_beds += 1 if len(roommate_bed_tags) > 0: if game_object.definition.has_build_buy_tag( *roommate_bed_tags): university_roommate_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 if roommate_service is not None: household_and_roommate_cap = roommate_service.HOUSEHOLD_AND_ROOMMATE_CAP bed_info_data.university_roommate_beds = min( household_and_roommate_cap, university_roommate_beds) zone_data.gameplay_zone_data.bed_info_data = bed_info_data if store_travel_group_placed_objects: current_zone = services.current_zone() save_game_protocol_buffer = services.get_persistence_service( ).get_save_game_data_proto() self._clear_clean_up_data_for_zone(current_zone, save_game_protocol_buffer) self._save_clean_up_destination_data( current_zone, objects_to_save_for_clean_up, save_game_protocol_buffer) lot = services.current_zone().lot for (inventory_type, inventory) in lot.get_all_object_inventories_gen( shared_only=True): for game_object in inventory: game_object.save_object(object_list.objects, ItemLocation.OBJECT_INVENTORY, inventory_type) def _clear_clean_up_data_for_zone(self, current_zone, save_game_protocol_buffer): current_zone_id = current_zone.id current_open_street_id = current_zone.open_street_id destination_clean_up_data = save_game_protocol_buffer.destination_clean_up_data for clean_up_save_data in destination_clean_up_data: indexes_to_clean_up = [] for (index, old_object_clean_up_data) in enumerate( clean_up_save_data.object_clean_up_data_list): if not old_object_clean_up_data.zone_id == current_zone_id: if old_object_clean_up_data.world_id == current_open_street_id: indexes_to_clean_up.append(index) indexes_to_clean_up.append(index) if len(indexes_to_clean_up) == len( clean_up_save_data.object_clean_up_data_list): clean_up_save_data.ClearField('object_clean_up_data_list') else: for index in reversed(indexes_to_clean_up): del clean_up_save_data.object_clean_up_data_list[index] def _save_clean_up_destination_data(self, current_zone, objects_to_save_for_clean_up, save_game_protocol_buffer): household_manager = services.household_manager() travel_group_manager = services.travel_group_manager() clean_up_save_data = None for object_data in sorted(objects_to_save_for_clean_up, key=lambda x: x.owner_id): owner_id = object_data.owner_id if clean_up_save_data is None or clean_up_save_data.household_id != owner_id: household = household_manager.get(owner_id) travel_group = None if household is not None: travel_group = household.get_travel_group() for clean_up_save_data in save_game_protocol_buffer.destination_clean_up_data: if clean_up_save_data.household_id != owner_id: continue if travel_group is not None: if travel_group.id == clean_up_save_data.travel_group_id: break if clean_up_save_data.travel_group_id in travel_group_manager: continue break with ProtocolBufferRollback( clean_up_save_data.object_clean_up_data_list ) as object_clean_up_data: if object_data.loc_type == ItemLocation.ON_LOT: object_clean_up_data.zone_id = current_zone.id else: object_clean_up_data.world_id = current_zone.open_street_id object_clean_up_data.object_data = object_data def add_sim_spawn_condition(self, sim_id, callback): for sim in services.sim_info_manager().instanced_sims_gen(): if 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 add_portal_lock(self, sim, callback): self.register_portal_added_callback(callback) for portal in self.portal_cache_gen(): portal.lock_sim(sim) 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 _is_valid_portal_object(self, portal): portal_component = portal.get_component(PORTAL_COMPONENT) if portal_component is None: return False return portal.has_portals() def add_portal_to_cache(self, portal): if portal not in self._portal_cache and self._is_valid_portal_object( portal): self._portal_cache.add(portal) self._portal_added_callbacks(portal) def remove_portal_from_cache(self, portal): if portal in self._portal_cache: 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 cleanup_build_buy_transient_objects(self): household_inventory_proxy_objects = self.get_objects_matching_tags( self.HOUSEHOLD_INVENTORY_OBJECT_TAGS) for obj in household_inventory_proxy_objects: self.remove(obj) def get_objects_matching_tags(self, tags: set, match_any=False): matching_objects = None for tag in tags: objs = self._tag_to_object_list[ tag] if tag in self._tag_to_object_list else set() if matching_objects is None: matching_objects = objs elif match_any: matching_objects |= objs else: matching_objects &= objs if not matching_objects: break if matching_objects: return frozenset(matching_objects) return EMPTY_SET def get_num_objects_matching_tags(self, tags: set, match_any=False): matching_objects = self.get_objects_matching_tags(tags, match_any) return len(matching_objects) @contextmanager def batch_commodity_flags_update(self): default_fn = self.clear_commodity_flags_for_objs_with_affordance try: affordances = set() self.clear_commodity_flags_for_objs_with_affordance = affordances.update yield None finally: self.clear_commodity_flags_for_objs_with_affordance = default_fn self.clear_commodity_flags_for_objs_with_affordance(affordances) def clear_commodity_flags_for_objs_with_affordance(self, affordances): for obj in self.valid_objects(): if not obj.has_updated_commodity_flags(): continue if any(affordance in affordances for affordance in obj.super_affordances()): obj.clear_commodity_flags() def get_all_objects_with_component_gen(self, component_definition): if component_definition is None: return for obj in self.valid_objects(): if obj.has_component(component_definition): yield obj def get_objects_with_tag_gen(self, tag): yield from self.get_objects_matching_tags((tag, )) def get_objects_with_tags_gen(self, *tags): yield from self.get_objects_matching_tags(tags, match_any=True) def on_location_changed(self, obj): self._registered_callbacks[CallbackTypes.ON_OBJECT_LOCATION_CHANGED]( obj) def process_invalid_unparented_objects(self): invalid_objects = self.get_objects_matching_tags( self.INVALID_UNPARENTED_OBJECT_TAGS, match_any=True) for invalid_object in invalid_objects: if invalid_object.parent is None: logger.error( 'Invalid unparented object {} existed in game. Cleaning up.', invalid_object) invalid_object.destroy( source=invalid_object, cause='Invalid unparented object found on zone spin up.') @classproperty def supports_parenting(self): return True def add_active_whim_set(self, whim_set): self._whim_set_cache[whim_set] += 1 def remove_active_whim_set(self, whim_set): self._whim_set_cache[whim_set] -= 1 if self._whim_set_cache[whim_set] <= 0: del self._whim_set_cache[whim_set] @property def active_whim_sets(self): return set(self._whim_set_cache.keys())
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 UiMultiPicker(UiDialogOkCancel): FACTORY_TUNABLES = {'pickers': TunableList(description='\n A list of picker interactions to use to build pickers.\n ', tunable=TunableTuple(description='\n \n ', picker_interaction=TunableReference(description='\n The interaction that will be used to generate a picker.\n ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), pack_safe=True), disabled_tooltip=TunableLocalizedString(description='\n The string that will be displayed when an item in the \n associated picker is not available.\n ')), tuning_group=GroupNames.PICKERTUNING), 'text_input': OptionalTunable(description='\n If enabled then this dialog will also support a text input box.\n ', tunable=UiTextInput.TunableFactory(description='\n The tuning for the Text Input part of the dialog.\n ', locked_args={'sort_order': 1}))} 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 add_text_listener(self, listener): self._text_listeners.append(listener) def set_target_sim(self, target_sim): self.target_sim = target_sim def set_target(self, target): self.target = target def build_msg(self, **kwargs): message = super().build_msg(**kwargs) message.dialog_type = Dialog_pb2.UiDialogMessage.MULTI_PICKER if self.text_input is not None: text_input_overrides = {} (text_name, text) = self.existing_text text_input_overrides = {} if text: text_input_overrides[text_name] = lambda *_, **__: LocalizationHelperTuning.get_raw_text(text) self.text_input.build_msg(self, message, name=text_name, text_input_overrides=text_input_overrides if text_input_overrides else None) context = InteractionContext(self._owner(), InteractionSource.SCRIPT, Priority.Low) multi_picker_msg = Dialog_pb2.UiDialogMultiPicker() for picker_data in self.pickers: aop = AffordanceObjectPair(picker_data.picker_interaction, None, picker_data.picker_interaction, None) result = aop.interaction_factory(context) if not result: continue interaction = result.interaction picker_tuning = picker_data.picker_interaction.picker_dialog if picker_tuning.title is None: title = lambda *_, **__: interaction.get_name(apply_name_modifiers=False) else: title = self.picker_dialog.title dialog = picker_tuning(self._owner(), title=title, resolver=interaction.get_resolver(context=context)) interaction._setup_dialog(dialog) dialog.add_listener(interaction._on_picker_selected) self._picker_dialogs[dialog.dialog_id] = dialog new_message = dialog.build_msg() multi_picker_item = multi_picker_msg.multi_picker_items.add() multi_picker_item.picker_data = new_message.picker_data multi_picker_item.picker_id = new_message.dialog_id multi_picker_item.disabled_tooltip = picker_data.disabled_tooltip message.multi_picker_data = multi_picker_msg return message def on_text_input(self, text_input_name='', text_input=''): self._text_listeners(self, text_input_name, text_input) return True @property def multi_select(self): return False def multi_picker_result(self, response_proto): for picker_result in response_proto.picker_responses: if picker_result.picker_id in self._picker_dialogs: dialog = self._picker_dialogs[picker_result.picker_id] if not self._changes_made: if self._check_for_changes(dialog, picker_result.choices): self._changes_made = True dialog.pick_results(picker_result.choices) dialog.respond(ButtonType.DIALOG_RESPONSE_OK) if self.existing_text[1] != response_proto.text_input: self._changes_made = True self.on_text_input(text_input=response_proto.text_input) def _check_for_changes(self, dialog, choices): for (index, row) in enumerate(dialog.picker_rows): if row.is_selected is not (index in choices): return True return False def get_single_result_tag(self): if self.response == ButtonType.DIALOG_RESPONSE_OK and self._changes_made: return True return False def get_result_tags(self): if self.response == ButtonType.DIALOG_RESPONSE_OK and self._changes_made: return True 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 CurfewService(Service): ALLOWED_CURFEW_TIMES = TunableList( description= '\n A list of times (in military time) that are allowed to be set as curfew\n times.\n \n NOTE: Many objects will have curfew components and will only support\n a visual of certain values. Changing these values without making sure\n the art supports the value will not work properly. Please only change\n these values if you know for sure they need to be changed and are \n getting support from modelling to make the change.\n ', tunable=TunableRange( description= '\n The hour for which the curfew will be set to.\n ', tunable_type=int, default=0, minimum=0, maximum=23)) CURFEW_END_TIME = TunableRange( description= '\n The time when the curfew is considered to be over and the Sims are \n no longer subject to it.\n \n This should probably be set to some time in the morning. 6am perhaps.\n ', tunable_type=int, default=0, minimum=0, maximum=23) MINUTES_BEFORE_CURFEW_WARNING = TunableSimMinute( description= '\n The minutes before the curfew starts that a Sim should receive a \n warning about the curfew being about to start.\n ', default=30) BREAK_CURFEW_WARNING = TunableLocalizedStringFactory( description= '\n The string that is used to warn the player that a pie menu action will\n cause the Sim to break curfew. This will wrap around the name of the \n interaction so should be tuned to something like [Warning] {0.String}.\n ' ) CURFEW_WARNING_TEXT_MESSAGE_DIALOG = UiDialogOk.TunableFactory( description= '\n The dialog to display as a text message when warning a Sim that their\n curfew is about to expire.\n ' ) CURFEW_WARNING_SIM_TESTS = TunableTestSet( description= '\n Tests to run on each of the Sims to determine if they should receive\n the curfew warning text message or not.\n ' ) BREAK_CURFEW_BUFF = TunablePackSafeReference( description= "\n The buff that get's added to a Sim that breaks curfew. This buff will\n enable the Sim to be disciplined for their behavior.\n ", manager=services.buff_manager()) INTERACTION_BLACKLIST_TAGS = TunableSet( description= '\n A list of all the tags that blacklist interactions from causing Sims to\n break curfew.\n ', tunable=TunableEnumEntry( description= '\n A tag that when tagged on the interaction will allow the Sim to run\n the interaction and not break curfew.\n ', tunable_type=Tag, default=Tag.INVALID, pack_safe=True)) CURFEW_BEGIN_LOOT = TunablePackSafeReference( description= '\n The loot to apply to all Sims in the family when curfew begins. This\n will allow us to give buffs that affect the behavior of the Sims if\n they pass certain tests.\n ', manager=services.get_instance_manager(sims4.resources.Types.ACTION)) CURFEW_END_LOOT = TunablePackSafeReference( description= '\n The loot to apply to all Sims in the family when curfew ends. This will\n allow us to remove buffs that affect the behavior of the Sims.\n ', manager=services.get_instance_manager(sims4.resources.Types.ACTION)) UNSET = -1 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._zone_curfew_data = {} self._curfew_warning_alarm_handle = None self._curfew_started_alarm_handle = None self._curfew_ended_alarm_handle = None self._curfew_message_alarm_handle = None self._curfew_warning_callback = CallableList() self._curfew_started_callback = CallableList() self._curfew_ended_callback = CallableList() self._time_set_callback = CallableList() def get_zone_curfew(self, zone_id): curfew_setting = self._zone_curfew_data.get(zone_id, self.UNSET) return curfew_setting def set_zone_curfew(self, zone_id, curfew_setting): if self._zone_curfew_data.get(zone_id, None) == curfew_setting: return if curfew_setting not in CurfewService.ALLOWED_CURFEW_TIMES and curfew_setting != CurfewService.UNSET: return self._zone_curfew_data[zone_id] = curfew_setting self._update_curfew_settings(zone_id, curfew_setting) self._setup_curfew_text_message() def _update_curfew_settings(self, current_zone_id, current_setting): self._create_alarm_handles(current_zone_id) self._time_set_callback(current_setting) def _create_alarm_handles(self, zone_id): for alarm in (self._curfew_warning_alarm_handle, self._curfew_started_alarm_handle, self._curfew_ended_alarm_handle): if alarm is not None: alarms.cancel_alarm(alarm) time = self._zone_curfew_data.get(zone_id, self.UNSET) now = services.time_service().sim_now self._create_warning_callback(now, time) self._create_curfew_callback(now, time) self._create_curfew_ended_callback(now, time) def _create_warning_callback(self, now, time): if time is not CurfewService.UNSET: alarm_time = date_and_time.create_date_and_time(hours=time - 1) warning_span = now.time_till_next_day_time(alarm_time) if warning_span.in_ticks() == 0: warning_span += TimeSpan(date_and_time.sim_ticks_per_day()) self._curfew_warning_alarm_handle = alarms.add_alarm( self, warning_span, self._handle_warning_callback, False) def _handle_warning_callback(self, handle): self._curfew_warning_callback() now = services.time_service().sim_now time = self._zone_curfew_data.get(services.current_zone_id(), CurfewService.UNSET) self._create_warning_callback(now, time) def _create_curfew_callback(self, now, time): if time is not self.UNSET: alarm_time = date_and_time.create_date_and_time(hours=time) curfew_span = now.time_till_next_day_time(alarm_time) if curfew_span.in_ticks() == 0: curfew_span += TimeSpan(date_and_time.sim_ticks_per_day()) self._curfew_started_alarm_handle = alarms.add_alarm( self, curfew_span, self._handle_curfew_callback, False) def _handle_curfew_callback(self, handle): self._curfew_started_callback() now = services.time_service().sim_now time = self._zone_curfew_data.get(services.current_zone_id(), CurfewService.UNSET) self.apply_curfew_loots() self._create_curfew_callback(now, time) def _create_curfew_ended_callback(self, now, time): if time is not CurfewService.UNSET: alarm_time = date_and_time.create_date_and_time( hours=CurfewService.CURFEW_END_TIME) curfew_span = now.time_till_next_day_time(alarm_time) if curfew_span.in_ticks() == 0: curfew_span += TimeSpan(date_and_time.sim_ticks_per_day()) self._curfew_ended_alarm_handle = alarms.add_alarm( self, curfew_span, self._handle_curfew_ended_callback, False) def _handle_curfew_ended_callback(self, handle): self._curfew_ended_callback() now = services.time_service().sim_now time = CurfewService.CURFEW_END_TIME self.remove_curfew_loots() self._create_curfew_ended_callback(now, time) def register_for_alarm_callbacks(self, warning_callback, curfew_callback, curfew_over_callback, time_set_callback): self._curfew_warning_callback.append(warning_callback) self._curfew_started_callback.append(curfew_callback) self._curfew_ended_callback.append(curfew_over_callback) self._time_set_callback.append(time_set_callback) def unregister_for_alarm_callbacks(self, warning_callback, curfew_callback, curfew_over_callback, time_set_callback): if warning_callback in self._curfew_warning_callback: self._curfew_warning_callback.remove(warning_callback) if curfew_callback in self._curfew_started_callback: self._curfew_started_callback.remove(curfew_callback) if curfew_over_callback in self._curfew_ended_callback: self._curfew_ended_callback.remove(curfew_over_callback) if time_set_callback in self._time_set_callback: self._time_set_callback.remove(time_set_callback) def sim_breaking_curfew(self, sim, target, interaction=None): if interaction is not None and self.interaction_blacklisted( interaction): return False if sim.sim_info.is_in_travel_group(): return False situation_manager = services.get_zone_situation_manager() sim_situations = situation_manager.get_situations_sim_is_in(sim) if any(situation.disallows_curfew_violation for situation in sim_situations): return False active_household = services.active_household() if active_household is None: return False home_zone_id = active_household.home_zone_id curfew_setting = self._zone_curfew_data.get(home_zone_id, CurfewService.UNSET) if sim.sim_info not in active_household: return False if curfew_setting is not CurfewService.UNSET: if sim.sim_info.is_young_adult_or_older: return False elif self.past_curfew(curfew_setting): if not services.current_zone_id() == home_zone_id: ensemble_service = services.ensemble_service() ensemble = ensemble_service.get_visible_ensemble_for_sim( sim) if ensemble is not None and any( sim.sim_info.is_young_adult_or_older and sim.sim_info in active_household for sim in ensemble): return False return True if target is not None and not target.is_in_inventory( ) and not services.active_lot().is_position_on_lot( target.position): return True elif target is None and not services.active_lot( ).is_position_on_lot(sim.position): return True return True if target is not None and not target.is_in_inventory( ) and not services.active_lot().is_position_on_lot( target.position): return True elif target is None and not services.active_lot( ).is_position_on_lot(sim.position): return True return False def interaction_blacklisted(self, interaction): interaction_tags = interaction.get_category_tags() for tag in CurfewService.INTERACTION_BLACKLIST_TAGS: if tag in interaction_tags: return True return False def past_curfew(self, curfew_setting): now = services.time_service().sim_now if now.hour() >= curfew_setting or now.hour( ) < CurfewService.CURFEW_END_TIME: return True return False def _setup_curfew_text_message(self): if self._curfew_message_alarm_handle is not None: self._curfew_message_alarm_handle.cancel() self._curfew_message_alarm_handle = None current_household = services.active_household() if current_household is None: return home_zone_id = current_household.home_zone_id curfew_setting = self._zone_curfew_data.get(home_zone_id, CurfewService.UNSET) if curfew_setting is CurfewService.UNSET: return now = services.time_service().sim_now alarm_time = date_and_time.create_date_and_time(hours=curfew_setting) time_till_alarm = now.time_till_next_day_time(alarm_time) span = date_and_time.create_time_span( minutes=CurfewService.MINUTES_BEFORE_CURFEW_WARNING) time_till_alarm -= span self._curfew_message_alarm_handle = alarms.add_alarm( self, time_till_alarm, self._handle_curfew_message_callback, False) def _handle_curfew_message_callback(self, handle): active_lot = services.active_lot() if active_lot.lot_id != services.active_household_lot_id(): from_sim = None for sim_info in services.active_household(): if sim_info.is_young_adult_or_older: if not sim_info.is_instanced(): from_sim = sim_info break if from_sim is None: return for sim_info in services.active_household(): if sim_info.get_sim_instance() is None: continue resolver = DoubleSimResolver(sim_info, from_sim) if not CurfewService.CURFEW_WARNING_SIM_TESTS.run_tests( resolver): continue dialog = self.CURFEW_WARNING_TEXT_MESSAGE_DIALOG( sim_info, target_sim_id=from_sim.id, resolver=resolver) dialog.show_dialog() def add_broke_curfew_buff(self, sim): if not sim.has_buff(CurfewService.BREAK_CURFEW_BUFF): sim.add_buff(CurfewService.BREAK_CURFEW_BUFF) def remove_broke_curfew_buff(self, sim): if sim.has_buff(CurfewService.BREAK_CURFEW_BUFF): sim.remove_buff_by_type(CurfewService.BREAK_CURFEW_BUFF) def is_curfew_active_on_lot_id(self, lot_id): curfew_setting = self._zone_curfew_data.get(lot_id, CurfewService.UNSET) if curfew_setting == CurfewService.UNSET: return False return self.past_curfew(curfew_setting) def apply_curfew_loots(self): for sim_info in services.active_household(): resolver = SingleSimResolver(sim_info) CurfewService.CURFEW_BEGIN_LOOT.apply_to_resolver(resolver) def remove_curfew_loots(self): for sim_info in services.active_household(): resolver = SingleSimResolver(sim_info) CurfewService.CURFEW_END_LOOT.apply_to_resolver(resolver) @classproperty def save_error_code(cls): return persistence_error_types.ErrorCodes.SERVICE_SAVE_FAILED_CURFEW_SERVICE def save(self, object_list=None, zone_data=None, open_street_data=None, store_travel_group_placed_objects=False, save_slot_data=None): persistence_service = services.get_persistence_service() for save_zone_data in persistence_service.zone_proto_buffs_gen(): setting = self._zone_curfew_data.get(save_zone_data.zone_id, CurfewService.UNSET) save_zone_data.gameplay_zone_data.curfew_setting = setting def load(self, zone_data=None): persistence_service = services.get_persistence_service() for zone_data in persistence_service.zone_proto_buffs_gen(): self._zone_curfew_data[ zone_data. zone_id] = zone_data.gameplay_zone_data.curfew_setting def on_zone_load(self): current_zone_id = services.current_zone_id() self._setup_curfew_text_message() self._create_alarm_handles(current_zone_id) venue_manager = services.get_instance_manager( sims4.resources.Types.VENUE) current_venue_tuning = venue_manager.get( build_buy.get_current_venue(current_zone_id)) if current_venue_tuning.is_residential or current_venue_tuning.is_university_housing: current_setting = self._zone_curfew_data.get( current_zone_id, CurfewService.UNSET) self._update_curfew_settings(current_zone_id, current_setting) else: self._update_curfew_settings(current_zone_id, CurfewService.UNSET)
class LocatorManager(Service): SPAWN_TAG_PREFIX = 'spawn_' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._locators = defaultdict(list) self._spawn_point_definitions = set() self._locators_changed_callbacks = CallableList() def __contains__(self, key): if not isinstance(key, int): raise TypeError('LocatorManager keys must be integers.') return key in self._locators def __getitem__(self, key): if not isinstance(key, int): raise TypeError('LocatorManager keys must be integers.') return self._locators[key] def __iter__(self): return iter(self._locators) def __len__(self): return len(self._locators) def __bool__(self): if self._locators: return True return False def get(self, key): return self._locators.get(key, ()) def keys(self): return self._locators.keys() def values(self): return self._locators.values() def items(self): return self._locators.items() def register_locators_changed_callback(self, callback): self._locators_changed_callbacks.append(callback) def unregister_locators_changed_callback(self, callback): if callback in self._locators_changed_callbacks: self._locators_changed_callbacks.remove(callback) def _is_locator_for_spawn_point(self, locator): tags = locator.get_tags() if locator.obj_def_guid in self._spawn_point_definitions: return True if locator.obj_def_guid in self._locators: return False for tag_val in tags: if self.SPAWN_TAG_PREFIX in str( tag.Tag.value_to_name[tag_val]).lower(): self._spawn_point_definitions.add(locator.obj_def_guid) return True return False def set_up_locators(self, locator_data_array): spawn_point_locators = [] for locator_data in locator_data_array: locator = LocatorObject(*locator_data) if self._is_locator_for_spawn_point(locator): spawn_point_locators.append(locator) else: self._locators[locator.obj_def_guid].append(locator) services.current_zone().set_up_world_spawn_points(spawn_point_locators)
class SimInfoBaseWrapper(OutfitTrackerMixin): 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 @property def id(self): return self.sim_id @id.setter def id(self, value): pass def ref(self, callback=None): return weakref.ref(self, protected_callback(callback)) def get_create_op(self, *args, **kwargs): additional_ops = list(self.get_additional_create_ops_gen()) return distributor.ops.SimInfoCreate(self, *args, additional_ops=additional_ops, **kwargs) def get_additional_create_ops_gen(self): pass def get_delete_op(self): return distributor.ops.SimInfoDelete() def get_create_after_objs(self): return () @property def valid_for_distribution(self): return self.manager is not None and self.id is not None @property def icon_info(self): return (self.id, self.manager.id) @staticmethod def copy_base_attributes(sim_info_a, sim_info_b): sim_info_a.age = sim_info_b.age sim_info_a.gender = sim_info_b.gender sim_info_a.extended_species = sim_info_b.extended_species @staticmethod def copy_physical_attributes(sim_info_a, sim_info_b): sim_info_a.physique = sim_info_b.physique sim_info_a.facial_attributes = sim_info_b.facial_attributes sim_info_a.voice_pitch = sim_info_b.voice_pitch sim_info_a.voice_actor = sim_info_b.voice_actor sim_info_a.voice_effect = sim_info_b.voice_effect sim_info_a.skin_tone = sim_info_b.skin_tone sim_info_a.flags = sim_info_b.flags if hasattr(sim_info_a, 'pelt_layers'): if hasattr(sim_info_b, 'pelt_layers'): sim_info_a.pelt_layers = sim_info_b.pelt_layers if hasattr(sim_info_a, 'base_trait_ids'): if hasattr(sim_info_b, 'base_trait_ids'): sim_info_a.base_trait_ids = list(sim_info_b.base_trait_ids) SimInfoBaseWrapper.copy_genetic_data(sim_info_a, sim_info_b) @staticmethod def copy_genetic_data(sim_info_a, sim_info_b): genetic_data_b = sim_info_b.genetic_data if hasattr(genetic_data_b, 'SerializeToString'): genetic_data_b = genetic_data_b.SerializeToString() if hasattr(sim_info_a.genetic_data, 'MergeFromString'): sim_info_a.genetic_data.MergeFromString(genetic_data_b) else: sim_info_a.genetic_data = genetic_data_b def get_icon_info_data(self): return IconInfoData(obj_instance=self) def get_icon_override(self): pass def populate_icon_canvas_texture_info(self, _): pass def populate_localization_token(self, token): token.type = LocalizedStringToken.SIM token.first_name = self.first_name token.last_name = self.last_name token.full_name_key = self.full_name_key token.is_female = self.is_female def save_sim_info(self, sim_info_msg): sim_info_msg.gender = self.gender sim_info_msg.age = self.age sim_info_msg.extended_species = self.extended_species sim_info_msg.physique = self.physique sim_info_msg.facial_attributes = self.facial_attributes sim_info_msg.skin_tone = self.skin_tone sim_info_msg.outfits = self.save_outfits() (current_outfit_type, current_outfit_index) = self.get_current_outfit() sim_info_msg.current_outfit_type = current_outfit_type sim_info_msg.current_outfit_index = current_outfit_index (previous_outfit_type, previous_outfit_index) = self.get_previous_outfit() sim_info_msg.previous_outfit_type = previous_outfit_type sim_info_msg.previous_outfit_index = previous_outfit_index def load_sim_info(self, sim_info_msg): self.gender = sim_info_msg.gender self.age = sim_info_msg.age self.extended_species = sim_info_msg.extended_species self.physique = sim_info_msg.physique self.facial_attributes = sim_info_msg.facial_attributes self.skin_tone = sim_info_msg.skin_tone self.load_outfits(sim_info_msg.outfits) self.set_current_outfit((sim_info_msg.current_outfit_type, sim_info_msg.current_outfit_index)) self._previous_outfit = (sim_info_msg.previous_outfit_type, sim_info_msg.previous_outfit_index) def get_traits(self): return () def set_trait_ids_on_base(self, trait_ids_override=None): trait_ids = trait_ids_override if trait_ids_override is not None else self.trait_ids self._base.base_trait_ids = trait_ids def update_gender_for_traits(self, gender_override=None, trait_ids_override=None): gender = gender_override if gender_override is not None else self.gender trait_ids = trait_ids_override if trait_ids_override is not None else self.trait_ids self._base.update_gender_for_traits(gender, trait_ids) def get_sim_instance(self, *args, **kwargs): pass def _get_trait_ids(self): return list(self._base.base_trait_ids) @distributor.fields.Field(op=distributor.ops.SetTraits, priority=distributor.fields.Field.Priority.LOW) def trait_ids(self): return self._get_trait_ids() resend_trait_ids = trait_ids.get_resend() @property def base_trait_ids(self): return self._base.base_trait_ids @base_trait_ids.setter def base_trait_ids(self, value): self._base.base_trait_ids = value self.resend_trait_ids() def resend_physical_attributes(self): self.resend_skin_tone() self.resend_pelt_layers() self.resend_custom_texture() self.resend_facial_attributes() self.resend_physique() self.resend_outfits() self.resend_voice_pitch() self.resend_voice_actor() self.resend_voice_effect() self.resend_trait_ids() def load_from_resource(self, resource_key, age=None, resend_physical_attributes=True): success = self._base.load_from_resource(resource_key, age) if not success: return if resend_physical_attributes: self.resend_physical_attributes() def apply_genetics(self, parent_a, parent_b, **kwargs): generate_offspring(parent_a._base, parent_b._base, self._base, **kwargs) def add_random_variation_to_modifiers(self, variation_scale=None, resend_physical_attributes=True): if variation_scale is None: variation_scale = random.uniform(-1, 1) if not self._base.add_random_variation_to_modifiers(variation_scale): return if resend_physical_attributes: self.resend_physical_attributes() def generate_club_outfit(self, tag_list=[], outfit_category_and_index=(0, 0), single_or_all_random=0): cas_parts = self._base.generate_club_outfit( tag_list, int(outfit_category_and_index[0]), outfit_category_and_index[1], int(OutfitCategory.SPECIAL), 0, single_or_all_random) return (cas_parts[0], cas_parts[1]) def generate_outfit(self, outfit_category, outfit_index=0, tag_list=(), filter_flag=DEFAULT, **kwargs): filter_flag = OutfitFilterFlag.USE_EXISTING_IF_APPROPRIATE if filter_flag is DEFAULT else filter_flag if not self._base.generate_outfit(outfit_category, outfit_index, tag_list, filter_flag=filter_flag, **kwargs): return False self.resend_outfits() self.on_outfit_generated(outfit_category, outfit_index) self.set_outfit_dirty(outfit_category) return True def generate_merged_outfit(self, source_sim_info, destination_outfit, template_outfit, source_outfit, preserve_outfit_flags=False): if preserve_outfit_flags: source_outfit_data = source_sim_info.get_outfit(*source_outfit) generate_merged_outfit(self._base, source_sim_info._base, destination_outfit[0], destination_outfit[1], template_outfit[0], template_outfit[1], source_outfit[0], source_outfit[1]) if preserve_outfit_flags: self.set_outfit_flags(destination_outfit[0], destination_outfit[1], source_outfit_data.outfit_flags) self.resend_outfits() @contextmanager def set_temporary_outfit_flags(self, outfit_category, outfit_index, outfit_flags): if outfit_flags != DEFAULT: outfit_data = self.get_outfit(outfit_category, outfit_index) restore_flags = outfit_data.outfit_flags self.set_outfit_flags(outfit_category, outfit_index, outfit_flags) try: yield None finally: if outfit_flags != DEFAULT: self.set_outfit_flags(outfit_category, outfit_index, restore_flags) @contextmanager def set_temporary_outfit_flags_on_all_outfits(self, outfit_flags): with ExitStack() as stack: for (outfit_category, outfit_index) in self.get_all_outfit_entries(): stack.enter_context( self.set_temporary_outfit_flags(outfit_category, outfit_index, outfit_flags)) yield None def generate_merged_outfits_for_category( self, source_sim_info, outfit_category, outfit_flags=DEFAULT, fallback_outfit_category=OutfitCategory.EVERYDAY, **kwargs): current_outfit = self.get_current_outfit() for (outfit_index, _) in enumerate( source_sim_info.get_outfits_in_category(outfit_category)): merged_outfit = (outfit_category, outfit_index) if self.has_outfit(merged_outfit): template_outfit = merged_outfit elif self.has_outfit((outfit_category, 0)): template_outfit = (outfit_category, 0) else: template_outfit = current_outfit with source_sim_info.set_temporary_outfit_flags( outfit_category, outfit_index, outfit_flags): self.generate_merged_outfit(source_sim_info, merged_outfit, template_outfit, merged_outfit, **kwargs) while len(self.get_outfits_in_category(outfit_category)) > len( source_sim_info.get_outfits_in_category(outfit_category)): self.remove_outfit(outfit_category) if self.has_outfit(current_outfit): self.set_current_outfit(current_outfit) elif self.has_outfit((outfit_category, 0)): self.set_current_outfit((outfit_category, 0)) elif self.has_outfit((fallback_outfit_category, 0)): self.set_current_outfit((fallback_outfit_category, 0)) elif self.has_outfit((OutfitCategory.BATHING, 0)): self.set_current_outfit((OutfitCategory.BATHING, 0)) def is_duplicate_outfit(self, source_sim_info, template_outfit, source_outfit): return is_duplicate_merged_outfit(self._base, source_sim_info._base, template_outfit[0], template_outfit[1], source_outfit[0], source_outfit[1]) def is_generated_outfit_duplicate_in_category(self, source_sim_info, source_outfit): return any( is_duplicate_merged_outfit(self._base, source_sim_info._base, source_outfit[0], outfit_index, source_outfit[0], source_outfit[1]) for outfit_index in range( len(self.get_outfits_in_category(source_outfit[0])))) def get_outfits(self): return self @distributor.fields.Field(op=distributor.ops.SetSimOutfits) def _client_outfits(self): override = self.appearance_tracker.appearance_override_sim_info if override is not None: return override return self resend_outfits = _client_outfits.get_resend() @distributor.fields.Field(op=distributor.ops.PreloadSimOutfit) def preload_outfit_list(self): return tuple(self._preload_outfit_list) resend_preload_outfit_list = preload_outfit_list.get_resend() def add_preload_outfit(self, outfit): self._preload_outfit_list.append(outfit) self.resend_preload_outfit_list() self.on_preload_outfits_changed() def set_preload_outfits(self, outfit_list): self._preload_outfit_list = list(outfit_list) self.resend_preload_outfit_list() self.on_preload_outfits_changed() @distributor.fields.Field(op=distributor.ops.ChangeSimOutfit, priority=distributor.fields.Field.Priority.LOW) def _current_outfit(self): return self._base.outfit_type_and_index resend_current_outfit = _current_outfit.get_resend() @_current_outfit.setter def _current_outfit(self, value): self._set_current_outfit_without_distribution(value) def _set_current_outfit_without_distribution(self, value): self._base.outfit_type_and_index = value self.on_outfit_changed(self, value) def get_current_outfit(self): return self._current_outfit def get_previous_outfit(self): return self._previous_outfit def set_current_outfit(self, outfit_category_and_index) -> bool: if self._current_outfit == outfit_category_and_index and not self.outfit_is_dirty( self._current_outfit[0]): return False if not self.has_outfit(outfit_category_and_index): outfit_category_and_index = ( self.occult_tracker.get_fallback_outfit_category( self.current_occult_types), 0) logger.warn('Trying to set outfit to a possible occult sim.', owner='amohananey') if not self.has_outfit(outfit_category_and_index): error_msg = 'Trying to set outfit on Sim missing it. Sim: {}, Category: {}, Index: {}, {}' error_args = [ self, outfit_category_and_index[0], outfit_category_and_index[1], StackVar(('interaction', )) ] career_tracker = getattr(self, 'career_tracker', None) if career_tracker is None: error_msg += '\n\tNo career tracker' else: error_msg += '\n\tCareers:' for career in career_tracker.careers.values(): error_msg += '\n\t\t{}: {}' error_args.append(str(career)) error_args.append(str(career.current_level_tuning)) logger.callstack(error_msg, *error_args, level=sims4.log.LEVEL_ERROR, owner='tingyul') self.set_previous_outfit(new_outfit=outfit_category_and_index) self._current_outfit = outfit_category_and_index self.clear_outfit_dirty(self._current_outfit[0]) return True def set_current_outfit_for_category(self, outfit_category): number_of_outfits = self.get_number_of_outfits_in_category( outfit_category) outfit_index = random.randrange(number_of_outfits) self.set_current_outfit((outfit_category, outfit_index)) def set_previous_outfit(self, new_outfit, force=False): if not force and new_outfit == self._current_outfit: return self._previous_outfit = self._current_outfit def register_for_outfit_changed_callback(self, callback): if callback not in self.on_outfit_changed: self.on_outfit_changed.append(callback) def unregister_for_outfit_changed_callback(self, callback): if callback in self.on_outfit_changed: self.on_outfit_changed.remove(callback) @property def is_male(self): return Gender(self._base.gender) == Gender.MALE @property def is_female(self): return Gender(self._base.gender) == Gender.FEMALE @distributor.fields.Field(op=distributor.ops.SetFirstName) def first_name(self): return self._base.first_name @first_name.setter def first_name(self, value): if self._base.first_name != value: self._base.first_name = value @distributor.fields.Field(op=distributor.ops.SetLastName) def last_name(self): return self._base.last_name @last_name.setter def last_name(self, value): if self._base.last_name != value: self._base.last_name = value @property def first_name_key(self): return self._base.first_name_key @first_name_key.setter def first_name_key(self, value): if self._base.first_name_key != value: self._base.first_name_key = value @property def last_name_key(self): return self._base.last_name_key @last_name_key.setter def last_name_key(self, value): if self._base.last_name_key != value: self._base.last_name_key = value @distributor.fields.Field(op=distributor.ops.SetFullNameKey) def full_name_key(self): return self._base.full_name_key @full_name_key.setter def full_name_key(self, value): if self._base.full_name_key != value: self._base.full_name_key = value @distributor.fields.Field(op=distributor.ops.SetBreedName) def breed_name(self): return self._base.breed_name @breed_name.setter def breed_name(self, value): if self._base.breed_name != value: self._base.breed_name = value @distributor.fields.Field(op=distributor.ops.SetBreedNameKey) def breed_name_key(self): return self._base.breed_name_key @breed_name_key.setter def breed_name_key(self, value): if self._base.breed_name_key != value: self._base.breed_name_key = value @property def full_name(self): return '' @distributor.fields.Field(op=distributor.ops.SetGender, priority=distributor.fields.Field.Priority.HIGH) def gender(self): return Gender(self._base.gender) @gender.setter def gender(self, value): self._base.gender = Gender(value) self.on_base_characteristic_changed() @distributor.fields.Field(op=distributor.ops.SetAge, priority=distributor.fields.Field.Priority.HIGH) def age(self): return Age(self._base.age) @age.setter def age(self, value): self._base.age = value resend_age = age.get_resend() def apply_age(self, age): self._base.update_for_age(age) self.age = age self.resend_physical_attributes() @property def species(self): return self._species @species.setter def species(self, value): self.extended_species = value @distributor.fields.Field(op=distributor.ops.SetSpecies, priority=distributor.fields.Field.Priority.HIGH) def extended_species(self): return self._base.species @extended_species.setter def extended_species(self, value): self._base.species = value self._species = SpeciesExtended.get_species(value) self.on_base_characteristic_changed() resend_extended_species = extended_species.get_resend() @property def is_human(self): return self.species == Species.HUMAN @property def is_pet(self): return self.species != Species.HUMAN @property def is_baby(self): return self.age == Age.BABY @property def is_toddler(self): return self.age == Age.TODDLER @property def rig_key(self): return self._base.rig_key @distributor.fields.Field(op=distributor.ops.SetSkinTone, priority=distributor.fields.Field.Priority.HIGH) def skin_tone(self): override = self.appearance_tracker.appearance_override_sim_info if override is not None: return override._base.skin_tone return self._base.skin_tone _resend_skin_tone = skin_tone.get_resend() def resend_skin_tone(self, *args, **kwargs): self._resend_skin_tone(*args, **kwargs) if self.is_baby: self.resend_baby_skin_tone(*args, **kwargs) @skin_tone.setter def skin_tone(self, value): self._base.skin_tone = value if self.is_baby: self.resend_baby_skin_tone() @distributor.fields.Field(op=distributor.ops.SetBabySkinTone) def baby_skin_tone(self): return BabyTuning.get_baby_skin_tone_enum(self) resend_baby_skin_tone = baby_skin_tone.get_resend() @distributor.fields.Field(op=distributor.ops.SetPeltLayers) def pelt_layers(self): return self._base.pelt_layers resend_pelt_layers = pelt_layers.get_resend() @pelt_layers.setter def pelt_layers(self, value): self._base.pelt_layers = value @distributor.fields.Field(op=distributor.ops.SetCustomTexture) def custom_texture(self): return self._base.custom_texture resend_custom_texture = custom_texture.get_resend() @custom_texture.setter def custom_texture(self, value): self._base.custom_texture = value @distributor.fields.Field(op=distributor.ops.SetVoicePitch) def voice_pitch(self): return self._base.voice_pitch resend_voice_pitch = voice_pitch.get_resend() @voice_pitch.setter def voice_pitch(self, value): self._base.voice_pitch = value @distributor.fields.Field(op=distributor.ops.SetVoiceActor) def voice_actor(self): return self._base.voice_actor resend_voice_actor = voice_actor.get_resend() @voice_actor.setter def voice_actor(self, value): self._base.voice_actor = value @distributor.fields.Field(op=distributor.ops.SetVoiceEffect) def voice_effect(self): return self._base.voice_effect @voice_effect.setter def voice_effect(self, value): if value is None: value = 0 self._base.voice_effect = value resend_voice_effect = voice_effect.get_resend() @distributor.fields.Field(op=distributor.ops.SetPhysique) def physique(self): return self._base.physique resend_physique = physique.get_resend() @physique.setter def physique(self, value): self._base.physique = value @distributor.fields.Field(op=distributor.ops.SetFacialAttributes) def facial_attributes(self): return self._base.facial_attributes @facial_attributes.setter def facial_attributes(self, value): self._base.facial_attributes = value resend_facial_attributes = facial_attributes.get_resend() @distributor.fields.Field(op=distributor.ops.SetGeneticData) def genetic_data(self): return self._base.genetic_data @genetic_data.setter def genetic_data(self, value): self._base.genetic_data = value resend_genetic_data = genetic_data.get_resend() @property def flags(self): return self._base.flags @flags.setter def flags(self, value): self._base.flags = value
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
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
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 RoutingComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=types.ROUTING_COMPONENT): _pathplan_context = None FACTORY_TUNABLES = {'plan_context_data': TunableTuple(description='\n Data used to populate fields on the path plan context.\n ', default_context=PathPlanContextWrapper.TunableFactory(description="\n If no age override is specified, the default path plan data to\n use for this agent's path planning.\n "), context_age_species_overrides=TunableList(description='\n List of age-species path plan context overrides for a specific\n routing agent.\n ', tunable=TunableTuple(description='\n Overrides to the path plan context of the agent defined by a\n combination of age and species.\n ', age=TunableEnumEntry(description='\n The age this override applies to.\n ', tunable_type=Age, default=Age.ADULT), species=TunableEnumEntry(description='\n The species this override applies to.\n ', tunable_type=SpeciesExtended, default=SpeciesExtended.HUMAN, invalid_enums=(SpeciesExtended.INVALID,)), context_override=PathPlanContextWrapper.TunableFactory()))), 'walkstyle_behavior': WalksStyleBehavior.TunableFactory(description='\n Define the walkstyle behavior for owners of this component.\n '), 'object_routing_component': OptionalTunable(description='\n If enabled, this object will have an Object Routing component, which\n controls an object routing behavior based on triggered states.\n ', tunable=ObjectRoutingComponent.TunableFactory())} 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 get_subcomponents_gen(self): yield from super().get_subcomponents_gen() if self.object_routing_component is not None: object_routing_component = self.object_routing_component(self.owner) yield from object_routing_component.get_subcomponents_gen() @property def current_path(self): return self._current_path @property def object_radius(self): return self._pathplan_context.agent_radius @property def route_event_context(self): return self._route_event_context @componentmethod def register_routing_stage_event(self, routing_stage_event, callback): self._routing_stage_event_callbacks[routing_stage_event].register(callback) def _on_routing_stage_event(self, routing_stage_event, **kwargs): callbacks = self._routing_stage_event_callbacks.get(routing_stage_event) if callbacks is not None: callbacks(self.owner, routing_stage_event, **kwargs) @contextmanager def temporary_walkstyle_request(self, walkstyle_request_factory): try: request = walkstyle_request_factory(self.owner) request.start() yield None finally: request.stop() @componentmethod def unregister_routing_stage_event(self, routing_stage_event, callback): self._routing_stage_event_callbacks[routing_stage_event].unregister(callback) @componentmethod def get_walkstyle_behavior(self): return self.walkstyle_behavior @componentmethod def get_cost_valid_walkstyle(self, cost_tuning): for walkstyle in self._walkstyle_requests: if walkstyle.priority != WalkStylePriority.COMBO: walkstyle_cost = cost_tuning.get(walkstyle.walkstyle, None) if walkstyle_cost is None: return walkstyle.walkstyle current_value = self.owner.get_stat_value(walkstyle_cost.walkstyle_cost_statistic) if current_value - walkstyle_cost.cost > walkstyle_cost.walkstyle_cost_statistic.min_value: return walkstyle.walkstyle walkstyle_behavior = self.get_walkstyle_behavior() return walkstyle_behavior.default_walkstyle @componentmethod def get_default_walkstyle(self): return self.walkstyle_behavior.get_default_walkstyle(self.owner) @componentmethod def get_walkstyle(self): for walkstyle in self._walkstyle_requests: if walkstyle.priority != WalkStylePriority.COMBO: return walkstyle.walkstyle walkstyle_behavior = self.get_walkstyle_behavior() return walkstyle_behavior.default_walkstyle @componentmethod def get_walkstyle_for_path(self, path): return self.walkstyle_behavior.get_walkstyle_for_path(self.owner, path) @componentmethod def get_walkstyle_list(self): return tuple(request.walkstyle for request in self._walkstyle_requests) @componentmethod def get_walkstyle_requests(self): return self._walkstyle_requests def _get_walkstyle_key(self): if self.owner.is_sim: return (self.owner.age, self.owner.gender, self.owner.extended_species) return (Age.ADULT, Gender.MALE, SpeciesExtended.HUMAN) @componentmethod def request_walkstyle(self, walkstyle_request, uid): if walkstyle_request.priority != WalkStylePriority.COMBO and not has_walkstyle_info(walkstyle_request.walkstyle, *self._get_walkstyle_key()): return self._walkstyle_requests.append(walkstyle_request) self._walkstyle_requests.sort(reverse=True, key=operator.attrgetter('priority')) self._walk_style_handles[uid] = RegistryHandle(lambda : self._unrequest_walkstyle(walkstyle_request)) self._update_walkstyle() @componentmethod def remove_walkstyle(self, uid): if uid in self._walk_style_handles: self._walk_style_handles[uid].release() del self._walk_style_handles[uid] def _unrequest_walkstyle(self, walkstyle_request): self._walkstyle_requests.remove(walkstyle_request) self._update_walkstyle() def _update_walkstyle(self): for primitive in self.owner.primitives: try: primitive.request_walkstyle_update() except AttributeError: pass @componentmethod def get_additional_scoring_for_surface(self, surface_type): return self._pathplan_context.surface_preference_scoring.get(surface_type, 0) @componentmethod def add_location_to_quadtree(self, *args, **kwargs): path_plan_context = self._pathplan_context return path_plan_context.add_location_to_quadtree(*args, **kwargs) @componentmethod def remove_location_from_quadtree(self, *args, **kwargs): path_plan_context = self._pathplan_context return path_plan_context.remove_location_from_quadtree(*args, **kwargs) @property def _pathplan_context(self): age_key = getattr(self.owner, 'age', DEFAULT) species_key = getattr(self.owner, 'extended_species', DEFAULT) combined_override_key = (age_key, species_key) if combined_override_key in self._path_plan_context_map: return self._path_plan_context_map[combined_override_key] for override in self.plan_context_data.context_age_species_overrides: if override.age == age_key: if override.species == species_key: self._path_plan_context_map[combined_override_key] = override.context_override(self.owner) return self._path_plan_context_map[combined_override_key] self._path_plan_context_map.clear() self._path_plan_context_map[combined_override_key] = self.plan_context_data.default_context(self.owner) return self._path_plan_context_map[combined_override_key] @componentmethod def get_routing_context(self): return self.pathplan_context def on_sim_added(self): self._update_quadtree_location() def on_sim_removed(self): self.on_slot = None self._routing_master_ref = None self.clear_routing_slaves() def on_reset_internal_state(self, reset_reason): self.clear_routing_slaves() def add_callbacks(self): self.owner.register_on_location_changed(self._update_quadtree_location) self.owner.register_on_location_changed(self._check_violations) if self.get_walkstyle_behavior().supports_wading_walkstyle_buff(self.owner): self.owner.register_on_location_changed(self.get_walkstyle_behavior().check_for_wading) self.on_plan_path.append(self._on_update_goals) self.on_intended_location_changed.append(self.owner.refresh_los_constraint) self.on_intended_location_changed.append(self.owner._update_social_geometry_on_location_changed) self.on_intended_location_changed.append(lambda *_, **__: self.owner.two_person_social_transforms.clear()) self.on_intended_location_changed.append(self.owner.update_intended_position_on_active_lot) def remove_callbacks(self): self.on_intended_location_changed.clear() if self._on_update_goals in self.on_plan_path: self.on_plan_path.remove(self._on_update_goals) if self.owner._on_location_changed_callbacks is not None and self._check_violations in self.owner._on_location_changed_callbacks: self.owner.unregister_on_location_changed(self._check_violations) if self.owner._on_location_changed_callbacks is not None and self._update_quadtree_location in self.owner._on_location_changed_callbacks: self.owner.unregister_on_location_changed(self._update_quadtree_location) @property def pathplan_context(self): return self._pathplan_context def get_or_create_routing_context(self): return self._pathplan_context @property def routing_context(self): return self._pathplan_context @property def connectivity_handles(self): return self._pathplan_context.connectivity_handles @property def is_moving(self): if self.owner.is_sim: return not self.owner.location.almost_equal(self.owner.intended_location) else: return self.current_path is not None @property def routing_master(self): if self._routing_master_ref is not None: return self._routing_master_ref() @routing_master.setter def routing_master(self, value): self._routing_master_ref = value.ref() if value is not None else None @property def route_interaction(self): return self._route_interaction @property def animation_context(self): if self._route_interaction is not None: return self._route_interaction.animation_context return self._animation_context SLAVE_RADIUS_MODIFIER = 0.5 MAX_ALLOWED_AGENT_RADIUS = 0.25 def _update_agent_radius(self): agent_radius_datas = [slave_data for slave_data in self._routing_slave_data if slave_data.should_increase_master_agent_radius] if not agent_radius_datas: return max_x = max(abs(slave_data.offset[0]) for slave_data in agent_radius_datas) max_x = max(max_x*self.SLAVE_RADIUS_MODIFIER, self._default_agent_radius) self._pathplan_context.agent_radius = min(self.MAX_ALLOWED_AGENT_RADIUS, max_x) def add_routing_slave(self, slave_data): if len(self._routing_slave_data) == 0: self._default_agent_radius = self._pathplan_context.agent_radius slave_data.slave.routing_master = self.owner self._routing_slave_data.append(slave_data) slave_data.on_add() self._update_agent_radius() def clear_routing_slaves(self): for slave_data in self._routing_slave_data: slave_data.slave.routing_master = None slave_data.on_release() self._routing_slave_data.clear() self._restore_agent_radius() self._update_agent_radius() @componentmethod def get_routing_slave_data(self): return self._routing_slave_data @componentmethod def get_formation_data_for_slave(self, obj): for slave_data in self._routing_slave_data: if slave_data.slave is obj: return slave_data @componentmethod def get_all_routing_slave_data_gen(self): yield from self._routing_slave_data @componentmethod def get_routing_slave_data_count(self, formation_type): return sum(1 for slave_data in self._routing_slave_data if slave_data.formation_type is formation_type) def clear_slave(self, slave): for slave_data in self._routing_slave_data: if slave_data.slave is slave: self._routing_slave_data.remove(slave_data) slave_data.on_release() break self._restore_agent_radius() slave.routing_master = None self._update_agent_radius() @componentmethod def write_slave_data_msg(self, route_msg, path=None): transitioning_sims = () actor = self.owner if actor.is_sim: if actor.transition_controller is not None: transitioning_sims = actor.transition_controller.get_transitioning_sims() for slave_data in self.get_routing_slave_data(): if slave_data.should_slave_for_path(path): if slave_data.slave in transitioning_sims: continue (slave_actor, slave_msg) = slave_data.add_routing_slave_to_pb(route_msg, path=path) slave_actor.write_slave_data_msg(slave_msg, path=path) for slave_actor in actor.children: if not slave_actor.is_sim: continue carry_walkstyle_behavior = slave_actor.get_walkstyle_behavior().carry_walkstyle_behavior if carry_walkstyle_behavior is None: continue with ProtocolBufferRollback(route_msg.slaves) as slave_msg: slave_msg.id = slave_actor.id slave_msg.type = Routing_pb2.SlaveData.SLAVE_PAIRED_CHILD walkstyle_override_msg = slave_msg.walkstyle_overrides.add() walkstyle_override_msg.from_walkstyle = 0 walkstyle_override_msg.to_walkstyle = carry_walkstyle_behavior.default_carry_walkstyle for (walkstyle, carry_walkstyle) in carry_walkstyle_behavior.carry_walkstyle_overrides.items(): walkstyle_override_msg = slave_msg.walkstyle_overrides.add() walkstyle_override_msg.from_walkstyle = walkstyle walkstyle_override_msg.to_walkstyle = carry_walkstyle slave_actor.write_slave_data_msg(slave_msg, path=path) def _restore_agent_radius(self): if self._default_agent_radius is not None: if len(self._routing_slave_data) == 0: self._pathplan_context.agent_radius = self._default_agent_radius self._default_agent_radius = None def contains_slave(self, slave): return any(slave.id == slave_data.slave.id for slave_data in self._routing_slave_data) def _on_update_goals(self, goal_list, starting): NUM_GOALS_TO_RESERVE = 2 for (index, goal) in enumerate(goal_list, start=1): if index > NUM_GOALS_TO_RESERVE: break if starting: self.add_location_to_quadtree(placement.ItemType.SIM_INTENDED_POSITION, position=goal.position, orientation=goal.orientation, routing_surface=goal.routing_surface_id, index=index) else: self.remove_location_from_quadtree(placement.ItemType.SIM_INTENDED_POSITION, index=index) def set_portal_mask_flag(self, flag): self._pathplan_context.set_portal_key_mask(self._pathplan_context.get_portal_key_mask() | flag) def clear_portal_mask_flag(self, flag): self._pathplan_context.set_portal_key_mask(self._pathplan_context.get_portal_key_mask() & ~flag) def set_portal_discouragement_mask_flag(self, flag): self._pathplan_context.set_portal_discourage_key_mask(self._pathplan_context.get_portal_discourage_key_mask() | flag) def clear_portal_discouragement_mask_flag(self, flag): self._pathplan_context.set_portal_discourage_key_mask(self._pathplan_context.get_portal_discourage_key_mask() & ~flag) @componentmethod def update_portal_locks(self): sim = self.owner sim.manager.add_portal_lock(sim, sim._portal_added_callback) def _update_quadtree_location(self, *_, **__): self.add_location_to_quadtree(placement.ItemType.SIM_POSITION) def add_stand_slot_reservation(self, interaction, position, routing_surface, excluded_sims): interaction.add_liability(STAND_SLOT_LIABILITY, StandSlotReservationLiability(self.owner, interaction)) excluded_sims.add(self.owner) self._stand_slot_reservation = position self.add_location_to_quadtree(placement.ItemType.ROUTE_GOAL_SUPPRESSOR, position=position, routing_surface=routing_surface) pathplan_context = self._pathplan_context reservation_radius = pathplan_context.agent_radius*2 polygon = sims4.geometry.generate_circle_constraint(6, position, reservation_radius) self.on_slot = (position, polygon, routing_surface) UserFootprintHelper.force_move_sims_in_polygon(polygon, routing_surface, exclude=excluded_sims) def remove_stand_slot_reservation(self, interaction): self.remove_location_from_quadtree(placement.ItemType.ROUTE_GOAL_SUPPRESSOR) self.on_slot = None self.stand_slot_reservation_removed_callbacks(sim=self) def get_stand_slot_reservation_violators(self, excluded_sims=()): if not self.on_slot: return (_, polygon, routing_surface) = self.on_slot violators = [] excluded_sims = {sim for sim in itertools.chain((self.owner,), excluded_sims)} for sim_nearby in placement.get_nearby_sims_gen(polygon.centroid(), routing_surface, radius=polygon.radius(), exclude=excluded_sims): if sims4.geometry.test_point_in_polygon(sim_nearby.position, polygon): if not sim_nearby.ignore_blocking_near_destination: violators.append(sim_nearby) return violators def _check_violations(self, *_, **__): if services.privacy_service().check_for_late_violators(self.owner): return for reaction_trigger in self.owner.reaction_triggers.values(): reaction_trigger.intersect_and_execute(self.owner) def create_route_interaction(self): if self.owner.is_sim: aop = AffordanceObjectPair(AnimationInteraction, None, AnimationInteraction, None, hide_unrelated_held_props=False) context = InteractionContext(self.owner, InteractionContext.SOURCE_SCRIPT, Priority.High) self._route_interaction = aop.interaction_factory(context).interaction else: self._animation_context = AnimationContext() self._animation_context.add_ref(self._current_path) for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.create_route_interaction() def cancel_route_interaction(self): if self._route_interaction is not None: self._route_interaction.cancel(FinishingType.AUTO_EXIT, 'Route Ended.') self._route_interaction.on_removed_from_queue() self._route_interaction = None if self._animation_context is not None: self._animation_context.release_ref(self._current_path) self._animation_context = None for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.cancel_route_interaction() def set_follow_path(self, follow_path): self._active_follow_path_weakref = weakref.ref(follow_path) def clear_follow_path(self): self._active_follow_path_weakref = None def _get_active_follow_path(self): if self._active_follow_path_weakref is not None: return self._active_follow_path_weakref() def get_approximate_cancel_location(self): follow_path = self._get_active_follow_path() if follow_path is not None: ret = follow_path.get_approximate_cancel_location() if ret is not None: return ret return (self.owner.intended_transform, self.owner.intended_routing_surface) @componentmethod def set_routing_path(self, path): if path is None: if gsi_handlers.route_event_handlers.archiver.enabled: gsi_handlers.route_event_handlers.archive_route_events(self._current_path, self.owner, gsi_handlers.route_event_handlers.PATH_TYPE_FINISHED, clear=True) self._on_routing_stage_event(RoutingStageEvent.ROUTE_END, path=self._current_path) self.cancel_route_interaction() self._current_path = None if self.owner.is_sim and self.owner.transition_controller is not None and self._initial_carry_targets != self.owner.posture_state.carry_targets: self.owner.transition_controller.derail(DerailReason.CONSTRAINTS_CHANGED, self.owner) self._initial_carry_targets = None return if self.owner.is_sim: self._initial_carry_targets = self.owner.posture_state.carry_targets self._current_path = path if self.pathplan_context.disable_fake_portals: self._current_path.remove_fake_portals() self.create_route_interaction() self._on_routing_stage_event(RoutingStageEvent.ROUTE_START, path=path) walkstyle = self.walkstyle_behavior.apply_walkstyle_to_path(self.owner, self._current_path) origin_q = (float(self.owner.orientation.x), float(self.owner.orientation.y), float(self.owner.orientation.z), float(self.owner.orientation.w)) origin_t = (float(self.owner.position.x), float(self.owner.position.y), float(self.owner.position.z)) (age, gender, species) = self._get_walkstyle_key() self._current_path.nodes.apply_initial_timing(origin_q, origin_t, walkstyle, age, gender, species, int(services.time_service().sim_now), services.current_zone_id()) @componentmethod def update_routing_path(self, time_offset): if self._current_path is None: return walkstyle = self.walkstyle_behavior.apply_walkstyle_to_path(self.owner, self._current_path, time_offset=time_offset) (age, gender, species) = self._get_walkstyle_key() self._current_path.nodes.update_timing(walkstyle, age, gender, species, time_offset, services.current_zone_id()) @componentmethod def update_slave_positions_for_path(self, path, transform, orientation, routing_surface, distribute=True, canceled=False): transitioning_sims = () if self.owner.is_sim: if self.owner.transition_controller is not None: transitioning_sims = self.owner.transition_controller.get_transitioning_sims() for slave_data in self.get_routing_slave_data(): if slave_data.slave in transitioning_sims: continue slave_data.update_slave_position(transform, orientation, routing_surface, distribute=distribute, path=path, canceled=canceled) def add_route_event_provider(self, request): if self._route_event_provider_requests is None: self._route_event_provider_requests = [] self._route_event_provider_requests.append(request) def remove_route_event_provider(self, request): if self._route_event_provider_requests is not None and request in self._route_event_provider_requests: self._route_event_provider_requests.remove(request) if not self._route_event_provider_requests: self._route_event_provider_requests = None def route_event_executed(self, event_id): if self._route_event_context is None: return self._route_event_context.handle_route_event_executed(event_id, self.owner, path=self._current_path) def route_event_skipped(self, event_id): if self._route_event_context is None: return self._route_event_context.handle_route_event_skipped(event_id, self.owner, path=self._current_path) def remove_route_event_by_data(self, event_data): if self._route_event_context is None: return self._route_event_context.remove_route_event_by_data(event_data) @componentmethod def route_finished(self, path_id): for primitive in self.owner.primitives: if hasattr(primitive, 'route_finished'): primitive.route_finished(path_id) @componentmethod def route_time_update(self, path_id, current_time): for primitive in self.owner.primitives: if hasattr(primitive, 'route_time_update'): primitive.route_time_update(path_id, current_time) def _gather_route_events(self, path, **kwargs): owner = self.owner if owner.is_sim: interaction = owner.transition_controller.interaction if owner.transition_controller is not None else None if interaction is not None and interaction.is_super: interaction.provide_route_events(self._route_event_context, owner, path, **kwargs) owner.Buffs.provide_route_events_from_buffs(self._route_event_context, owner, path, **kwargs) broadcaster_service = services.current_zone().broadcaster_service broadcaster_service.provide_route_events(self._route_event_context, owner, path, **kwargs) if owner.weather_aware_component is not None: owner.weather_aware_component.provide_route_events(self._route_event_context, owner, path, **kwargs) if self._route_event_provider_requests is not None: for request in self._route_event_provider_requests: request.provide_route_events(self._route_event_context, owner, path, **kwargs) object_manager = services.object_manager(owner.zone_id) if object_manager is not None: for node in path.nodes: if node.portal_object_id != 0: portal_object = object_manager.get(node.portal_object_id) if portal_object is not None: portal_object.provide_route_events(node.portal_id, self._route_event_context, owner, path, node=node, **kwargs) def clear_route_events(self, *args, **kwargs): if self._route_event_context is None: return self._route_event_context.clear_route_events() for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.clear_route_events() def schedule_and_process_route_events_for_new_path(self, path): if self._route_event_context is None: return if self.owner.is_sim and not self.owner.posture.mobile: return self.clear_route_events() start_time = RouteEventContext.ROUTE_TRIM_START end_time = min(start_time + ROUTE_EVENT_WINDOW_DURATION, path.duration()) self._gather_route_events(path, start_time=start_time, end_time=end_time) self._route_event_context.schedule_route_events(self.owner, path) self._route_event_context.process_route_events(self.owner) for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.schedule_and_process_route_events_for_new_path(path) if gsi_handlers.route_event_handlers.archiver.enabled: gsi_handlers.route_event_handlers.archive_route_events(path, self.owner, gsi_handlers.route_event_handlers.PATH_TYPE_INITIAL) def append_route_events_to_route_msg(self, route_msg): if self._route_event_context is None: return self._route_event_context.append_route_events_to_route_msg(route_msg) for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.append_route_events_to_route_msg(route_msg) def update_route_events_for_current_path(self, path, current_time, time_offset): (failed_events, failed_types) = self._route_event_context.prune_stale_events_and_get_failed_types(self.owner, path, current_time) start_time = current_time window_duration = ROUTE_EVENT_WINDOW_DURATION if time_offset < 0: window_duration -= time_offset end_time = min(start_time + window_duration, path.duration()) self._gather_route_events(path, failed_types=failed_types, start_time=start_time, end_time=end_time) self._route_event_context.schedule_route_events(self.owner, path, start_time=start_time) should_update = True if failed_events else False or self._route_event_context.has_pending_events_to_process() for slave_data in self.get_routing_slave_data(): should_update |= slave_data.slave.routing_component.update_route_events_for_current_path(path, current_time, time_offset) if gsi_handlers.route_event_handlers.archiver.enabled and gsi_handlers.route_event_handlers.update_log_enabled: gsi_handlers.route_event_handlers.archive_route_events(path, self.owner, gsi_handlers.route_event_handlers.PATH_TYPE_UPDATE) return should_update def process_updated_route_events(self): self._route_event_context.process_route_events(self.owner) for slave_data in self.get_routing_slave_data(): slave_data.slave.routing_component.process_updated_route_events() @componentmethod def should_route_instantly(self): zone = services.current_zone() if zone.force_route_instantly: return True if self.owner.is_sim: if not (zone.are_sims_hitting_their_marks and self.owner._allow_route_instantly_when_hitting_marks): return False else: return not services.sim_spawner_service().sim_is_leaving(self.owner) return False
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
class ClientObjectMixin: INITIAL_DEPRECIATION = TunablePercent( 20, description= 'Amount (0%%-100%%) of depreciation to apply to an object after purchase. An item worth 10 in the catalog if tuned at 20%% will be worth 8 after purchase.' ) FADE_DURATION = TunableSimMinute( 1.2, description='Default fade time (in sim minutes) for objects.') VISIBLE_TO_AUTOMATION = True _get_next_ui_metadata_handle = uid.UniqueIdGenerator(min_uid=1) HOVERTIP_HANDLE = 0 ui_metadata = distributor.sparse.SparseField( ui_protocols.UiObjectMetadata, distributor.ops.SetUiObjectMetadata) _generic_ui_metadata_setters = {} FORWARD_OFFSET = 0.04 def __init__(self, definition, **kwargs): super().__init__(definition, **kwargs) if definition is not None: self.apply_definition(definition, **kwargs) self._ui_metadata_stack = None self._ui_metadata_handles = None self._ui_metadata_cache = None self.primitives = distributor.ops.DistributionSet(self) zone_id = services.current_zone_id() self._location = sims4.math.Location( sims4.math.Transform(), routing.SurfaceIdentifier(zone_id, 0, routing.SurfaceType.SURFACETYPE_WORLD)) self._children_objects = None self._scale = 1 self._parent_type = ObjectParentType.PARENT_NONE self._parent_location = 0 self._build_buy_lockout = False self._build_buy_lockout_alarm_handler = None self._tint = None self._opacity = None self._censor_state = None self._geometry_state = None self._geometry_state_overrides = None self._standin_model = None self._visibility = None self._visibility_flags = None self._material_state = None self._reference_arb = None self._audio_effects = None self._video_playlist = None self._painting_state = None self.custom_name = None self.custom_description = None self._multicolor = None self._display_number = None self._awareness_scores = None self._scratched = False self._base_value = definition.price self._needs_post_bb_fixup = False self._needs_depreciation = False self._swapping_to_parent = None self._swapping_from_parent = None self._on_children_changed = None self.allow_opacity_change = True self._wind_speed_effect = None def get_create_op(self, *args, **kwargs): additional_ops = list(self.get_additional_create_ops_gen()) return distributor.ops.ObjectCreate(self, *args, additional_ops=additional_ops, **kwargs) @forward_to_components_gen def get_additional_create_ops_gen(self): pass def get_create_after_objs(self): parent = self.parent_object(child_type=ChildrenType.BB_ONLY) if parent is not None: return (parent, ) return () def get_delete_op(self, fade_duration=0): return distributor.ops.ObjectDelete(fade_duration=fade_duration) @forward_to_components def apply_definition(self, definition, obj_state=0): if not isinstance(definition, objects.definition.Definition): definition = services.definition_manager().get(definition) self._model = definition.get_model(obj_state) self._material_variant = definition.material_variant self._rig = definition.get_rig(obj_state) self._slot = definition.get_slot(obj_state) self._slots_resource = definition.get_slots_resource(obj_state) self._state_index = obj_state def set_definition(self, definition_id, ignore_rig_footprint=False): new_definition = services.definition_manager().get(definition_id) (result, error) = self.definition.is_similar( new_definition, ignore_rig_footprint=ignore_rig_footprint) if not result: logger.error( 'Trying to set the definition {} to an incompatible definition {}.\n {}', self.definition.id, definition_id, error, owner='nbaker') return False services.definition_manager().unregister_definition( self.definition.id, self) self.apply_definition(new_definition, self._state_index) self.definition = new_definition services.definition_manager().register_definition( new_definition.id, self) self.resend_model_with_material_variant() self.resend_slot() self.resend_state_index() op = distributor.ops.SetObjectDefinitionId(definition_id) distributor.system.Distributor.instance().add_op(self, op) return True @property def hover_tip(self): if self._ui_metadata_stack is None or ClientObjectMixin.HOVERTIP_HANDLE not in self._ui_metadata_handles: return (_, _, value) = self._ui_metadata_handles[ClientObjectMixin.HOVERTIP_HANDLE] return value @hover_tip.setter def hover_tip(self, value): if value is not None: if self._ui_metadata_stack is None: self._ui_metadata_stack = [] self._ui_metadata_handles = {} self._ui_metadata_cache = {} data = self._ui_metadata_handles.get( ClientObjectMixin.HOVERTIP_HANDLE) if data is not None: self._ui_metadata_stack.remove(data) data = (ClientObjectMixin.HOVERTIP_HANDLE, 'hover_tip', value) self._ui_metadata_stack.append(data) self._ui_metadata_handles[ClientObjectMixin.HOVERTIP_HANDLE] = data def add_ui_metadata(self, name, value): if self._ui_metadata_stack is None: self._ui_metadata_stack = [] self._ui_metadata_handles = {} self._ui_metadata_cache = {} if name not in self._ui_metadata_cache: default_value = type(self).ui_metadata.generic_getter(name)(self) self._ui_metadata_cache[name] = default_value handle = self._get_next_ui_metadata_handle() data = (handle, name, value) self._ui_metadata_stack.append(data) self._ui_metadata_handles[handle] = data return handle def get_ui_metadata(self, handle): return self._ui_metadata_handles[handle] def remove_ui_metadata(self, handle): if self._ui_metadata_stack is not None: self._ui_metadata_stack.remove(self._ui_metadata_handles[handle]) def update_ui_metadata(self, use_cache=True): if self._ui_metadata_stack is None: return ui_metadata = {} for (_, name, value) in self._ui_metadata_stack: ui_metadata[name] = value for (name, value) in ui_metadata.items(): if name in self._ui_metadata_cache and self._ui_metadata_cache[ name] == value and use_cache: continue if name in self._generic_ui_metadata_setters: setter = self._generic_ui_metadata_setters[name] else: setter = type(self).ui_metadata.generic_setter(name, auto_reset=True) self._generic_ui_metadata_setters[name] = setter try: setter(self, value) except (ValueError, TypeError): logger.error( 'Error trying to set field {} to value {} in object {}.', name, value, self, owner='camilogarcia') for name in self._ui_metadata_cache.keys() - ui_metadata.keys(): try: if name in self._generic_ui_metadata_setters: self._generic_ui_metadata_setters[name](self, None) except (ValueError, TypeError): logger.error( 'Error trying to set field {} to default in object {}.', name, self, owner='nabaker') self._ui_metadata_cache = ui_metadata @property def swapping_to_parent(self): return self._swapping_to_parent @property def swapping_from_parent(self): return self._swapping_from_parent @contextmanager def _swapping_parents(self, old_parent, new_parent): self._swapping_from_parent = old_parent self._swapping_to_parent = new_parent try: yield None finally: self._swapping_from_parent = None self._swapping_to_parent = None @property def location(self): return self._location @distributor.fields.Field(op=distributor.ops.SetLocation) def _location_field_internal(self): return self resend_location = _location_field_internal.get_resend() @location.setter def location(self, new_location): self.set_location_without_distribution(new_location) self.resend_location() def set_location_without_distribution(self, new_location): if not isinstance(new_location, sims4.math.Location): raise TypeError() if not (new_location == self._location and (self.parts is not None and new_location.parent is not None) and new_location.parent.parts is not None): return old_location = self._location events = [(self, old_location)] for child in self.get_all_children_recursive_gen(): events.append((child, child._location)) if new_location.parent != old_location.parent: self.pre_parent_change(new_location.parent) with self._swapping_parents(old_location.parent, new_location.parent): if old_location.parent is not None: old_location.parent._remove_child( self, new_parent=new_location.parent) if new_location.parent is not None: new_location.parent._add_child(self, new_location) visibility_state = self.visibility or VisibilityState() if new_location.parent is not None and new_location.parent._disable_child_footprint_and_shadow: visibility_state.enable_drop_shadow = False else: visibility_state.enable_drop_shadow = True self.visibility = visibility_state if new_location.parent is not None: current_inventory = self.get_inventory() if current_inventory is not None and not current_inventory.try_remove_object_by_id( self.id): raise RuntimeError( 'Unable to remove object: {} from the inventory: {}, parenting request will be ignored.' .format(self, current_inventory)) posture_graph_service = services.current_zone().posture_graph_service with posture_graph_service.object_moving(self): self._location = new_location if self.parts: for part in self.parts: part.on_owner_location_changed() if new_location.parent != old_location.parent: self.on_parent_change(new_location.parent) for (obj, old_value) in events: if obj is not self: new_location = obj.location.clone() obj._location = new_location obj.on_location_changed(old_value) def set_location(self, location): self.location = location def move_to(self, **overrides): self.location = self._location.clone(**overrides) @distributor.fields.Field(op=distributor.ops.SetAudioEffects) def audio_effects(self): return self._audio_effects resend_audio_effects = audio_effects.get_resend() def append_audio_effect(self, key, audio_effect_data): if self._audio_effects is None: self._audio_effects = {} self._audio_effects[key] = audio_effect_data self.resend_audio_effects() def remove_audio_effect(self, key): if self._audio_effects is None: logger.error( 'Found audio effects is None while trying to remove audio effect with key {} on {}', key, self, owner='jdimailig') return if key in self._audio_effects: del self._audio_effects[key] if not self._audio_effects: self._audio_effects = None self.resend_audio_effects() @forward_to_components def on_location_changed(self, old_location): pass @property def transform(self): return self._location.world_transform @transform.setter def transform(self, transform): if self.parent is not None: self.move_to(transform=transform, parent=None, routing_surface=self.parent.routing_surface) return self.move_to(transform=transform) @property def position(self): return self.transform.translation @property def position_with_forward_offset(self): return self.position + self.forward * ClientObjectMixin.FORWARD_OFFSET @property def intended_position_with_forward_offset(self): return self.intended_position + self.intended_forward * ClientObjectMixin.FORWARD_OFFSET @property def orientation(self): return self.transform.orientation @property def forward(self): return self.orientation.transform_vector( self.forward_direction_for_picking) @property def routing_surface(self): return self._location.world_routing_surface @property def level(self): routing_surface = self.routing_surface if routing_surface is None: return return routing_surface.secondary_id @property def routing_location(self): return self.get_routing_location_for_transform(self.transform) def get_routing_location_for_transform(self, transform, routing_surface=DEFAULT): routing_surface = self.routing_surface if routing_surface is DEFAULT else routing_surface return routing.Location(transform.translation, transform.orientation, routing_surface) @property def intended_transform(self): return self.transform @property def intended_position(self): return self.intended_transform.translation @property def intended_forward(self): return self.intended_transform.orientation.transform_vector( self.forward_direction_for_picking) @property def intended_routing_surface(self): return self.routing_surface @property def parent(self): parent = self._location.parent parent = self.attempt_to_remap_parent(parent) if parent is not None: children = parent.children if not (self not in children and self.is_part and self.part_owner in children): return return parent @property def bb_parent(self): return self._location.parent def attempt_to_remap_parent(self, parent): if self.is_part and parent is not None and parent.parts is not None: distance = None found_part = None for part in parent.parts: dot = sims4.math.vector_dot(self.forward, part.forward) if dot < -0.98: new_distance = (part.position - self.position).magnitude_squared() if not distance is None: if new_distance <= distance: if new_distance == distance and found_part.subroot_index is not None: continue distance = new_distance found_part = part if new_distance == distance and found_part.subroot_index is not None: continue distance = new_distance found_part = part return found_part return parent def parent_object(self, child_type=ChildrenType.DEFAULT): if child_type is ChildrenType.BB_ONLY: parent = self.bb_parent else: parent = self.parent if parent is not None: if parent.is_part: parent = parent.part_owner return parent @property def parent_slot(self): parent = self._location.parent if parent is None: return bone_name_hash = self._location.joint_name_or_hash or self._location.slot_hash result = None for runtime_slot in parent.get_runtime_slots_gen( bone_name_hash=bone_name_hash): assert not result is not None result = runtime_slot if result is None: result = RuntimeSlot(parent, bone_name_hash, frozenset()) return result def get_parenting_root(self): result = self next_parent = result.parent while next_parent is not None: result = next_parent next_parent = result.parent return result @property def children(self): if self._children_objects is not None: return self._children_objects[ChildrenType.DEFAULT] return () def children_recursive_gen(self, include_self=False): if include_self: yield self for child in self.children: yield child for grandchild in child.children_recursive_gen(): yield grandchild @assertions.hot_path def _children_recursive_fast_gen(self): yield self for child in self.children: yield from child._children_recursive_fast_gen() def get_all_children_gen(self): if self._children_objects is not None: for children in self._children_objects.values(): yield from children def get_all_children_recursive_gen(self): for child in self.get_all_children_gen(): yield child yield from child.get_all_children_recursive_gen() def clear_default_children(self): if self._children_objects is not None: self._children_objects[ChildrenType.DEFAULT].clear() @assertions.hot_path def parenting_hierarchy_gen(self): self_parent = self.parent if self_parent is not None: master_parent = self_parent master_parent_parent = master_parent.parent while master_parent_parent is not None: master_parent = master_parent_parent master_parent_parent = master_parent.parent yield from master_parent._children_recursive_fast_gen() else: yield from self._children_recursive_fast_gen() def on_reset_send_op(self, reset_reason): super().on_reset_send_op(reset_reason) if self.valid_for_distribution: if reset_reason != ResetReason.BEING_DESTROYED or self.vehicle_component is not None: try: reset_op = distributor.ops.ResetObject(self.id) dist = Distributor.instance() dist.add_op(self, reset_op) except: logger.exception( 'Exception thrown sending reset op for {}', self) def on_reset_internal_state(self, reset_reason): if self.valid_for_distribution and reset_reason != ResetReason.BEING_DESTROYED: self.geometry_state = None self.material_state = None self.resend_location() self._reset_reference_arb() super().on_reset_internal_state(reset_reason) def on_reset_get_interdependent_reset_records(self, reset_reason, reset_records): super().on_reset_get_interdependent_reset_records( reset_reason, reset_records) for child in set(self.get_all_children_gen()): reset_records.append( ResetRecord(child, ResetReason.RESET_EXPECTED, self, 'Child')) @property def slot_hash(self): return self._location.slot_hash @slot_hash.setter def slot_hash(self, value): if self._location.slot_hash != value: self.location = self._location.clone(slot_hash=value) @property def bone_name_hash(self): return self._location.joint_name_or_hash or self._location.slot_hash @property def part_suffix(self) -> str: pass @distributor.fields.Field(op=distributor.ops.SetModel) def model_with_material_variant(self): return (self._model, self._material_variant) resend_model_with_material_variant = model_with_material_variant.get_resend( ) @model_with_material_variant.setter def model_with_material_variant(self, value): (self._model, self._material_variant) = value @property def model(self): return self._model @model.setter def model(self, value): model_res_key = None if isinstance(value, sims4.resources.Key): model_res_key = value elif isinstance(value, Definition): model_res_key = value.get_model(index=0) self.set_definition(value.id, ignore_rig_footprint=True) else: if value is not None: logger.error( 'Trying to set the model of object {} to the invalid value of {}. The object will revert to its default model instead.', self, value, owner='tastle') model_res_key = self.definition.get_model(self._state_index) self.model_with_material_variant = (model_res_key, self._material_variant) @property def material_variant(self): return self._material_variant @material_variant.setter def material_variant(self, value): if value is None: self.model_with_material_variant = (self._model, None) else: if not isinstance(value, str): raise TypeError('Model variant value must be a string') if not value: self.model_with_material_variant = (self._model, None) else: try: variant_value = int(value) except ValueError: variant_value = sims4.hash_util.hash32(value) self.model_with_material_variant = (self._model, variant_value) @distributor.fields.Field(op=distributor.ops.SetStandInModel) def standin_model(self): return self._standin_model @standin_model.setter def standin_model(self, value): self._standin_model = value @distributor.fields.Field(op=distributor.ops.SetObjectDefStateIndex, default=0) def state_index(self): return self._state_index resend_state_index = state_index.get_resend() @distributor.fields.Field(op=distributor.ops.SetRig, priority=distributor.fields.Field.Priority.HIGH) def rig(self): return self._rig @rig.setter def rig(self, value): if not isinstance(value, sims4.resources.Key): raise TypeError self._rig = value @distributor.fields.Field(op=distributor.ops.SetSlot) def slot(self): return self._slot resend_slot = slot.get_resend() @property def slots_resource(self): return self._slots_resource @distributor.fields.Field(op=distributor.ops.SetScale, default=1) def _client_scale(self): scale_value = self.scale for modifier in self.scale_modifiers_gen(): scale_value *= modifier return scale_value _resend_client_scale = _client_scale.get_resend() @property def scale(self): return self._scale @forward_to_components_gen def scale_modifiers_gen(self): pass @scale.setter def scale(self, value): if self._scale != value: self._scale = value self.on_location_changed(self._location) self._resend_client_scale() @property def parent_type(self): return self._parent_type @parent_type.setter def parent_type(self, value): self._parent_type = value self._resend_parent_type_info() @distributor.fields.Field(op=distributor.ops.SetParentType, default=None) def parent_type_info(self): return (self._parent_type, self._parent_location) @parent_type_info.setter def parent_type_info(self, value): (self._parent_type, self._parent_location) = value _resend_parent_type_info = parent_type_info.get_resend() @property def build_buy_lockout(self): return self._build_buy_lockout @distributor.fields.Field(op=distributor.ops.SetTint, default=None) def tint(self): if self.build_buy_lockout and lockout_visualization: return sims4.color.ColorARGB32(23782) return self._tint @tint.setter def tint(self, tint_color): value = getattr(tint_color, 'value', tint_color) if value and not isinstance(value, sims4.color.ColorARGB32): raise TypeError('Tint value must be a Color') if value == sims4.color.Color.WHITE: self._tint = None else: self._tint = value resend_tint = tint.get_resend() @distributor.fields.Field(op=distributor.ops.SetMulticolor, default=None) def multicolor(self): return self._multicolor @multicolor.setter def multicolor(self, value): self._multicolor = value resend_multicolor = multicolor.get_resend() @distributor.fields.Field(op=distributor.ops.SetDisplayNumber, default=None) def display_number(self): return self._display_number @display_number.setter def display_number(self, value): self._display_number = value resend_display_number = display_number.get_resend() def update_display_number(self, display_number=None): if display_number is not None: self.display_number = display_number return if hasattr(self, 'get_display_number'): self.display_number = self.get_display_number() @distributor.fields.Field(op=distributor.ops.SetOpacity, default=None) def opacity(self): return self._opacity @opacity.setter def opacity(self, value): if self.allow_opacity_change: self._opacity = self._clamp_opacity(value) def _clamp_opacity(self, value): if value is None: return try: value = float(value) except: raise TypeError('Opacity value must be a float') return sims4.math.clamp(0.0, value, 1.0) @distributor.fields.Field(op=SetAwarenessSourceOp) def awareness_scores(self): return self._awareness_scores resend_awareness_scores = awareness_scores.get_resend() def add_awareness_scores(self, awareness_sources): if self._awareness_scores is None: self._awareness_scores = Counter() self._awareness_scores.update(awareness_sources) self.resend_awareness_scores() def remove_awareness_scores(self, awareness_sources): if self._awareness_scores is None: return self._awareness_scores.subtract(awareness_sources) for awareness_channel in tuple(self.awareness_scores): if not self._awareness_scores[awareness_channel]: del self._awareness_scores[awareness_channel] if not self._awareness_scores: self._awareness_scores = None self.resend_awareness_scores() def add_geometry_state_override(self, original_geometry_state, override_geometry_state): if self._geometry_state_overrides is None: self._geometry_state_overrides = {} original_state_hash = sims4.hash_util.hash32(original_geometry_state) override_state_hash = sims4.hash_util.hash32(override_geometry_state) logger.assert_raise( original_state_hash not in self._geometry_state_overrides, 'add_geometry_state_override does not support multiple overrides per state' ) self._geometry_state_overrides[ original_state_hash] = override_state_hash self.geometry_state = self.geometry_state def remove_geometry_state_override(self, original_geometry_state): state_hash = sims4.hash_util.hash32(original_geometry_state) if state_hash in self._geometry_state_overrides: del self._geometry_state_overrides[state_hash] if not self._geometry_state_overrides: self._geometry_state_overrides = None @distributor.fields.Field(op=distributor.ops.SetGeometryState, default=None) def geometry_state(self): return self._geometry_state @geometry_state.setter def geometry_state(self, value): self._geometry_state = self._get_geometry_state_for_value(value) def _get_geometry_state_for_value(self, value): if not value: return if isinstance(value, str): state_hash = sims4.hash_util.hash32(value) elif isinstance(value, int): state_hash = value if self._geometry_state_overrides is not None: if state_hash in self._geometry_state_overrides: state_hash = self._geometry_state_overrides[state_hash] return state_hash @distributor.fields.Field(op=distributor.ops.SetCensorState, default=None) def censor_state(self): return self._censor_state @censor_state.setter def censor_state(self, value): try: value = CensorState(value) except: raise TypeError('Censor State value must be an int') self._censor_state = value @distributor.fields.Field(op=distributor.ops.SetVisibility, default=None) def visibility(self): return self._visibility @visibility.setter def visibility(self, value): if not isinstance(value, VisibilityState): raise TypeError( 'Visibility must be set to value of type VisibilityState') self._visibility = value if value is not None: if value.visibility is True: if value.inherits is False: if value.enable_drop_shadow is False: self._visibility = None @distributor.fields.Field(op=distributor.ops.SetVisibilityFlags) def visibility_flags(self): return self._visibility_flags @visibility_flags.setter def visibility_flags(self, value): self._visibility_flags = value @distributor.fields.Field(op=distributor.ops.SetMaterialState, default=None) def material_state(self): return self._material_state @material_state.setter def material_state(self, value): if value is None: self._material_state = None else: if not isinstance(value, MaterialState): raise TypeError( 'Material State must be set to value of type MaterialState' ) if value.state_name_hash == 0: self._material_state = None else: self._material_state = value @property def material_hash(self): if self.material_state is None: return 0 else: return self.material_state.state_name_hash @distributor.fields.Field(op=distributor.ops.StartArb, default=None) def reference_arb(self): return self._reference_arb def update_reference_arb(self, arb): if self._reference_arb is None: self._reference_arb = animation.arb.Arb() native.animation.update_post_condition_arb(self._reference_arb, arb) def _reset_reference_arb(self): if self._reference_arb is not None: reset_arb_element = ArbElement(animation.arb.Arb()) reset_arb_element.add_object_to_reset(self) reset_arb_element.distribute() reset_arb_element.cleanup() self._reference_arb = None _NO_SLOTS = EMPTY_SET @property def deco_slot_size(self): return get_object_decosize(self.definition.id) @property def deco_slot_types(self): return DecorativeSlotTuning.get_slot_types_for_object( self.deco_slot_size) @property def slot_type_set(self): key = get_object_slotset(self.definition.id) return get_slot_type_set_from_key(key) @property def slot_types(self): slot_type_set = self.slot_type_set if slot_type_set is not None: return slot_type_set.slot_types return self._NO_SLOTS @property def ideal_slot_types(self): carryable = self.get_component(CARRYABLE_COMPONENT) if carryable is not None: slot_type_set = carryable.ideal_slot_type_set if slot_type_set is not None: return slot_type_set.slot_types & (self.slot_types | self.deco_slot_types) return self._NO_SLOTS @property def all_valid_slot_types(self): return self.deco_slot_types | self.slot_types def _add_child(self, child, location): if self._children_objects is None: self._children_objects = defaultdict(WeakSet) if not isinstance(self.children, (WeakSet, set)): raise TypeError( "self.children is not a WeakSet or a set, it's {}".format( self.children)) bone_name_hash = location.joint_name_or_hash or location.slot_hash found_runtime_slot = None for runtime_slot in location.parent.get_runtime_slots_gen( bone_name_hash=bone_name_hash): assert not found_runtime_slot is not None found_runtime_slot = runtime_slot if found_runtime_slot is not None: for slot_type in found_runtime_slot.slot_types: if not slot_type.bb_only: self._children_objects[ChildrenType.DEFAULT].add(child) break else: self._children_objects[ChildrenType.BB_ONLY].add(child) else: self._children_objects[ChildrenType.DEFAULT].add(child) if self.parts: for part in self.parts: part.on_children_changed() self.on_child_added(child, location) def _remove_child(self, child, new_parent=None): if not isinstance(self.children, (WeakSet, set)): raise TypeError( "self.children is not a WeakSet or a set, it's {}".format( self.children)) for (_, weak_obj_set) in self._children_objects.items(): if child in weak_obj_set: weak_obj_set.discard(child) break if self.parts: for part in self.parts: part.on_children_changed() self.on_child_removed(child, new_parent=new_parent) @forward_to_components def on_remove_from_client(self): super().on_remove_from_client() for primitive in tuple(self.primitives): primitive.detach(self) def post_remove(self): super().post_remove() for primitive in tuple(self.primitives): primitive.detach(self) self.primitives = None @forward_to_components def on_child_added(self, child, location): if self._on_children_changed is None: return self._on_children_changed(child, location=location) @forward_to_components def on_child_removed(self, child, new_parent=None): if self._on_children_changed is None: return self._on_children_changed(child, new_parent=new_parent) @forward_to_components def pre_parent_change(self, parent): pass @forward_to_components def on_parent_change(self, parent): caches.clear_all_caches() if parent is None: self.parent_type = ObjectParentType.PARENT_NONE else: self.parent_type = ObjectParentType.PARENT_OBJECT def create_parent_location(self, parent, transform=sims4.math.Transform.IDENTITY(), joint_name_or_hash=None, slot_hash=0, routing_surface=None): if parent is not None: if self in parent.ancestry_gen(): raise ValueError( 'Invalid parent value (parent chain is circular)') if joint_name_or_hash: native.animation.get_joint_transform_from_rig( parent.rig, joint_name_or_hash) if slot_hash and not parent.has_slot(slot_hash): raise KeyError( 'Could not slot {}/{} in slot {} on {}/{}'.format( self, self.definition, hex(slot_hash), parent, parent.definition)) part_joint_name = joint_name_or_hash or slot_hash if parent is not None: if part_joint_name is not None: if not parent.is_part: if parent.parts: for part in parent.parts: if part.has_slot(part_joint_name): parent = part break new_location = self._location.clone( transform=transform, joint_name_or_hash=joint_name_or_hash, slot_hash=slot_hash, parent=parent, routing_surface=routing_surface) return new_location def set_parent(self, *args, **kwargs): new_location = self.create_parent_location(*args, **kwargs) self.location = new_location def clear_parent(self, transform, routing_surface): return self.set_parent(None, transform=transform, routing_surface=routing_surface) def remove_reference_from_parent(self): parent = self.bb_parent if parent is not None: parent._remove_child(self, new_parent=UNSET) @distributor.fields.Field(op=distributor.ops.VideoSetPlaylistOp, default=None) def video_playlist(self): return self._video_playlist @video_playlist.setter def video_playlist(self, playlist): self._video_playlist = playlist _resend_video_playlist = video_playlist.get_resend() def fade_opacity(self, opacity: float, duration: float, immediate=False, additional_channels=None): if self.allow_opacity_change: opacity = self._clamp_opacity(opacity) if opacity != self._opacity: self._opacity = opacity fade_op = distributor.ops.FadeOpacity(opacity, duration, immediate=immediate) if additional_channels: for channel in additional_channels: fade_op.add_additional_channel(*channel) distributor.ops.record(self, fade_op) def fade_in(self, fade_duration=None, immediate=False, additional_channels=None): if self.allow_opacity_change: if fade_duration is None: fade_duration = ClientObjectMixin.FADE_DURATION if self.visibility is not None: if not self.visibility.visibility: self.visibility = VisibilityState() self.opacity = 0 self.fade_opacity(1, fade_duration, immediate=immediate, additional_channels=additional_channels) def fade_out(self, fade_duration=None, immediate=False, additional_channels=None): if self.allow_opacity_change: if fade_duration is None: fade_duration = ClientObjectMixin.FADE_DURATION self.fade_opacity(0, fade_duration, immediate=immediate, additional_channels=additional_channels) @distributor.fields.Field(op=distributor.ops.SetValue, default=None) def current_value(self): new_value = self._base_value statistic_component = self.statistic_component if statistic_component is not None: new_value += statistic_component.get_added_monetary_value() state_component = self.state_component if state_component is not None: return max( round(new_value * state_component.state_based_value_mod), 0) return max(new_value, 0) @current_value.setter def current_value(self, value): state_component = self.state_component if state_component is not None: self.base_value = value / state_component.state_based_value_mod else: self.base_value = value _resend_current_value = current_value.get_resend() def update_current_value(self, update_tooltip=True): self._resend_current_value() if update_tooltip: self.update_tooltip_field(TooltipFieldsComplete.simoleon_value, self.current_value) @property def base_value(self): return self._base_value @base_value.setter def base_value(self, value): self._base_value = round(max(value, 0)) update_tooltip = self.get_tooltip_field( TooltipFieldsComplete.simoleon_value) is not None self.update_current_value(update_tooltip=update_tooltip) @property def depreciated_value(self): if not self.definition.get_can_depreciate(): return self.catalog_value return self.catalog_value * (1 - self.INITIAL_DEPRECIATION) @property def catalog_value(self): return self.get_object_property(GameObjectProperty.CATALOG_PRICE) @property def depreciated(self): return not self._needs_depreciation def set_post_bb_fixup_needed(self): self._needs_post_bb_fixup = True self._needs_depreciation = True def try_post_bb_fixup(self, force_fixup=False, active_household_id=0): if force_fixup or self._needs_depreciation: if force_fixup: self._needs_depreciation = True self._on_try_depreciation(active_household_id=active_household_id) if force_fixup or self._needs_post_bb_fixup: self._needs_post_bb_fixup = False self.on_post_bb_fixup() @forward_to_components def on_post_bb_fixup(self): services.get_event_manager().process_events_for_household( test_events.TestEvent.ObjectAdd, services.household_manager().get(self._household_owner_id), obj=self) def _on_try_depreciation(self, active_household_id=0): if self._household_owner_id != active_household_id: return self._needs_depreciation = False if not self.definition.get_can_depreciate(): return self.base_value = floor(self._base_value * (1 - self.INITIAL_DEPRECIATION)) def register_for_on_children_changed_callback(self, callback): if self._on_children_changed is None: self._on_children_changed = CallableList() if callback not in self._on_children_changed: self._on_children_changed.append(callback) def unregister_for_on_children_changed_callback(self, callback): if self._on_children_changed is not None: if callback in self._on_children_changed: self._on_children_changed.remove(callback) if not self._on_children_changed: self._on_children_changed = None @distributor.fields.Field(op=distributor.ops.SetScratched, default=False) def scratched(self): return self._scratched @scratched.setter def scratched(self, scratched): self._scratched = scratched @distributor.fields.Field(op=distributor.ops.SetWindSpeedEffect, default=None) def wind_speed_level(self): return self._wind_speed_effect @wind_speed_level.setter def wind_speed_level(self, value): self._wind_speed_effect = value.wind_speed
class RelationshipTracker: __qualname__ = 'RelationshipTracker' 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 __iter__(self): return self._relationships.values().__iter__() def __len__(self): return len(self._relationships) @property def suppress_client_updates(self): return self._suppress_client_updates @contextmanager def suppress_client_updates_context_manager(self): self._suppress_client_updates = True try: yield None finally: self._suppress_client_updates = False def create_relationship(self, target_sim_id): return self._find_relationship(target_sim_id, True) def destroy_relationship(self, target_sim_id, notify_client=True): if target_sim_id in self._relationships: relationship = self._relationships[target_sim_id] relationship.destroy(notify_client=notify_client) del self._relationships[target_sim_id] def _clear_relationships(self): for sim_id in tuple(self._relationships.keys()): self.destroy_relationship(sim_id) def destroy_all_relationships(self): sim_id = self._sim_info.id keys = tuple(self._relationships.keys()) for target_sim_id in keys: relationship = self._relationships[target_sim_id] target_sim_info = relationship.find_target_sim_info() if target_sim_info is not None: target_sim_info.relationship_tracker.destroy_relationship( sim_id) self.destroy_relationship(target_sim_id) def save(self): save_list = [ relationship.get_persistance_protocol_buffer() for relationship in self._relationships.values() ] return save_list def load(self, relationship_save_data): with self.suppress_client_updates_context_manager(): self._clear_relationships() while relationship_save_data: for rel_save in relationship_save_data: relationship = self.create_relationship(rel_save.target_id) relationship.load(self._sim_info, rel_save) def send_relationship_info(self, target_sim_id=None): if target_sim_id is None: for relationship in self._relationships.values(): relationship.send_relationship_info() else: relationship = self._find_relationship(target_sim_id) if relationship is not None: relationship.send_relationship_info() def clean_and_send_remaining_relationship_info(self): sim_info_manager = services.sim_info_manager() for (target_sim_info_id, relationship) in tuple(self._relationships.items()): if target_sim_info_id in sim_info_manager: relationship.send_relationship_info() else: self.destroy_relationship(target_sim_info_id, notify_client=False) def add_create_relationship_listener(self, callback): if callback not in self._create_relationship_callbacks: self._create_relationship_callbacks.append(callback) def remove_create_relationship_listener(self, callback): self._create_relationship_callbacks.remove(callback) @caches.cached def get_relationship_score(self, target_sim_id, track=DEFAULT): if track is DEFAULT: track = RelationshipGlobalTuning.REL_INSPECTOR_TRACK relationship = self._find_relationship(target_sim_id) if relationship: return relationship.get_track_score(track) return Relationship.DEFAULT_RELATIONSHIP_VALUE def add_relationship_score(self, target_sim_id, increment, track=DEFAULT, threshold=None): if track is DEFAULT: track = RelationshipGlobalTuning.REL_INSPECTOR_TRACK if target_sim_id == self._sim_info.sim_id: return relationship = self._find_relationship(target_sim_id, True) if relationship: if threshold is None or threshold.compare( relationship.get_track_score(track)): relationship.add_track_score(increment, track) logger.debug( 'Adding to score to track {} for {}: += {}; new score = {}', track, relationship, increment, relationship.get_track_score(track)) else: logger.debug( 'Attempting to add to track {} for {} but {} not within threshold {}', track, relationship, relationship.get_track_score(track), threshold) else: logger.error( 'Attempting to add to the relationship score with myself: Sim = {}', self._sim_info) def set_relationship_score(self, target_sim_id, value, track=DEFAULT, threshold=None): if track is DEFAULT: track = RelationshipGlobalTuning.REL_INSPECTOR_TRACK if target_sim_id == self._sim_info.sim_id: return relationship = self._find_relationship(target_sim_id, True) if relationship: if threshold is None or threshold.compare( relationship.get_track_score(track)): relationship.set_track_score(value, track) logger.debug( 'Setting score on track {} for {}: = {}; new score = {}', track, relationship, value, relationship.get_track_score(track)) else: logger.debug( 'Attempting to set score on track {} for {} but {} not within threshold {}', track, relationship, relationship.get_track_score(track), threshold) else: logger.error( 'Attempting to set the relationship score with myself: Sim = {}', self._sim_info) def enable_selectable_sim_track_decay(self, to_enable=True): logger.debug('Enabling ({}) decay for selectable sim: {}'.format( to_enable, self._sim_info)) for relationship in self._relationships.values(): relationship.enable_selectable_sim_track_decay(to_enable) def get_relationship_track_utility_score(self, target_sim_id, track=DEFAULT): if track is DEFAULT: track = RelationshipGlobalTuning.REL_INSPECTOR_TRACK relationship = self._find_relationship(target_sim_id) if relationship is not None: return relationship.get_track_utility_score(track) return track.autonomous_desire def get_relationship_prevailing_short_term_context_track( self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: return relationship.get_prevailing_short_term_context_track() def get_default_short_term_context_bit(self): track = Relationship.DEFAULT_SHORT_TERM_CONTEXT_TRACK return track.get_bit_at_relationship_value(track.initial_value) def get_relationship_track_tracker(self, target_sim_id, add=False): relationship = self._find_relationship(target_sim_id, add) if relationship: return relationship.bit_track_tracker return def get_relationship_track(self, target_sim_id, track=DEFAULT, add=False): with self.suppress_client_updates_context_manager(): if track is DEFAULT: track = RelationshipGlobalTuning.REL_INSPECTOR_TRACK relationship = self._find_relationship(target_sim_id, add) if relationship: return relationship.get_track(track, add) return def relationship_tracks_gen(self, target_sim_id): with self.suppress_client_updates_context_manager(): relationship = self._find_relationship(target_sim_id) while relationship: for track in relationship.bit_track_tracker: yield track def add_relationship_multipliers(self, handle, relationship_multipliers): if relationship_multipliers: for relationship in self: self._apply_relationship_multiplier_to_relationship( relationship, relationship_multipliers) self._relationship_multipliers[handle] = relationship_multipliers def _apply_relationship_multiplier_to_relationship( self, relationship, relationship_multipliers): for (track_type, multiplier) in relationship_multipliers.items(): relationship_track = relationship.get_track( track_type, add=track_type.add_if_not_in_tracker) while relationship_track is not None: relationship_track.add_statistic_multiplier(multiplier) def remove_relationship_multipliers(self, handle): relationship_multipliers = self._relationship_multipliers.pop( handle, None) if relationship_multipliers is None: return for relationship in self: for (track_type, multiplier) in relationship_multipliers.items(): relationship_track = relationship.get_track(track_type, add=False) while relationship_track is not None: relationship_track.remove_statistic_multiplier(multiplier) def on_added_to_social_group(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: relationship.apply_social_group_decay() def on_removed_from_social_group(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: relationship.remove_social_group_decay() def set_default_tracks(self, target_sim, update_romance=True, family_member=False): if target_sim is None or target_sim.sim_id == self._sim_info.sim_id: return with self.suppress_client_updates_context_manager(): target_sim_id = target_sim.sim_id key = DefaultGenealogyLink.Roommate if family_member: key = DefaultGenealogyLink.FamilyMember default_relationship = DefaultRelationshipInHousehold.RelationshipSetupMap.get( key) default_relationship.apply(self, target_sim) if update_romance and self.spouse_sim_id == target_sim_id: key = DefaultGenealogyLink.Spouse default_relationship = DefaultRelationshipInHousehold.RelationshipSetupMap.get( key) default_relationship.apply(self, target_sim) for (gender, gender_preference_statistic ) in self._sim_info.get_gender_preferences_gen(): while gender == target_sim.gender: gender_preference_statistic.set_value( gender_preference_statistic.max_value) relationship_setup_logger.info( 'Set default tracks {:25} -> {:25} as {}', self._sim_info.full_name, target_sim.full_name, key) def add_relationship_bit(self, target_sim_id, bit_to_add, force_add=False): if bit_to_add is None: logger.error('Attempting to add None bit to relationship for {}', self._sim_info) return if bit_to_add.trait_replacement_bits is not None: trait_tracker = self._sim_info.trait_tracker for (trait, replacement_bit) in bit_to_add.trait_replacement_bits.items(): while trait_tracker.has_trait(trait): self.add_relationship_bit(target_sim_id, replacement_bit, force_add) return relationship = self._find_relationship(target_sim_id, True) if not relationship: logger.error('Failed to find relationship for {} and {}', self._sim_info, services.sim_info_manager().get(target_sim_id)) return for requirement in bit_to_add.permission_requirements: while self._sim_info.sim_permissions.is_permission_enabled( requirement.permission) != requirement.required_enabled: return if force_add: if bit_to_add.is_track_bit and bit_to_add.triggered_track is not None: track = bit_to_add.triggered_track mean_list = track.bit_data.get_track_mean_list_for_bit( bit_to_add) for mean_tuple in mean_list: self.set_relationship_score(target_sim_id, mean_tuple.mean, mean_tuple.track) for required_bit in bit_to_add.required_bits: self.add_relationship_bit(target_sim_id, required_bit, force_add=True) self._send_relationship_prechange_event(target_sim_id) if not bit_to_add.is_track_bit: relationship.add_bit(bit_to_add) self._send_relationship_changed_event(target_sim_id) services.social_service.post_relationship_message(self._sim_info, bit_to_add, target_sim_id, added=True) def remove_relationship_bit(self, target_sim_id, bit): relationship = self._find_relationship(target_sim_id) if relationship: self._send_relationship_prechange_event(target_sim_id) relationship.remove_bit(bit) self._send_relationship_changed_event(target_sim_id) services.social_service.post_relationship_message(self._sim_info, bit, target_sim_id, added=False) def _check_for_living_status(self, sim_id, allow_dead_targets, allow_living_targets): sim_info = services.sim_info_manager().get(sim_id) if sim_info is None: logger.error( '_check_for_living_status() could not find SimInfo for sim_id {} with requesting sim {}', sim_id, self._sim_info, owner='ayarger') return False is_sim_dead = sim_info.is_dead return allow_dead_targets and is_sim_dead or allow_living_targets and not is_sim_dead def get_all_bits(self, target_sim_id: int = None, allow_dead_targets=True, allow_living_targets=True): bits = [] if target_sim_id is None: for relationship in self._relationships.values(): if not self._check_for_living_status( relationship.target_sim_id, allow_dead_targets, allow_living_targets): pass bits.extend(relationship.get_bits()) else: relationship = self._find_relationship(target_sim_id) if relationship: if not self._check_for_living_status( relationship.target_sim_id, allow_dead_targets, allow_living_targets): return bits bits.extend(relationship.get_bits()) return bits def get_relationship_depth(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship: return relationship.depth return 0 def has_bit(self, target_sim_id, bit): relationship = self._find_relationship(target_sim_id) if relationship is not None: return relationship.has_bit(bit) return False def get_highest_priority_track_bit(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: return relationship.get_highest_priority_track_bit() return def get_highest_priority_bit(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: return relationship.get_highest_priority_bit() return def update_bits_on_age_up(self, current_age): for relationship in self._relationships.values(): relationship.add_historical_bits_on_age_up(current_age) def target_sim_gen(self): for target_sim_id in self._relationships.keys(): yield target_sim_id def add_relationship_appropriateness_buffs(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship is not None: relationship.add_relationship_appropriateness_buffs() def add_neighbor_bit_if_necessary(self): for relationship in self._relationships.values(): relationship.add_neighbor_bit_if_necessary(self._sim_info) def get_knowledge(self, target_sim_id, initialize=False): relationship = self._find_relationship(target_sim_id, create=initialize) if relationship is not None: return relationship.get_knowledge(initialize=initialize) def add_known_trait(self, trait, target_sim_id, num_traits=None, notify_client=True): relationship = self._find_relationship(target_sim_id, True) knowledge = relationship.get_knowledge(initialize=True) knowledge.add_known_trait(trait, num_traits=num_traits, notify_client=notify_client) def print_relationship_info(self, target_sim_id, _connection): relationship = self._find_relationship(target_sim_id) if relationship is not None: sims4.commands.output( '{}:\n\tTotal Depth: {}\n\tBits:\n{}\n\tTracks:\n{}'.format( relationship, relationship.depth, relationship.build_printable_string_of_bits(), relationship.build_printable_string_of_tracks()), _connection) else: sims4.commands.output( 'Relationship not found between {} and {}:\n\tTotal Depth: {}\n\tBits:\n{}\n\tTracks:\n{}' .format(self._sim_info, target_sim_id, self.get_relationship_depth(target_sim_id), self._build_printable_string_of_bits(target_sim_id), self._build_printable_string_of_tracks(target_sim_id)), _connection) def _find_relationship(self, target_sim_id, create=False): if self._sim_info.sim_id == target_sim_id: return if target_sim_id in self._relationships: return self._relationships[target_sim_id] if create: logger.debug('Creating relationship for {0} and {1}', self._sim_info, target_sim_id) relationship = Relationship(self, self._sim_info.sim_id, target_sim_id) self._relationships[target_sim_id] = relationship relationship.add_neighbor_bit_if_necessary(self._sim_info) for multiplier in self._relationship_multipliers.values(): self._apply_relationship_multiplier_to_relationship( relationship, multiplier) self._create_relationship_callbacks(relationship) return relationship def _build_printable_string_of_bits(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship: return relationship.build_printable_string_of_bits() return '' def _build_printable_string_of_tracks(self, target_sim_id): relationship = self._find_relationship(target_sim_id) if relationship: return relationship.build_printable_string_of_tracks() return '' def _send_relationship_prechange_event(self, target_sim_id): services.get_event_manager().process_event( event_testing.test_events.TestEvent.PrerelationshipChanged, self._sim_info, sim_id=self._sim_info.id, target_sim_id=target_sim_id) def _send_relationship_changed_event(self, target_sim_id): services.get_event_manager().process_event( event_testing.test_events.TestEvent.RelationshipChanged, sim_info=self._sim_info, sim_id=self._sim_info.id, target_sim_id=target_sim_id)
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 BroadcasterService(Service): 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._object_cache_tags = None self._pending_update = False self._quadtrees = defaultdict(sims4.geometry.QuadTree) def create_update_alarm(self): self._alarm_handle = add_alarm(self, interval_in_real_seconds(self.INTERVAL), self._on_update, repeating=True, use_sleep_time=False) def start(self): self.create_update_alarm() 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._on_wall_contours_changed) 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._on_wall_contours_changed) def add_broadcaster(self, broadcaster): if broadcaster not in self._pending_broadcasters: self._pending_broadcasters.append(broadcaster) if broadcaster.immediate: self._pending_update = True 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 _is_valid_cache_object(self, obj): if obj.is_sim: return False elif self._object_cache_tags: object_tags = obj.get_tags() if object_tags & self._object_cache_tags: return True else: return False return False return True def get_object_cache_info(self): return (self._object_cache, self._object_cache_tags) def _generate_object_cache(self): self._object_cache = WeakSet(obj for obj in services.object_manager().valid_objects() if self._is_valid_cache_object(obj)) def _update_object_cache(self, obj=None): if obj is None: self._object_cache = None self._object_cache_tags = None return if self._object_cache is not None and self._is_valid_cache_object(obj): self._object_cache.add(obj) def _is_valid_broadcaster(self, broadcaster): broadcasting_object = broadcaster.broadcasting_object if broadcasting_object is None or not broadcasting_object.visible_to_client: return False if broadcasting_object.is_in_inventory(): return False elif 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: if broadcaster.guid == broadcaster_type.guid: if broadcaster.should_cluster(): if broadcaster.routing_surface.secondary_id == broadcaster_level: yield broadcaster def get_broadcasters_debug_gen(self): for cluster_request in self._cluster_requests.values(): for cluster in cluster_request.get_clusters_gen(): broadcaster_iter = cluster.objects_gen() yield next(broadcaster_iter) yield from cluster_request.get_rejects() for broadcaster in self._active_broadcasters: if not broadcaster.should_cluster(): if self._is_valid_broadcaster(broadcaster): yield broadcaster def get_broadcasters_gen(self): for (cluster_request_key, cluster_request) in self._cluster_requests.items(): is_cluster_dirty = cluster_request.is_dirty() for broadcaster in self._get_broadcasters_for_cluster_request_gen(*cluster_request_key): if self._is_valid_broadcaster(broadcaster): broadcaster.regenerate_constraint() for cluster in cluster_request.get_clusters_gen(): linkable_broadcasters_iter = (b for b in cluster.objects_gen() if self._is_valid_broadcaster(b)) master_broadcaster = next(linkable_broadcasters_iter, None) if master_broadcaster is None: continue master_broadcaster.set_linked_broadcasters(linkable_broadcasters_iter) yield master_broadcaster yield from (b for b in cluster_request.get_rejects() if self._is_valid_broadcaster(b)) for broadcaster in self._active_broadcasters: if not broadcaster.should_cluster(): if self._is_valid_broadcaster(broadcaster): yield broadcaster PathSegmentData = namedtuple('PathSegmentData', ('prev_pos', 'cur_pos', 'segment_vec', 'segment_mag_sq', 'segment_normal')) def get_broadcasters_along_route_gen(self, sim, path, start_time=0, end_time=0): path_segment_datas = {} start_index = max(0, path.node_at_time(start_time).index - 1) end_index = min(len(path) - 1, path.node_at_time(end_time).index) for broadcaster in self.get_broadcasters_gen(): if broadcaster.route_events: if not broadcaster.can_affect(sim): continue constraint = broadcaster.get_constraint() geometry = constraint.geometry if geometry is None: continue polygon = geometry.polygon if polygon is None: continue if not constraint.valid: continue constraint_pos = polygon.centroid() constraint_radius_sq = polygon.radius() constraint_radius_sq = constraint_radius_sq*constraint_radius_sq for index in range(end_index, start_index, -1): prev_index = index - 1 prev_node = path.nodes[prev_index] if not constraint.is_routing_surface_valid(prev_node.routing_surface_id): continue segment_key = (prev_index, index) segment_data = path_segment_datas.get(segment_key, None) if segment_data is None: cur_node = path.nodes[index] cur_pos = sims4.math.Vector3(*cur_node.position) prev_pos = sims4.math.Vector3(*prev_node.position) segment_vec = cur_pos - prev_pos segment_vec.y = 0 segment_mag_sq = segment_vec.magnitude_2d_squared() if sims4.math.almost_equal_sq(segment_mag_sq, 0): segment_normal = None else: segment_normal = segment_vec/sims4.math.sqrt(segment_mag_sq) segment_data = BroadcasterService.PathSegmentData(prev_pos, cur_pos, segment_vec, segment_mag_sq, segment_normal) path_segment_datas[segment_key] = segment_data else: (prev_pos, cur_pos, segment_vec, segment_mag_sq, segment_normal) = segment_data if segment_normal is None: constraint_vec = constraint_pos - prev_pos constraint_dist_sq = constraint_vec.magnitude_2d_squared() if constraint_radius_sq < constraint_dist_sq: continue else: constraint_vec = constraint_pos - prev_pos constraint_vec.y = 0 contraint_proj = constraint_vec - segment_normal*sims4.math.vector_dot_2d(constraint_vec, segment_normal) if constraint_radius_sq < contraint_proj.magnitude_2d_squared(): continue for (transform, _, time) in path.get_location_data_along_segment_gen(prev_index, index): if not geometry.test_transform(transform): continue yield (time, broadcaster) break break def get_pending_broadcasters_gen(self): yield from self._pending_broadcasters def _get_all_objects_gen(self): is_any_broadcaster_allowing_objects = True if self._object_cache else False if not is_any_broadcaster_allowing_objects: for broadcaster in self._active_broadcasters: (allow_objects, allow_objects_tags) = broadcaster.allow_objects.is_affecting_objects() if allow_objects: is_any_broadcaster_allowing_objects = True if allow_objects_tags is None: self._object_cache_tags = None break else: if self._object_cache_tags is None: self._object_cache_tags = set() self._object_cache_tags |= allow_objects_tags if is_any_broadcaster_allowing_objects: if self._object_cache is None: self._generate_object_cache() yield from list(self._object_cache) else: self._object_cache = None self._object_cache_tags = None yield from 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 _on_wall_contours_changed(self, *_, **__): self._update_object_cache() def provide_route_events(self, route_event_context, sim, path, failed_types=None, start_time=0, end_time=0, **kwargs): for (time, broadcaster) in self.get_broadcasters_along_route_gen(sim, path, start_time=start_time, end_time=end_time): resolver = broadcaster.get_resolver(sim) for route_event in broadcaster.route_events: if not failed_types is None: pass if not route_event_context.route_event_already_scheduled(route_event, provider=broadcaster) and route_event.test(resolver): route_event_context.add_route_event(RouteEventType.BROADCASTER, route_event(time=time, provider=broadcaster, provider_required=True)) def update(self): if self._pending_update: self._pending_update = False self._update() def _is_location_affected(self, constraint, transform, routing_surface): if constraint.geometry is not None and not constraint.geometry.test_transform(transform): return False elif not constraint.is_routing_surface_valid(routing_surface): return False return True def update_broadcasters_one_shot(self, broadcasters): for obj in self._get_all_objects_gen(): object_transform = None routing_surface = obj.routing_surface for broadcaster in broadcasters: if broadcaster.can_affect(obj): constraint = broadcaster.get_constraint() if not constraint.valid: continue if object_transform is None: parent = obj.parent if parent is None: object_transform = obj.transform else: object_transform = parent.transform if self._is_location_affected(constraint, object_transform, routing_surface): broadcaster.apply_broadcaster_effect(obj) broadcaster.remove_broadcaster_effect(obj) if not obj.valid_for_distribution: break def _update(self): try: self._activate_pending_broadcasters() current_broadcasters = set(self.get_broadcasters_gen()) for obj in self._get_all_objects_gen(): object_transform = None is_affected = False for broadcaster in current_broadcasters: if broadcaster.can_affect(obj): constraint = broadcaster.get_constraint() if not constraint.valid: continue if object_transform is None: parent = obj.parent if parent is None: object_transform = obj.transform else: object_transform = parent.transform if self._is_location_affected(constraint, object_transform, obj.routing_surface): broadcaster.apply_broadcaster_effect(obj) if not obj.valid_for_distribution: is_affected = False break is_affected = True if not is_affected: if self._object_cache is not None: self._object_cache.discard(obj) for broadcaster in current_broadcasters: broadcaster.on_processed() finally: self._on_update_callbacks()
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 SituationGoal(SituationGoalDisplayMixin, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.SITUATION_GOAL)): INSTANCE_SUBCLASSES_ONLY = True IS_TARGETED = False INSTANCE_TUNABLES = { '_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), '_cancel_on_travel': Tunable( description= '\n If set, this situation goal will cancel (technically, complete\n with score overridden to 0 so that situation score is not\n progressed) if situation changes zone.\n ', tunable_type=bool, default=False, 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), 'score_on_iteration_complete': OptionalTunable( description= '\n If enabled then we will add an amount of score to the situation\n with every iteration of the situation goal completing.\n ', tunable=Tunable( description= '\n An amount of score that should be applied when an iteration\n completes.\n ', tunable_type=int, default=10)), '_pre_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 started.\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()), '_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()), 'noncancelable': Tunable( description= '\n Checking this box will prevent the player from canceling this goal in the whim system.', tunable_type=bool, 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_awarded_notification': OptionalTunable( description= '\n If enabled, this goal will have a notification associated with it.\n It is up to whatever system awards the goal (e.g. the Whim system)\n to display the notification when necessary.\n ', tunable=TunableUiDialogNotificationSnippet()), 'goal_completion_notification': OptionalTunable(tunable=UiDialogNotification.TunableFactory( description= '\n A TNS that will fire when this situation goal is completed.\n ' )), 'goal_completion_notification_and_modal_target': OptionalTunable( description= '\n If enabled then we will use the tuned situation job to pick a\n random sim in the owning situation with that job to be the target\n sim of the notification and modal dialog.\n ', tunable=TunableReference( description= '\n The situation job that will be used to find a sim in the owning\n situation to be the target sim.\n ', manager=services.get_instance_manager( sims4.resources.Types.SITUATION_JOB))), 'audio_sting_on_complete': TunableResourceKey( description= '\n The sound to play when this goal is completed.\n ', resource_types=(sims4.resources.Types.PROPX, ), default=None, allow_none=True, 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 ' )), 'visible_minor_goal': Tunable( description= '\n Whether or not this goal should be displayed in the minor goals\n list if this goal is for a player facing situation.\n ', tunable_type=bool, default=True, tuning_group=GroupNames.UI), 'display_type': TunableEnumEntry( description= '\n How this goal is presented in user-facing situations.\n ', tunable_type=SituationGoalDisplayType, default=SituationGoalDisplayType.NORMAL, tuning_group=GroupNames.UI) } @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 else: resolver = GlobalResolver() 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, 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 setup(self): self._setup = True def destroy(self): self.decommision() self._sim_info = None self._situation = None def decommision(self): if self._setup: self._decommision() 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 target_sim_info = self.get_required_target_sim_info() target_id = 0 if target_sim_info is None else target_sim_info.sim_id secondary_target_id = 0 if self._secondary_sim_info is None else self._secondary_sim_info.sim_id seedling = situations.situation_serialization.GoalSeedling( type(self), actor_id, target_id, secondary_target_id, self._count, self._locked, self._completed_time) 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_offered(self): if self._situation is None: return for sim in self._situation.all_sims_in_situation_gen(): resolver = sim.get_resolver() for loots in self._pre_goal_loot_list: for loot in loots.goal_loot_actions: loot.apply_to_resolver(resolver) def _display_goal_completed_dialogs(self): actor_sim_info = services.active_sim_info() target_sim_info = None if self.goal_completion_notification_and_modal_target is not None: possible_sims = list( self._situation.all_sims_in_job_gen( self.goal_completion_notification_and_modal_target)) if possible_sims: target_sim_info = random.choice(possible_sims) if target_sim_info is None: return resolver = DoubleSimResolver(actor_sim_info, target_sim_info) if self.goal_completion_notification is not None: notification = self.goal_completion_notification(actor_sim_info, resolver=resolver) notification.show_dialog() if self.goal_completion_modal_dialog is not None: dialog = self.goal_completion_modal_dialog(actor_sim_info, resolver=resolver) dialog.show_dialog() def _on_goal_completed(self, start_cooldown=True): if start_cooldown: self._completed_time = services.time_service().sim_now loot_sims = (self._sim_info, ) if self._situation is None else tuple( self._situation.all_sims_in_situation_gen()) for loots in self._goal_loot_list: for loot in loots.goal_loot_actions: for sim in loot_sims: loot.apply_to_resolver(sim.get_resolver()) self._display_goal_completed_dialogs() with situations.situation_manager.DelayedSituationDestruction(): self._on_goal_completed_callbacks(self, True) def _on_iteration_completed(self): self._on_goal_completed_callbacks(self, False) def force_complete(self, target_sim=None, score_override=None, start_cooldown=True): self._score_override = score_override self._count = self._iterations self._on_goal_completed(start_cooldown=start_cooldown) def _valid_event_sim_of_interest(self, sim_info): return self._sim_info is None or self._sim_info is sim_info def handle_event(self, sim_info, event, resolver): if not self._valid_event_sim_of_interest(sim_info): return if self._run_goal_completion_tests(sim_info, event, resolver): self._count += 1 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 should_autocomplete_on_load(self, previous_zone_id): if self._cancel_on_travel: zone_id = services.current_zone_id() if previous_zone_id != zone_id: return True return False def get_actual_target_sim_info(self): pass @property def sim_info(self): return self._sim_info def get_required_target_sim_info(self): pass def get_secondary_sim_info(self): return self._secondary_sim_info @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): target_sim_info = self.get_required_target_sim_info() return (self._numerical_token, self._sim_info, target_sim_info, self._secondary_sim_info) def get_display_name(self): display_name = self.display_name if display_name is not None: return display_name(*self.get_localization_tokens()) def get_display_tooltip(self): display_tooltip = self.display_tooltip if display_tooltip is not None: return display_tooltip(*self.get_localization_tokens()) @property def score(self): if self._score_override is not None: return self._score_override return self._score @property def goal_status_override(self): return self._goal_status_override @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 @property def locked(self): return self._locked def toggle_locked_status(self): self._locked = not self._locked def validate_completion(self): if self._completed_time is not None: return if self.completed_iterations < self.max_iterations: return self.force_complete() def show_goal_awarded_notification(self): if self.goal_awarded_notification is None: return icon_override = IconInfoData(icon_resource=self.display_icon) secondary_icon_override = IconInfoData(obj_instance=self._sim_info) notification = self.goal_awarded_notification(self._sim_info) notification.show_dialog( additional_tokens=self.get_localization_tokens(), icon_override=icon_override, secondary_icon_override=secondary_icon_override)
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 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 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