def __init__(self, target_default=ParticipantType.Object, locked_args={}, carry_target_default=ParticipantType.Object, **kwargs): super().__init__( TunableTuple( affordance=TunableReference( services.affordance_manager(), description= 'The affordance to push as a continuation on the actor for this SI.' ), si_affordance_override=TunableReference( services.affordance_manager(), description= "When the tuned affordance is a mixer for a different SI, use this to specify the mixer's appropriate SI. This is useful for pushing socials." ), actor=TunableEnumEntry( ParticipantType, ParticipantType.Actor, description='The Sim on which the affordance is pushed.'), target=TunableEnumEntry( ParticipantType, target_default, description='The participant the affordance will target.'), carry_target=OptionalTunable( TunableEnumEntry( ParticipantType, carry_target_default, description= 'The participant the affordance will set as a carry target.' )), locked_args=locked_args), **kwargs)
class TravelTuning: ENTER_LOT_AFFORDANCE = TunableReference(services.affordance_manager(), description='SI to push when sim enters the lot.') EXIT_LOT_AFFORDANCE = TunableReference(services.affordance_manager(), description='SI to push when sim is exiting the lot.') NPC_WAIT_TIME = TunableSimMinute(15, description='Delay in sim minutes before pushing the ENTER_LOT_AFFORDANCE on a NPC at the spawn point if they have not moved.') TRAVEL_AVAILABILITY_SIM_FILTER = TunableSimFilter.TunableReference(description='Sim Filter to show what Sims the player can travel with to send to Game Entry.') TRAVEL_SUCCESS_AUDIO_STING = TunablePlayAudio(description='\n The sound to play when we finish loading in after the player has traveled.\n ') NEW_GAME_AUDIO_STING = TunablePlayAudio(description='\n The sound to play when we finish loading in from a new game, resume, or\n household move in.\n ') GO_HOME_INTERACTION = TunableReference(description='\n The interaction to push a Sim to go home.\n ', manager=services.get_instance_manager(sims4.resources.Types.INTERACTION))
class TeleportLiability(Liability, HasTunableFactory, AutoFactoryInit): __qualname__ = 'TeleportLiability' LIABILITY_TOKEN = 'TeleportLiability' FACTORY_TUNABLES = { 'on_success_affordance': TunableReference( description= '\n If specified, the affordance to push if the teleportation was\n successful.\n ', manager=services.affordance_manager()), 'on_failure_affordance': TunableReference( description= '\n If specified, the affordance to push if the teleportation failed or\n if on_success_affordance is specified and failed to execute.\n ', manager=services.affordance_manager()) } def __init__(self, interaction, **kwargs): super().__init__(**kwargs) self._interaction = interaction self._interaction.route_fail_on_transition_fail = False self._constraint = self._interaction.constraint_intersection() @classmethod def on_affordance_loaded_callback(cls, affordance, liability_tuning): affordance.disable_distance_estimation_and_posture_checks = True def release(self): if self._teleport() and self.on_success_affordance is not None: if self._interaction.sim.push_super_affordance( self.on_success_affordance, self._interaction.target, self._interaction.context): return if self._interaction.transition_failed and self.on_failure_affordance is not None: self._interaction.sim.push_super_affordance( self.on_failure_affordance, self._interaction.target, self._interaction.context) def _teleport(self): polygon = None if self._constraint.geometry is None else self._constraint.geometry.polygon if isinstance(polygon, CompoundPolygon): scoring_functions = [ScoringFunctionPolygon(cp) for cp in polygon] else: scoring_functions = (ScoringFunctionPolygon(polygon), ) search_flags = FGLSearchFlagsDefault | FGLSearchFlag.USE_SIM_FOOTPRINT routing_surface = self._constraint.routing_surface fgl_context = FindGoodLocationContext( starting_position=self._constraint.average_position, scoring_functions=scoring_functions, starting_routing_surface=routing_surface, search_flags=search_flags) (translation, orientation) = find_good_location(fgl_context) if polygon and translation is not None and orientation is not None: self._interaction.sim.move_to(translation=translation, orientation=orientation, routing_surface=routing_surface) return True return False
class PickUpObjectSuperInteraction(SuperInteraction): INSTANCE_TUNABLES = { 'basic_content': TunableBasicContentSet(one_shot=True, no_content=True, default='no_content'), 'si_to_push': TunableReference(services.affordance_manager(), allow_none=True, description='SI to push after picking up the object.') } @classmethod def _constraint_gen(cls, *args, **kwargs): yield Constraint(debug_name='PickUpObjectSuperInteraction({})'.format( cls.si_to_push), posture_state_spec=CARRY_TARGET_POSTURE_STATE_SPEC) @classmethod def _test(cls, target, context, **kwargs): from sims.sim import Sim if isinstance(target.parent, Sim): return TestResult(False, 'Cannot pick up an object parented to a Sim.') if context.source == context.SOURCE_AUTONOMY and context.sim.posture_state.get_carry_track( target.definition.id) is not None: return TestResult( False, 'Sims should not autonomously pick up more than one object.') return TestResult.TRUE
class InteractionOfInterest(AutoFactoryInit): __qualname__ = 'InteractionOfInterest' FACTORY_TUNABLES = { 'affordances': TunableList( TunableReference(services.affordance_manager()), description= "The Sim must have started either any affordance in this list or an interaction matching one of the tags in this tunable's Tags field." ), 'tags': TunableSet( TunableEnumEntry(Tag, Tag.INVALID), description= 'The Sim must have run either an interaction matching one of these Tags or an affordance from the list of Affordances in this tunable.' ) } def get_expected_args(self): return {'interaction': event_testing.test_events.FROM_EVENT_DATA} def __call__(self, interaction=None): if interaction is not None and self.tags & interaction.get_category_tags( ): tag_match = True else: tag_match = False if not tag_match and interaction.affordance not in self.affordances: return TestResult(False, 'Failed affordance check: {} not in {}', interaction.affordance, self.affordances) return TestResult.TRUE
class VoodooSummonSituation(situations.situation_complex.SituationComplexCommon ): __qualname__ = 'VoodooSummonSituation' INSTANCE_TUNABLES = { 'summoned_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim summoned.\n ' ), come_to_me_state=RoleState.TunableReference( description= '\n The state for telling the summoned sim to come here.\n ' )), 'come_here_affordance': sims4.tuning.tunable.TunableReference( services.affordance_manager(), description='SI to bring summoned sim to the summoner.') } REMOVE_INSTANCE_TUNABLES = ( '_buff', '_cost', '_NPC_host_filter', '_NPC_hosted_player_tests', 'NPC_hosted_situation_start_message', 'NPC_hosted_situation_use_player_sim_as_filter_requester', 'NPC_hosted_situation_player_job', 'venue_types', 'venue_invitation_message', 'venue_situation_player_job', 'category', 'main_goal', 'minor_goal_chains', 'max_participants', '_initiating_sim_tests', '_icon', 'targeted_situation', '_resident_job', 'situation_description', 'job_display_ordering', 'entitlement', '_jobs_to_put_in_party', '_relationship_between_job_members', 'main_goal_audio_sting', 'audio_sting_on_start', '_level_data', '_display_name') @staticmethod def _states(): return [(1, _ComeHereState)] @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.summoned_job.situation_job, cls.summoned_job.come_to_me_state)] @classmethod def default_job(cls): return cls.summoned_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._summoned_sim = None def start_situation(self): super().start_situation() self._change_state(_ComeHereState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._summoned_sim = sim def _on_sim_removed_from_situation_prematurely(self, sim): super()._on_sim_removed_from_situation_prematurely(sim) self._summoned_sim = None
class InteractionOfInterest(AutoFactoryInit): __qualname__ = 'InteractionOfInterest' FACTORY_TUNABLES = { 'affordance': TunableReference( description= '\n The affordance that we are are timing for length of runtime.\n ', manager=services.affordance_manager(), class_restrictions='SuperInteraction'), 'tags': TunableSet( description= '\n A set of tags that will match an affordance instead of looking\n for a specific one.\n ', tunable=TunableEnumEntry(Tag, Tag.INVALID)), 'duration': TunableRange( description= '\n The amount of time in sim hours that this interaction has to\n run for this test to be considered passed.\n ', tunable_type=int, default=10, minimum=1) } def get_expected_args(self): return {'interaction': event_testing.test_events.FROM_EVENT_DATA} def __call__(self, interaction=None): if interaction.affordance is self.affordance: return TestResult.TRUE if self.tags & interaction.get_category_tags(): return TestResult.TRUE return TestResult( False, 'Failed affordance check: {} is not {} and does not have any matching tags in {}.', interaction.affordance, self.affordance, self.tags)
class PushInteractionOnAllGreetedSimsInteraction( interactions.base.super_interaction.SuperInteraction): __qualname__ = 'PushInteractionOnAllGreetedSimsInteraction' INSTANCE_TUNABLES = { '_pushed_interaction_tunables': TunableTuple( affordance_to_push=sims4.tuning.tunable.TunableReference( description= '\n Affordance to push on all sims in the household and all greeted\n sims.\n ', manager=services.affordance_manager()), push_on_actor=sims4.tuning.tunable.Tunable( description= '\n Whether Afforance To Push should be pushed on the actor.\n ', tunable_type=bool, default=False), target_override_for_pushed_affordance=OptionalTunable( TunableEnumEntry( description= '\n ParticipantType for the target to be set on the pushed\n affordance.\n ', tunable_type=ParticipantType, default=ParticipantType.Actor))), '_required_appropriateness_tags': TunableSet( description= '\n A list of tags that a Sim must have to be eligible for this\n interaction.\n ', tunable=TunableEnumEntry(tunable_type=tag.Tag, default=tag.Tag.INVALID)) } @classmethod def _test(cls, target, context, **interaction_parameters): sim = next(cls._target_sim_gen(context.sim), None) if sim is None: return event_testing.results.TestResult(False, 'No valid sims to call.') return super()._test(target, context, **interaction_parameters) def _run_interaction_gen(self, timeline): if self._pushed_interaction_tunables.target_override_for_pushed_affordance is not None: new_target = self.get_participant( self._pushed_interaction_tunables. target_override_for_pushed_affordance) else: new_target = self.target for target_sim in self._target_sim_gen(self.sim): target_context = self.context.clone_for_sim(target_sim) target_sim.push_super_affordance( self._pushed_interaction_tunables.affordance_to_push, new_target, target_context) return event_testing.results.ExecuteResult.NONE @classmethod def _target_sim_gen(cls, sim): for target_sim in services.sim_info_manager( ).instanced_sims_on_active_lot_gen(): while target_sim.Buffs.is_appropriate( cls._required_appropriateness_tags): if not cls._pushed_interaction_tunables.push_on_actor and target_sim is sim: pass yield target_sim
def MP_Chat_add_super_affordances(original, self): original(self) sa_list = [] affordance_manager = services.affordance_manager() for sa_id in MP_Chat_sa_instance_ids: tuning_class = affordance_manager.get(sa_id) if not tuning_class is None: sa_list.append(tuning_class) self._super_affordances = self._super_affordances + tuple(sa_list)
class EnvironmentScoreTuning: __qualname__ = 'EnvironmentScoreTuning' ENVIRONMENT_SCORE_BROADCASTER = BroadcasterEnvironmentScore.TunableReference( description= '\n The singleton broadcaster that groups all scoring objects. The\n constraints on this broadcaster determine the constraint within which a\n Sim is affected by environment score.\n ' ) ENVIRONMENT_SCORE_MOODS = TunableMapping( description= "\n Tags on Objects correspond to a particular Mood.\n \n When an object is going to contribute to the environment score, put a\n tag in it's catalog object, and make sure that tag points to a Mood\n here.\n ", key_type=TunableEnumEntry( description= '\n The Tag that corresponds to mood and environmental scoring data.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID), value_type=Mood.TunableReference( description= '\n The mood that the Sim must be in for an object that emits this mood\n to score. Corresponds to the mood_tag.\n ' ), key_name='object_tag', value_name='mood') NEGATIVE_ENVIRONMENT_SCORING = Commodity.TunableReference( description= '\n Defines the ranges and corresponding buffs to apply for negative\n environmental contribution.\n \n Be sure to tune min, max, and the different states. The convergence\n value is what will remove the buff. Suggested to be 0.\n ' ) POSITIVE_ENVIRONMENT_SCORING = Commodity.TunableReference( description= '\n Defines the ranges and corresponding buffs to apply for positive\n environmental contribution.\n \n Be sure to tune min, max, and the different states. The convergence\n value is what will remove the buff. Suggested to be 0.\n ' ) ENABLE_AFFORDANCE = TunableReference( description= '\n The interaction that will turn on Environment Score for a particular\n object. This interaction should set a state on the object that will\n have multipliers of 1 and adders of 0 for all moods.\n ', manager=services.affordance_manager()) DISABLE_AFFORDANCE = TunableReference( description= '\n The interaction that will turn off Environment Score for a particular\n object. This interaction should set a state on the object that will\n have multipliers of 0 and adders of 0 for all moods.\n ', manager=services.affordance_manager()) ENABLED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n A state value that indicates the object should be contributing\n Environment Score.\n ' ) DISABLED_STATE_VALUE = ObjectStateValue.TunableReference( description= '\n A state value that indicates the object should not be contributing\n Environment Score.\n ' )
def get_affordance(pack_safe=False): return { 'affordance': TunableReference( description= '\n The affordance to push on the subject.\n ', manager=services.affordance_manager(), class_restrictions=('SuperInteraction', ), pack_safe=pack_safe) }
class SpawnActionAffordance(HasTunableSingletonFactory, AutoFactoryInit): __qualname__ = 'SpawnActionAffordance' FACTORY_TUNABLES = { 'spawn_affordance': TunableReference( description= '\n The affordance that is pushed on the Sim as soon as they are spawned\n on the lot.\n ', manager=services.affordance_manager(), class_restrictions=('SuperInteraction', )) } def __call__(self, sim): context = InteractionContext(sim, InteractionSource.SCRIPT, Priority.Critical) return sim.push_super_affordance(self.spawn_affordance, None, context)
class TimeoutLiability(Liability, HasTunableFactory): LIABILITY_TOKEN = 'TimeoutLiability' FACTORY_TUNABLES = { 'description': 'Establish a timeout for this affordance. If it has not run when the timeout hits, cancel and push timeout_affordance, if set.', 'timeout': TunableSimMinute( 4, minimum=0, description= 'The time, in Sim minutes, after which the interaction is canceled and time_toute affordance is pushed, if set.' ), 'timeout_affordance': TunableReference( services.affordance_manager(), allow_none=True, description= 'The affordance to push when the timeout expires. Can be unset, in which case the interaction will just be canceled.' ) } def __init__(self, interaction, *, timeout, timeout_affordance, **kwargs): super().__init__(**kwargs) def on_alarm(*_, **__): if interaction.running: return if interaction.transition is not None and interaction.transition.running: return if timeout_affordance is not None: context = interaction.context.clone_for_continuation( interaction) interaction.sim.push_super_affordance(timeout_affordance, interaction.target, context) interaction.cancel( FinishingType.LIABILITY, cancel_reason_msg='Timeout after {} sim minutes.'.format( timeout)) time_span = clock.interval_in_sim_minutes(timeout) self._handle = alarms.add_alarm(self, time_span, on_alarm) def release(self): alarms.cancel_alarm(self._handle) def should_transfer(self, continuation): return False
class VoodooSummonSituation(situations.situation_complex.SituationComplexCommon ): INSTANCE_TUNABLES = { 'summoned_job': sims4.tuning.tunable.TunableTuple( situation_job=SituationJob.TunableReference( description= '\n A reference to the SituationJob used for the Sim summoned.\n ' ), come_to_me_state=RoleState.TunableReference( description= '\n The state for telling the summoned sim to come here.\n ' )), 'come_here_affordance': sims4.tuning.tunable.TunableReference( services.affordance_manager(), description='SI to bring summoned sim to the summoner.') } REMOVE_INSTANCE_TUNABLES = Situation.NON_USER_FACING_REMOVE_INSTANCE_TUNABLES @classmethod def _states(cls): return (SituationStateData(1, _ComeHereState), ) @classmethod def _get_tuned_job_and_default_role_state_tuples(cls): return [(cls.summoned_job.situation_job, cls.summoned_job.come_to_me_state)] @classmethod def default_job(cls): return cls.summoned_job.situation_job def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._summoned_sim = None def start_situation(self): super().start_situation() self._change_state(_ComeHereState()) def _on_set_sim_job(self, sim, job_type): super()._on_set_sim_job(sim, job_type) self._summoned_sim = sim def _on_sim_removed_from_situation_prematurely(self, sim, sim_job): self._summoned_sim = None
def inject_into_relationship_panel(cls, sim): affordance_manager = services.affordance_manager() injected_interactions = [] for interaction_id in cls.sim_interactions: interaction_class = affordance_manager.get(interaction_id) if interaction_class is None: Logger.log( 'interaction {} not found in affordance_manager'.format( interaction_id)) continue injected_interactions.append(interaction_class) sim._relation_panel_affordances = (sim._relation_panel_affordances + tuple(injected_interactions))
class PushAffordanceFromRole(HasTunableSingletonFactory, AutoFactoryInit): __qualname__ = 'PushAffordanceFromRole' FACTORY_TUNABLES = { 'description': '\n Push the specific affordance onto the sim.\n ', 'affordance': TunableReference(services.affordance_manager()), 'source': TunableEnumEntry(tunable_type=InteractionSource, default=InteractionSource.SCRIPT), 'priority': TunableEnumEntry(tunable_type=Priority, default=Priority.High, description='Priority to push the interaction'), 'run_priority': TunableEnumEntry( tunable_type=Priority, default=None, description= 'Priority to run the interaction. None means use the (push) priority' ), 'target': TunableEnumEntry( description= '\n The target of the affordance. We will try to get\n the target from the situation the role sim is\n running.\n ', tunable_type=SituationAffordanceTarget, default=SituationAffordanceTarget.NO_TARGET) } def __call__(self, role_state, role_affordance_target): sim = role_state.sim affordance = self.affordance source = self.source priority = self.priority run_priority = self.run_priority if run_priority is None: run_priority = priority interaction_context = InteractionContext(sim, source, priority, run_priority=run_priority) target = role_state._get_target_for_push_affordance( self.target, role_affordance_target=role_affordance_target) sim.push_super_affordance(affordance, target, interaction_context)
def generate_animation_element_data(*args, zone_id: int = None, **kwargs): affordance_manager = services.affordance_manager() animation_element_data = [] for (animation_element, usage) in get_animation_reference_usage().items(): animation_element_data.append({ 'animation_element_name': animation_element.__name__, 'count_interaction': usage[InteractionAsmType.Interaction], 'count_outcome': usage[InteractionAsmType.Outcome], 'count_response': usage[InteractionAsmType.Response], 'count_reactionlet': usage[InteractionAsmType.Reactionlet], 'count_total': sum(count for count in usage.values()) }) return animation_element_data
def writeauto_add_superaffordances(original, self): original(self) if self.TYPE == Types.OBJECT: # First, get a tuple containing the tunings for all the super affordances... affordance_manager = services.affordance_manager() sa_list = [] for sa_id in jay_writeautonomously_sa_instance_ids: key = sims4.resources.get_resource_key(sa_id, Types.INTERACTION) sa_tuning = affordance_manager.get(key) if not sa_tuning is None: sa_list.append(sa_tuning) sa_tuple = tuple(sa_list) # Now update the tunings for all the objects in our object list for obj_id in JAY_COMPUTER_IDS: key = sims4.resources.get_resource_key(obj_id, Types.OBJECT) obj_tuning = self._tuned_classes.get(key) if not obj_tuning is None: obj_tuning._super_affordances = obj_tuning._super_affordances + sa_tuple
class ChooseDeliverySuperInteraction(CraftingPhaseSuperInteractionMixin, SuperInteraction): __qualname__ = 'ChooseDeliverySuperInteraction' INSTANCE_TUNABLES = {'delivery_to_bar_affordance': TunableReference(services.affordance_manager(), class_restrictions=ServeDrinkToCounterSuperInteraction, description="Affordance used to deliver a drink to a slot, if the order sim doesn't sit at the barstool slot to the bar."), 'delivery_to_sit_drink_slot_affordance': TunableReference(services.affordance_manager(), class_restrictions=ServeDrinkToSitDrinkSlotSuperInteraction, description='The affordance to delivery a drink to the sit_drink slot, if the order sim sits at the barstool slot to the bar.')} @property def auto_goto_next_phase(self): return False @classmethod def is_guaranteed(cls, *args, **kwargs): return True def _pick_affordance(self, order_sim, object_info, context): deliver_part = None carry_track = object_info.carry_track deliver_to_bar = self.delivery_to_bar_affordance deliver_to_sit_drink_slot = self.delivery_to_sit_drink_slot_affordance if self.sim == order_sim or order_sim is None: return (deliver_to_bar, self.target, deliver_part, carry_track) order_surface_target = order_sim.posture_state.surface_target if order_surface_target is not None and order_surface_target.is_part: bar = None if self.target.is_part: bar = self.target.part_owner if order_surface_target.part_owner is bar: if order_surface_target.is_valid_for_placement(definition=object_info.definition): deliver_part = order_surface_target return (deliver_to_sit_drink_slot, self.target, deliver_part, carry_track) return (deliver_to_bar, self.target, deliver_part, carry_track) def _run_interaction_gen(self, timeline): (order_sim, recipe) = self.process.pop_order() object_info = recipe.final_product context = self.context.clone_for_continuation(self) (deliver_affordance, target, deliver_part, carry_track) = self._pick_affordance(order_sim, object_info, context) obj_info_copy = FrozenAttributeDict(object_info, carry_track=carry_track) new_process = self.process.copy_for_serve_drink(recipe) aop = AffordanceObjectPair(deliver_affordance, target, deliver_affordance, None, order_sim=order_sim, object_info=obj_info_copy, deliver_part=deliver_part, phase=self.process.phase, crafting_process=new_process) self._went_to_next_phase_or_finished_crafting = True return aop.test_and_execute(context)
class FinishWalkState(CommonInteractionCompletedSituationState): FACTORY_TUNABLES = { 'go_home_affordance': TunableReference( description= '\n The affordance that the dog walker runs to go home.\n ', manager=services.affordance_manager()) } def __init__(self, *args, go_home_affordance=None, **kwargs): super().__init__(*args, **kwargs) self.go_home_affordance = go_home_affordance def on_activate(self, reader=None): super().on_activate(reader=reader) walker = self.owner.get_walker() if walker is None or not walker.sim_info.lives_here: self.owner.walk_onward() context = InteractionContext(walker, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.NEXT) aop = interactions.aop.AffordanceObjectPair(self.go_home_affordance, walker, self.go_home_affordance, None) aop.test_and_execute(context) def _additional_tests(self, sim_info, event, resolver): walker = self.owner.get_walker() if walker is None or not sim_info.id == walker.id: return False return True def _on_interaction_of_interest_complete(self, **kwargs): self.owner.walk_onward() def timer_expired(self): self.owner.walk_onward()
class SpawnActionAffordance(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'spawn_affordance': TunableReference( description= '\n The affordance that is pushed on the Sim as soon as they are spawned\n on the lot.\n ', manager=services.affordance_manager(), class_restrictions=('SuperInteraction', )) } def __call__(self, sim): context = InteractionContext(sim, InteractionSource.SCRIPT, Priority.Critical) result = sim.push_super_affordance(self.spawn_affordance, None, context) if not result: logger.error('{} failed to run, with result {}. Fading {} in.', self.spawn_affordance, result, sim) sim.fade_in() result.interaction.add_liability( SpawnActionLiability.LIABILITY_TOKEN, SpawnActionLiability(sim, self.spawn_affordance)) return True
class MultipleSimInteractionOfInterest(AutoFactoryInit): __qualname__ = 'MultipleSimInteractionOfInterest' FACTORY_TUNABLES = { 'affordance': TunableReference( description= '\n The affordance in question that is being run by all the sims.\n ', manager=services.affordance_manager(), class_restrictions='SuperInteraction'), 'tags': TunableSet( description= '\n A set of tags that match the affordance being run by all the sims. \n ', tunable=TunableEnumWithFilter( tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=tag.INTERACTION_PREFIX)), 'sim_count': Tunable( description= '\n The number of sims simultaneously running the appropriate interactions.\n ', tunable_type=int, default=2) } def get_expected_args(self): return {'interaction': event_testing.test_events.FROM_EVENT_DATA} def __call__(self, interaction=None): if interaction.affordance is self.affordance: return TestResult.TRUE if self.tags & interaction.get_category_tags(): return TestResult.TRUE return TestResult( False, 'Failed affordance check: {} is not {} and does not have any matching tags in {}.', interaction.affordance, self.affordance, self.tags)
class InteractionOfInterest(AutoFactoryInit): FACTORY_TUNABLES = { 'affordances': TunableList( description= "\n The Sim must have started either any affordance in this list or an\n interaction matching one of the tags in this tunable's Tags\n field.\n ", tunable=TunableReference(services.affordance_manager(), pack_safe=True)), 'tags': TunableSet( description= '\n The Sim must have run either an interaction matching one of these\n Tags or an affordance from the list of Affordances in this\n tunable.', tunable=TunableEnumEntry(Tag, Tag.INVALID)) } expected_kwargs = (('interaction', event_testing.test_constants.FROM_EVENT_DATA), ) def get_expected_args(self): return dict(self.expected_kwargs) def __call__(self, interaction=None): if interaction is None: return TestResult(False, 'No affordance to check against {}', self.affordances) if self.tags & interaction.get_category_tags(): return TestResult.TRUE if interaction.affordance in self.affordances: return TestResult.TRUE return TestResult(False, 'Failed affordance check: {} not in {}', interaction.affordance, self.affordances) def custom_keys_gen(self): for affordance in self.affordances: yield affordance for tag in self.tags: yield tag
class PushAffordanceFromRole(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'description': '\n Push the specific affordance onto the sim.\n ', 'affordance': TunableReference(manager=services.affordance_manager()), 'super_affordance_for_mixer': OptionalTunable( description= '\n If we want to push mixer directly in the affordance tuning for this\n role state, we would need to provide a super affordance here to\n handle the mixer.\n ', tunable=TunableReference(manager=services.affordance_manager()), disabled_name='do_not_need_si', enabled_name='provide_si'), 'source': TunableEnumEntry(tunable_type=InteractionSource, default=InteractionSource.SCRIPT), 'priority': TunableEnumEntry( description= '\n Priority to push the interaction\n ', tunable_type=Priority, default=Priority.High), 'run_priority': TunableEnumEntry( description= '\n Priority to run the interaction. None means use the (push) priority\n ', tunable_type=Priority, default=None), 'target': TunableEnumEntry( description= '\n The target of the affordance. We will try to get\n the target from the situation the role sim is\n running.\n ', tunable_type=SituationAffordanceTarget, default=SituationAffordanceTarget.NO_TARGET), 'leave_situation_on_failure': Tunable( description= '\n If set to True, when push affordance on the sim fails, sim will\n leave the situation.\n ', tunable_type=bool, default=False), 'add_situation_liability': Tunable( description= '\n If set to True, we will add a liability to the pushed interaction\n such that we will cancel the situation owning this role state\n if the interaction (and its continuations) are completed or \n canceled.\n ', tunable_type=bool, default=False) } def __call__(self, role_state, role_affordance_target, situation=None, **kwargs): sim = role_state.sim affordance = self.affordance source = self.source priority = self.priority run_priority = self.run_priority if run_priority is None: run_priority = priority interaction_context = InteractionContext(sim, source, priority, run_priority=run_priority, **kwargs) target = role_state._get_target_for_push_affordance( self.target, situation=situation, role_affordance_target=role_affordance_target) try: push_result = False if affordance.is_super: push_result = sim.push_super_affordance( affordance, target, interaction_context) else: super_affordance = self.super_affordance_for_mixer if super_affordance is not None: potential_parent_si = sim.si_state.get_si_by_affordance( super_affordance) if potential_parent_si is not None: aop = AffordanceObjectPair(affordance, target, super_affordance, potential_parent_si) push_result = aop.test_and_execute(interaction_context) if push_result: if self.add_situation_liability: liability = SituationLiability(situation) push_result.interaction.add_liability( SITUATION_LIABILITY, liability) elif self.leave_situation_on_failure: situation_manager = services.get_zone_situation_manager() situation_manager.remove_sim_from_situation(sim, situation.id) except AttributeError: logger.error( 'Attribute Error occurred pushing interaction {} on sim: {} for role_state:{}', affordance, sim, role_state, owner='msantander') raise
class RoleState(HasDependentTunableReference, role.role_state_base.RoleStateBase, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.ROLE_STATE)): INSTANCE_TUNABLES = { '_role_priority': TunableEnumEntry( RolePriority, RolePriority.NORMAL, description= '\n The priority of this role state. All the role states with the\n same priority will all be applied together. The highest group\n of priorities is considered the active ones.\n ' ), '_buffs': TunableList( buffs.tunable.TunableBuffReference(pack_safe=True), description= '\n Buffs that will be added to sim when role is active.\n ' ), '_off_lot_autonomy_buff': buffs.tunable.TunableBuffReference( description= 'A buff that\n prevents autonomy from considering some objects based on the\n location of the object (e.g. on lot, off lot, within a radius of the\n sim).\n \n In the buff set: Game Effect Modifiers->Autonomy Modifier->Off Lot\n Autonomy Rule.\n ', allow_none=True), 'tags': TunableSet( TunableEnumEntry(Tag, Tag.INVALID), description= '\n Tags for the role state for checking role states against a set\n of tags rather than against a list of role states.\n ' ), 'role_affordances': TunableList( description= "\n A list of affordances that are available on the Sim in this Role\n State.\n \n e.g: when a Maid is in the working Role State, he or she will have\n the 'Dismiss' and 'Fire' affordances available in the Pie Menu.\n ", tunable=TunableReference(manager=services.affordance_manager(), class_restrictions=('SuperInteraction', ), pack_safe=True)), 'role_target_affordances': TunableList( description= '\n A list of affordances that are available on other Sims when the\n actor Sim is in this Role State.\n \n e.g. a Sim in a specific Role State could have an "Invite to\n Situation" interaction available when bringing up other Sims\' Pie\n Menus.\n ', tunable=TunableReference(manager=services.affordance_manager(), class_restrictions=('SuperInteraction', ), pack_safe=True)), 'preroll_affordances': TunableList( description= '\n A list of affordances that are available for sims to consider when\n running pre-roll. Objects related to role can specify preroll\n autonomy, but there are some roles that may not have an object\n associated with it\n \n e.g. Romance guru in romance festival preroll to an attractor point.\n ', tunable=TunableReference(manager=services.affordance_manager(), class_restrictions=('SuperInteraction', ), pack_safe=True)), '_on_activate': TunableVariant( description= '\n Select the autonomy behavior when this role state becomes active on the sim.\n disabled: Take no action.\n autonomy_ping: We explicitly force an autonomy ping on the sim.\n push_affordance: Push the specific affordance on the sim.\n ', locked_args={'disabled': None}, autonomy_ping=DoAutonomyPingFromRole.TunableFactory(), parameterized_autonomy_ping=DoParameterizedAutonomyPingFromRole. TunableFactory(), push_affordance=PushAffordanceFromRole.TunableFactory(), default='disabled'), '_portal_disallowance_tags': TunableSet( description= '\n A set of tags that define what the portal disallowance tags of\n this role state are. Portals that include any of these\n disallowance tags are considered locked for sims that have this\n role state.\n ', tunable=TunableEnumWithFilter( description= '\n A single portal disallowance tag.\n ', tunable_type=tag.Tag, default=tag.Tag.INVALID, filter_prefixes=tag.PORTAL_DISALLOWANCE_PREFIX)), '_allow_npc_routing_on_active_lot': Tunable( description= '\n If True, then npc in this role will be allowed to route on the\n active lot.\n If False, then npc in this role will not be allowed to route on the\n active lot, unless they are already on the lot when the role\n state is activated.\n \n This flag is ignored for player sims and npcs who live on the\n active lot.\n \n e.g. ambient walkby sims should not be routing on the active lot\n because that is rude.\n ', tunable_type=bool, default=True), '_autonomy_state_override': OptionalTunable( description= '\n If tuned, will force role sims into a specific autonomy state.\n Please consult your GPE partner before using this.\n ', tunable=TunableEnumEntry(tunable_type=AutonomyState, default=AutonomyState.LIMITED_ONLY, invalid_enums=(AutonomyState.MEDIUM, )), tuning_filter=FilterTag.EXPERT_MODE), '_crafting_process_override': TunableEnumEntry( description= '\n The override option of who to assign ownership of objects made\n by Sims in this role state.\n ', tunable_type=RoleStateCraftingOwnershipOverride, default=RoleStateCraftingOwnershipOverride.NO_OVERRIDE), 'always_active': Tunable( description= "\n If set to True, this role will always be allowed to be active\n when set on a Sim, regardless of whether or not it is \n lower priority than the Sim's other currently active roles. \n Use for roles that are important but retuning priority for it \n and/or other roles isn't feasible.\n \n Consult a GPE before you set this to True.\n This is not to be used lightly and there may be other options\n like situation exclusivity that can be explored before you\n go down this route.\n \n e.g. Sim is possessed which runs at HIGH priority.\n Sim wants to go visit an NPC residential lot, which places\n Sim in NORMAL priority Role_UngreetedPlayerVisitingNPC, which\n sets portal disallowance and adds specific buffs.\n \n We actually want Role_UngreetedPlayerVisitingNPC to run\n even though the role priority is now HIGH, because \n otherwise a possessed Sim visiting an NPC would magically\n be able to route through homes because portal disallowance\n is removed.\n ", tunable_type=bool, default=False) } @classmethod def _verify_tuning_callback(cls): for buff_ref in cls.buffs: if buff_ref is None: logger.error( '{} has empty buff in buff list. Please fix tuning.', cls) elif buff_ref.buff_type._temporary_commodity_info is not None: logger.error( '{} has a buff {} that has a temporary commodity.', cls, buff_ref.buff_type) @classproperty def role_priority(cls): return cls._role_priority @classproperty def buffs(cls): return cls._buffs @classproperty def off_lot_autonomy_buff(cls): return cls._off_lot_autonomy_buff @classproperty def role_specific_affordances(cls): return cls.role_affordances @classproperty def allow_npc_routing_on_active_lot(cls): return cls._allow_npc_routing_on_active_lot @classproperty def autonomy_state_override(cls): return cls._autonomy_state_override @classproperty def on_activate(cls): return cls._on_activate @classproperty def portal_disallowance_tags(cls): return cls._portal_disallowance_tags @classproperty def has_full_permissions(cls): current_venue = services.get_current_venue() if current_venue and current_venue.allow_rolestate_routing_on_navmesh: return True return not cls._portal_disallowance_tags and cls._allow_npc_routing_on_active_lot def _get_target_for_push_affordance(self, situation_target, situation=None, role_affordance_target=None): if situation_target == SituationAffordanceTarget.NO_TARGET: return if situation_target == SituationAffordanceTarget.CRAFTED_OBJECT: return role_affordance_target if situation_target == SituationAffordanceTarget.TARGET_OBJECT and situation is not None: return situation.get_target_object() if situation_target == SituationAffordanceTarget.CREATED_OBJECT and situation is not None: return situation.get_created_object() logger.error( 'Unable to resolve target when trying to push affordance on role state {} activate. requested target type was {}', self, self._on_activate.target) @classproperty def active_household_crafting_override(cls): return cls._crafting_process_override == RoleStateCraftingOwnershipOverride.ACTIVE_HOUSEHOLD @classproperty def lot_owner_crafting_override(cls): return cls._crafting_process_override == RoleStateCraftingOwnershipOverride.LOT_OWNER
class CarryableComponent( Component, HasTunableFactory, AutoFactoryInit, component_name=objects.components.types.CARRYABLE_COMPONENT): class _CarryableAllowedHands(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'biped_allowed_hands': TunableVariant(locked_args={ 'both': (Hand.LEFT, Hand.RIGHT), 'left_only': (Hand.LEFT, ), 'right_only': (Hand.RIGHT, ) }, default='both'), 'quadruped_allowed_hands': TunableVariant(locked_args={ 'both': (Hand.LEFT, Hand.RIGHT), 'mouth_only': (Hand.RIGHT, ), 'back_only': (Hand.LEFT, ) }, default='mouth_only') } def get_allowed_hands(self, sim): if sim is None: return self.biped_allowed_hands return sim.get_allowed_hands_type(self) class _CarryableTransitionConstraint(HasTunableSingletonFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'constraint_mobile': TunableList( description= '\n The constraint to use when the Sim is in a mobile posture.\n ', tunable=TunableGeometricConstraintVariant( disabled_constraints={ 'spawn_points', 'relative_circle', 'current_position' })), 'constraint_non_mobile': TunableList( description= '\n The constraint to use when the Sim is not in a mobile posture.\n ', tunable=TunableGeometricConstraintVariant( disabled_constraints={ 'spawn_points', 'relative_circle', 'current_position' })) } DEFAULT_GEOMETRIC_TRANSITION_CONSTRAINT = _CarryableTransitionConstraint.TunableFactory( description= '\n Unless specifically overridden, the constraint to use when transitioning\n into and out of a carry for any carryable object.\n ' ) DEFAULT_GEOMETRIC_TRANSITION_LARGE = TunableRange( description= '\n This is a large transition distance. This is used by:\n * TYAE humans picking up any pet\n ', tunable_type=float, default=0.7, minimum=0) DEFAULT_GEOMETRIC_TRANSITION_MEDIUM = TunableRange( description= '\n This is a medium transition distance. This is used by:\n * TYAE humans picking up P humans\n ', tunable_type=float, default=0.6, minimum=0) DEFAULT_GEOMETRIC_TRANSITION_SMALL = TunableRange( description= '\n This is a small transition distance. This is used by:\n * C humans picking up AE cats and AE small dogs\n ', tunable_type=float, default=0.503, minimum=0) DEFAULT_GEOMETRIC_TRANSITION_TINY = TunableRange( description= '\n This is a tiny transition distance. This is used by:\n * C humans picking up C cats and C dogs and small dogs\n ', tunable_type=float, default=0.419, minimum=0) DEFAULT_CARRY_AFFORDANCES = TunableList( description= '\n The list of default carry affordances.\n ', tunable=TunableReference(manager=services.get_instance_manager( sims4.resources.Types.INTERACTION))) PUT_IN_INVENTORY_AFFORDANCE = TunableReference( description= '\n The affordance used by carryable component to put objects in inventory.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)) PUT_DOWN_HERE_AFFORDANCE = TunableReference( description= '\n The affordance used by carryable component to put down here via the\n PutDownLiability liability.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)) PUT_DOWN_ANYWHERE_AFFORDANCE = TunableReference( description= '\n The affordance used by carryable component to put down objects anywhere\n via the PutDownLiability liability.\n ', manager=services.get_instance_manager( sims4.resources.Types.INTERACTION)) FACTORY_TUNABLES = { 'put_down_tuning': TunableVariant( description= '\n Define how Sims prefer to put this object down.\n ', reference=TunablePutDownStrategySpeciesMapping( description= '\n Define specific costs for all the possible locations this object\n could be put down. Also, define, if necessary, custom\n interactions to put the object down.\n \n If this is not specified, and the default is used, the most\n appropriate put down strategy is used. This allows the object to\n be put down in most places (floor, inventory, slots). Also,\n species might specify their own custom behavior.\n ' ), locked_args={'use_default': None}, default='use_default'), 'state_based_put_down_tuning': TunableMapping( description= '\n A mapping from a state value to a putdownstrategy. If the owning\n object is in any of the states tuned here, it will use that state\'s\n associated putdownstrategy in place of the one putdownstrategy tuned\n in the "put_down_tuning" field. If the object is in multiple states\n listed in this mapping, the behavior is undefined.\n ', key_type=TunableReference( description= '\n The state value this object must be in in order to use the\n associated putdownstrategy.\n ', manager=services.get_instance_manager( sims4.resources.Types.OBJECT_STATE)), value_type= TunableVariant(reference=TunablePutDownStrategySpeciesMapping( description= '\n Tuning for how to score where a Sim might want to set an\n object down.\n ' )), key_name='State', value_name='PutDownStrategy'), 'carry_affordances': OptionalTunable(tunable=TunableList(tunable=TunableReference( description= '\n The versions of the HoldObject affordance that this object\n supports.\n ', manager=services.affordance_manager())), disabled_name='use_default_affordances', enabled_name='use_custom_affordances'), 'provided_affordances': TunableProvidedAffordances( description= '\n Affordances that are generated when a Sim holding this object\n selects another object to interact with. The generated interactions\n target the selected object, and have their carry target set to the\n component\'s owner.\n \n By default, this is applied to Sims. e.g.: The "Drink" interaction\n provides "Make Toast" on other Sims.\n \n Optionally, you can specify a tag to have the interaction appear on\n other objects. e.g.: The "Hold Puppy" interaction might unlock "Put\n Down Here" on sofas.\n \n Target defaults to the object selected (Invalid). Carry Target is\n locked to this object being carried. If is_linked is\n checked, the affordance will be linked to the interaction\n carrying this object.\n ', class_restrictions=('SuperInteraction', ), locked_args={ 'allow_self': False, 'target': ParticipantType.Object, 'carry_target': ParticipantType.CarriedObject }), 'constraint_pick_up': OptionalTunable( description= '\n A list of constraints that must be fulfilled in order to\n interact with this object.\n ', tunable=TunableList(tunable=TunableConstraintVariant( description= '\n A constraint that must be fulfilled in order to\n interact with this object.\n ' ))), 'allowed_hands_data': _CarryableAllowedHands.TunableFactory(), 'holster_while_routing': Tunable( description= '\n If True, the Sim will holster the object before routing and\n unholster when the route is complete.\n ', tunable_type=bool, default=False), 'holster_compatibility': TunableAffordanceFilterSnippet( description= '\n Define interactions for which holstering this object is\n explicitly disallowed.\n \n e.g. The Scythe is tuned to be holster-incompatible with\n sitting, meaning that Sims will holster the Sctyhe when sitting.\n ' ), 'unholster_when_routing': TunableUnholsterWhileRoutingBehaviorVariant(), 'prefer_owning_sim_inventory_when_not_on_home_lot': Tunable( description= "\n If checked, this object will highly prefer to be put into the\n owning Sim's inventory when being put down by the owning Sim on\n a lot other than their home lot.\n \n Certain objects, like consumables, should be exempt from this.\n ", tunable_type=bool, default=True), 'is_valid_posture_graph_object': Tunable( description= '\n If this is checked, this object is allowed to provide postures and\n expand in the posture graph, despite its ability to be carried.\n \n Normally, for the performance reasons, carryable objects are not\n posture providing objects. The preferred way of interacting with a\n carryable object is marking it as a carry requirement in the ASM.\n \n However, there are objects for which this is not possible. For\n example, Nesting Blocks are carryable, but toddlers interact with\n them while in the "Sit On Ground" posture, which must be provided by\n the object\'s parts.\n \n This field should be used with caution, since posture graph\n generation is one of our slowest systems. Do not enable it on\n objects such as food or drinks, since the world is bound to have\n many of them.\n ', tunable_type=bool, default=False), 'portal_key_mask_flags': TunableEnumFlags( description= "\n Any flag tuned here will be kept on the Sim routing context who's\n picking up this object. This will allow a Sim to pickup some\n type of objects and still be allowed to transition through\n some portals while carrying an object.\n ", enum_type=PortalFlags, allow_no_flags=True), 'reslot_plumbbob': OptionalTunable( description= '\n If tuned, the plumbbob will be repositioned when this item is carried.\n Reslot will always go away when sim stops carrying the object.\n ', tunable=TunableReslotPlumbbob()), 'defer_putdown': Tunable( description= '\n If true, the put down will be deferred to the end of the route. \n If false, put down will be done at the start of the route. This\n should be the default behavior. \n ', tunable_type=bool, default=False), 'put_down_height_tolerance': TunableRange( description= '\n Maximum height tolerance on the terrain we will use for the \n placement of this object when asking FGL to find a spot on the\n floor.\n Having a high value here will make it so an object can be placed\n in a terrain spot with a high slope that might result with\n clipping depending on the width of the object. The way this will\n work will be using the object footprint, if the edges are at a\n height higher than the height tolerance, then the location will\n not be valid.\n ', tunable_type=float, default=1.1, minimum=0) } def __init__(self, owner, **kwargs): super().__init__(owner, **kwargs) self._attempted_putdown = False self._attempted_alternative_putdown = False self._cached_put_down_strategy = None @property def attempted_putdown(self): return self._attempted_putdown @property def attempted_alternative_putdown(self): return self._attempted_alternative_putdown @property def ideal_slot_type_set(self): put_down_strategy = self.get_put_down_strategy() return put_down_strategy.ideal_slot_type_set @componentmethod def get_carry_object_posture(self): if self.carry_affordances is None: return CarryPostureStaticTuning.POSTURE_CARRY_OBJECT return self.carry_affordances[0].provided_posture_type @componentmethod_with_fallback(lambda *_, **__: ()) def get_allowed_hands(self, sim): return self.allowed_hands_data.get_allowed_hands(sim) @componentmethod_with_fallback(lambda *_, **__: 0) def get_portal_key_make_for_carry(self): return self.portal_key_mask_flags @componentmethod def should_unholster(self, *args, **kwargs): return self.unholster_when_routing.should_unholster(*args, **kwargs) @componentmethod def get_put_down_strategy(self, parent=DEFAULT): if self._cached_put_down_strategy is None: parent = self.owner.parent if parent is DEFAULT else parent species = parent.species if parent is not None else Species.HUMAN for (state_value, put_down_strategy ) in self.state_based_put_down_tuning.items(): if self.owner.state_value_active(state_value): self._cached_put_down_strategy = put_down_strategy.get( species) break else: if self.put_down_tuning is not None: self._cached_put_down_strategy = self.put_down_tuning.get( species) if self._cached_put_down_strategy is None: put_down_strategy = parent.get_default_put_down_strategy() self._cached_put_down_strategy = put_down_strategy return self._cached_put_down_strategy def _get_carry_transition_distance_for_sim(self, sim): carry_sim = self.owner.sim_info carrying_sim = sim.sim_info if carry_sim.is_toddler: return self.DEFAULT_GEOMETRIC_TRANSITION_MEDIUM if carrying_sim.is_teen_or_older: return self.DEFAULT_GEOMETRIC_TRANSITION_LARGE if carry_sim.is_teen_or_older: return self.DEFAULT_GEOMETRIC_TRANSITION_SMALL return self.DEFAULT_GEOMETRIC_TRANSITION_TINY def _get_adjusted_circle_constraint(self, sim, constraint): if not self.owner.is_sim: return constraint if not isinstance(constraint, TunedCircle): return constraint ideal_radius_override = self._get_carry_transition_distance_for_sim( sim) constraint = copy.copy(constraint) constraint.ideal_radius_width = constraint.ideal_radius_width / constraint.ideal_radius * ideal_radius_override constraint.radius = constraint.radius / constraint.ideal_radius * ideal_radius_override constraint.ideal_radius = ideal_radius_override return constraint @componentmethod def get_carry_transition_constraint(self, sim, position, routing_surface, cost=0, mobile=True): constraints = self.DEFAULT_GEOMETRIC_TRANSITION_CONSTRAINT constraints = constraints.constraint_mobile if mobile else constraints.constraint_non_mobile final_constraint = Anywhere() for constraint in constraints: if mobile: constraint = self._get_adjusted_circle_constraint( sim, constraint) final_constraint = final_constraint.intersect( constraint.create_constraint(None, None, target_position=position, routing_surface=routing_surface)) final_constraint = final_constraint.generate_constraint_with_cost(cost) final_constraint = final_constraint._copy(_multi_surface=True) return final_constraint @componentmethod def get_pick_up_constraint(self, sim): if self.constraint_pick_up is None: return final_constraint = Anywhere() for constraint in self.constraint_pick_up: constraint = self._get_adjusted_circle_constraint(sim, constraint) constraint = constraint.create_constraint(sim, target=self.owner) final_constraint = final_constraint.intersect(constraint) final_constraint = final_constraint._copy(_multi_surface=True) return final_constraint @componentmethod def get_provided_aops_gen(self, target, context, **kwargs): for provided_affordance_data in self.provided_affordances: if not provided_affordance_data.affordance.is_affordance_available( context=context): continue if not provided_affordance_data.object_filter.is_object_valid( target): continue if provided_affordance_data.affordance.is_social and not target.is_sim: affordance = provided_affordance_data.affordance interaction_target = self.owner preferred_objects = (target, ) else: affordance = CarryTargetInteraction.generate( provided_affordance_data.affordance, self.owner) interaction_target = target preferred_objects = () depended_on_si = None parent = self.owner.parent if parent is not None: if parent.is_sim: carry_posture = parent.posture_state.get_carry_posture( self.owner) if carry_posture is not None: if provided_affordance_data.is_linked: depended_on_si = carry_posture.source_interaction yield from affordance.potential_interactions( interaction_target, context, depended_on_si=depended_on_si, preferred_objects=preferred_objects, **kwargs) def component_super_affordances_gen(self, **kwargs): if self.carry_affordances is None: affordances = self.DEFAULT_CARRY_AFFORDANCES else: affordances = self.carry_affordances for affordance in affordances: yield affordance def component_interactable_gen(self): yield self def on_state_changed(self, state, old_value, new_value, from_init): if new_value in self.state_based_put_down_tuning or old_value in self.state_based_put_down_tuning: self._cached_put_down_strategy = None def component_reset(self, reset_reason): self.reset_put_down_count() @componentmethod def get_initial_put_down_position(self, carrying_sim=None): carrying_sim = carrying_sim or self.owner.parent if carrying_sim is None: return (self.owner.position, self.owner.routing_surface) additional_put_down_distance = carrying_sim.posture.additional_put_down_distance position = carrying_sim.position + carrying_sim.forward * ( carrying_sim.object_radius + additional_put_down_distance) sim_los_constraint = carrying_sim.lineofsight_component.constraint if not sims4.geometry.test_point_in_compound_polygon( position, sim_los_constraint.geometry.polygon): position = carrying_sim.position return (position, carrying_sim.routing_surface) @componentmethod def get_put_down_aop(self, interaction, context, alternative_multiplier=1, own_inventory_multiplier=1, object_inventory_multiplier=DEFAULT, in_slot_multiplier=DEFAULT, on_floor_multiplier=1, visibility_override=None, display_name_override=None, additional_post_run_autonomy_commodities=None, add_putdown_liability=False, **kwargs): sim = interaction.sim owner = self.owner if owner.transient: return self._get_destroy_aop(sim, **kwargs) put_down_strategy = self.get_put_down_strategy(parent=sim) if object_inventory_multiplier is DEFAULT: object_inventory_multiplier = sim.get_put_down_object_inventory_cost_override( ) if in_slot_multiplier is DEFAULT: in_slot_multiplier = sim.get_put_down_slot_cost_override() slot_types_and_costs = self.get_slot_types_and_costs( multiplier=in_slot_multiplier) (terrain_transform, terrain_routing_surface) = self._get_terrain_transform(interaction) objects = self._get_objects_with_inventory(interaction) objects = [obj for obj in objects if obj.can_access_for_putdown(sim)] if put_down_strategy.floor_cost is not None and on_floor_multiplier is not None: world_cost = put_down_strategy.floor_cost * on_floor_multiplier else: world_cost = None if put_down_strategy.inventory_cost is not None and own_inventory_multiplier is not None: sim_inventory_cost = put_down_strategy.inventory_cost * own_inventory_multiplier else: sim_inventory_cost = None if put_down_strategy.object_inventory_cost is not None and object_inventory_multiplier is not None: object_inventory_cost = put_down_strategy.object_inventory_cost * object_inventory_multiplier else: object_inventory_cost = None if not put_down_strategy.affordances: self._attempted_alternative_putdown = True if not self._attempted_alternative_putdown or self.owner.is_sim: self._attempted_alternative_putdown = True scored_aops = [] for scored_aop in self._gen_affordance_score_and_aops( interaction, slot_types_and_costs=slot_types_and_costs, world_cost=world_cost, sim_inventory_cost=sim_inventory_cost, object_inventory_cost=object_inventory_cost, terrain_transform=terrain_transform, terrain_routing_surface=terrain_routing_surface, objects_with_inventory=objects, visibility_override=visibility_override, display_name_override=display_name_override, additional_post_run_autonomy_commodities= additional_post_run_autonomy_commodities, multiplier=alternative_multiplier, add_putdown_liability=add_putdown_liability): if scored_aop.aop.test(context): scored_aops.append(scored_aop) if scored_aops: scored_aops.sort(key=operator.itemgetter(0)) return scored_aops[-1].aop affordance = CarryableComponent.PUT_DOWN_ANYWHERE_AFFORDANCE if add_putdown_liability: liabilities = ((PutDownLiability.LIABILITY_TOKEN, PutDownLiability(self.owner)), ) else: liabilities = () aop = AffordanceObjectPair( affordance, self.owner, affordance, None, slot_types_and_costs=slot_types_and_costs, world_cost=world_cost, sim_inventory_cost=sim_inventory_cost, object_inventory_cost=object_inventory_cost, terrain_transform=terrain_transform, terrain_routing_surface=terrain_routing_surface, objects_with_inventory=objects, visibility_override=visibility_override, display_name_override=display_name_override, additional_post_run_autonomy_commodities= additional_post_run_autonomy_commodities, liabilities=liabilities, **kwargs) self._attempted_putdown = True return aop def _gen_affordance_score_and_aops( self, interaction, slot_types_and_costs, world_cost, sim_inventory_cost, object_inventory_cost, terrain_transform, terrain_routing_surface, objects_with_inventory, visibility_override, display_name_override, additional_post_run_autonomy_commodities, multiplier=1, add_putdown_liability=False): put_down_strategy = self.get_put_down_strategy() for affordance in put_down_strategy.affordances: if add_putdown_liability: liabilities = ((PutDownLiability.LIABILITY_TOKEN, PutDownLiability(self.owner)), ) else: liabilities = () aop = AffordanceObjectPair( affordance, self.owner, affordance, None, slot_types_and_costs=slot_types_and_costs, world_cost=world_cost, sim_inventory_cost=sim_inventory_cost, object_inventory_cost=object_inventory_cost, terrain_transform=terrain_transform, terrain_routing_surface=terrain_routing_surface, objects_with_inventory=objects, visibility_override=visibility_override, display_name_override=display_name_override, additional_post_run_autonomy_commodities= additional_post_run_autonomy_commodities, liabilities=liabilities) yield ScoredAOP(multiplier, aop) def _get_cost_for_slot_type(self, slot_type): put_down_strategy = self.get_put_down_strategy() if slot_type in self.owner.ideal_slot_types: return put_down_strategy.preferred_slot_cost return put_down_strategy.normal_slot_cost def get_slot_types_and_costs(self, multiplier=1): slot_types_and_costs = [] for slot_type in self.owner.all_valid_slot_types: cost = self._get_cost_for_slot_type(slot_type) if cost is not None and multiplier is not None: cost *= multiplier else: cost = None slot_types_and_costs.append((slot_type, cost)) return slot_types_and_costs def _get_terrain_transform(self, interaction): if not self.owner.is_sim and self.owner.footprint_component is None: return (None, None) else: sim = interaction.sim put_down_position = interaction.interaction_parameters.get( 'put_down_position') put_down_routing_surface = interaction.interaction_parameters.get( 'put_down_routing_surface') if put_down_position is None: (starting_position, starting_routing_surface ) = self.get_initial_put_down_position(carrying_sim=sim) else: starting_position = put_down_position starting_routing_surface = put_down_routing_surface starting_location = placement.create_starting_location( position=starting_position, orientation=sim.orientation, routing_surface=starting_routing_surface) if self.owner.is_sim: search_flags = FGLSearchFlagsDefaultForSim | FGLSearchFlag.STAY_IN_CURRENT_BLOCK fgl_context_fn = functools.partial( placement.create_fgl_context_for_sim, search_flags=search_flags) else: search_flags = FGLSearchFlag.STAY_IN_CURRENT_BLOCK | FGLSearchFlag.SHOULD_TEST_ROUTING | FGLSearchFlag.CALCULATE_RESULT_TERRAIN_HEIGHTS | FGLSearchFlag.DONE_ON_MAX_RESULTS | FGLSearchFlag.SHOULD_TEST_BUILDBUY fgl_context_fn = functools.partial( placement.create_fgl_context_for_object, search_flags=search_flags) MAX_PUTDOWN_STEPS = 8 MAX_PUTDOWN_DISTANCE = 10 fgl_context = fgl_context_fn( starting_location, self.owner, max_steps=MAX_PUTDOWN_STEPS, max_distance=MAX_PUTDOWN_DISTANCE, height_tolerance=self.put_down_height_tolerance) (position, orientation) = placement.find_good_location(fgl_context) if position is not None: put_down_transform = sims4.math.Transform( position, orientation) return (put_down_transform, starting_routing_surface) return (None, None) def _get_objects_with_inventory(self, interaction): objects = [] inventory_item = self.owner.inventoryitem_component if inventory_item is not None: if CarryableComponent.PUT_IN_INVENTORY_AFFORDANCE is not None: for obj in inventory_item.valid_object_inventory_gen(): objects.append(obj) return objects def _get_destroy_aop(self, sim, **kwargs): affordance = CarryableComponent.PUT_DOWN_HERE_AFFORDANCE return AffordanceObjectPair(affordance, self.owner, affordance, None, put_down_transform=None, **kwargs) def reset_put_down_count(self): self._attempted_alternative_putdown = False self._attempted_putdown = False self._cached_put_down_strategy = None def on_object_carry(self, actor, *_, **__): if self.reslot_plumbbob is not None: reslot_plumbbob(actor, self.reslot_plumbbob) def on_object_uncarry(self, actor, *_, **__): if self.reslot_plumbbob is not None: unslot_plumbbob(actor)
class _BehaviorActionRunInteraction(_BehaviorAction): FACTORY_TUNABLES = {'affordance': TunableReference(description='\n The interaction to push.\n ', manager=services.affordance_manager()), 'cancel_trigger_interaction': Tunable(description="\n If this is checked, once this interaction has completed, we'll\n attempt to cancel the triggering interaction. If multiple\n interactions are triggered (e.g. by multiple behaviors or multiple\n slaves), the last interaction to complete cancels the interaction.\n ", tunable_type=bool, default=False)} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._interaction = None def execute_behavior_action(self, resolver=None, **event_data): for si in self._slave.si_state: if si.get_liability(_BehaviorActionRunInteractionLiability.LIABILITY_TOKEN) is not None: return context = InteractionContext(self._slave, InteractionContext.SOURCE_SCRIPT, Priority.Low) result = self._slave.push_super_affordance(self.affordance, None, context) if not result: return self._interaction = result.interaction cancel_trigger_liability = None if self.cancel_trigger_interaction: trigger_interaction = resolver.interaction if resolver is not None else None if trigger_interaction is not None: cancel_trigger_liability = trigger_interaction.get_liability(_BehaviorActionCancelInteractionLiability.LIABILITY_TOKEN) if cancel_trigger_liability is None: cancel_trigger_liability = _BehaviorActionCancelInteractionLiability(trigger_interaction) trigger_interaction.add_liability(_BehaviorActionCancelInteractionLiability.LIABILITY_TOKEN, cancel_trigger_liability) cancel_trigger_liability.add_triggered_interaction() liability = _BehaviorActionRunInteractionLiability(self, cancel_trigger_liability) self._interaction.add_liability(_BehaviorActionRunInteractionLiability.LIABILITY_TOKEN, liability) def stop_behavior_action(self, *, from_release): if from_release: return if self._interaction is not None and not self._interaction.is_finishing: self._interaction.cancel(FinishingType.NATURAL, cancel_reason_msg='Slaved Sim required to route.')
class _BehaviorTriggerInteractionStart(_BehaviorTrigger): FACTORY_TUNABLES = {'affordance': TunableReference(description='\n The trigger is fired if the master runs this specific interaction.\n ', manager=services.affordance_manager())} def get_events(self): return (TestEvent.InteractionStart,) def _callback(self, resolver): if resolver.interaction is None: return if resolver.interaction.get_interaction_type() is not self.affordance: return return super()._callback(resolver=resolver)
class CurfewComponent(Component, HasTunableFactory, AutoFactoryInit, component_name=CURFEW_COMPONENT): FACTORY_TUNABLES = { 'curfew_state_reference': TunableStateTypeReference( description= '\n This is a reference to the State type we will be manipulating when\n we change states on this object.\n ' ), 'times_state_reference': TunableStateTypeReference( description= '\n This is a reference to the State type we will be manipulating when\n we change states on this object.\n ' ), 'curfew_not_set': TunableStateValueReference( description= '\n This is the reference to the state to apply on the owning object\n when there is no active curfew setting. Or the setting is UNSET.\n ' ), 'curfew_warning_state': TunableStateValueReference( description= '\n This is the reference to the state to apply to the owning object\n when the curfew is about to start.\n ' ), 'curfew_past_state': TunableStateValueReference( description= '\n This is the reference to the state to apply to the owning object\n when curfew is active.\n ' ), 'curfew_on_state': TunableStateValueReference( description= '\n This is the reference to the state to apply to the owning object\n when the curfew is set but not currently active.\n ' ), 'not_set_state': TunableStateValueReference( description= "\n This is the reference to the state to apply to the owning object\n when there isn't a curfew set at all.\n " ), 'times_set': TunableMapping( description= '\n This is a Mapping of time (in military time) to state to apply to\n the owning object in order to display the correct time that the\n curfew is set for.\n ', key_type=int, value_type=TunableStateValueReference()), 'set_curfew_affordances': TunableList( description= '\n A List of the interactions that will be used to set the curfew\n via this object.\n ', tunable=TunableReference( description= '\n This is the interaction that will be used to "set" the curfew.\n ', manager=services.affordance_manager(), class_restrictions=('SuperInteraction', ))) } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._current_curfew_setting = None def on_add(self): curfew_service = services.get_curfew_service() current_curfew = curfew_service.get_zone_curfew( services.current_zone_id()) if current_curfew not in self.times_set: pass self.apply_state_for_setting(current_curfew) self.apply_warning_state(current_curfew) self._register_for_alarms(curfew_service) def on_remove(self): curfew_service = services.get_curfew_service() self._unregister_for_alarms(curfew_service) def component_super_affordances_gen(self, **kwargs): yield from self.set_curfew_affordances def update_states(self, curfew_setting): if curfew_setting == self._current_curfew_setting: return self.apply_state_for_setting(curfew_setting) self.apply_warning_state(curfew_setting) def apply_state_for_setting(self, setting): if setting is CurfewService.UNSET: self.owner.set_state(self.times_state_reference, self.not_set_state) state_to_apply = self.times_set.get(setting) if state_to_apply is not None: self.owner.set_state(self.times_state_reference, state_to_apply) def apply_warning_state(self, curfew_setting): if curfew_setting is CurfewService.UNSET: self.owner.set_state(self.curfew_state_reference, self.curfew_not_set) return now = services.time_service().sim_now.hour() if now >= CurfewService.CURFEW_END_TIME and now < curfew_setting: self._on_curfew_over_alarm() elif now == curfew_setting - 1: self._on_warning_time_alarm() else: self._on_curfew_started_alarm() def _register_for_alarms(self, curfew_service): curfew_service.register_for_alarm_callbacks( self._on_warning_time_alarm, self._on_curfew_started_alarm, self._on_curfew_over_alarm, self.update_states) def _unregister_for_alarms(self, curfew_service): curfew_service.unregister_for_alarm_callbacks( self._on_warning_time_alarm, self._on_curfew_started_alarm, self._on_curfew_over_alarm, self.update_states) def _on_warning_time_alarm(self): self.owner.set_state(self.curfew_state_reference, self.curfew_warning_state, force_update=True) def _on_curfew_started_alarm(self): self.owner.set_state(self.curfew_state_reference, self.curfew_past_state, force_update=True) def _on_curfew_over_alarm(self): self.owner.set_state(self.curfew_state_reference, self.curfew_on_state, force_update=True)
def __init__(self, target_default=ParticipantType.Object, locked_args={}, carry_target_default=ParticipantType.Object, **kwargs): super().__init__(TunableTuple(affordance=TunableReference(services.affordance_manager(), description='The affordance to push as a continuation on the actor for this SI.'), si_affordance_override=TunableReference(services.affordance_manager(), description="When the tuned affordance is a mixer for a different SI, use this to specify the mixer's appropriate SI. This is useful for pushing socials."), actor=TunableEnumEntry(ParticipantType, ParticipantType.Actor, description='The Sim on which the affordance is pushed.'), target=TunableEnumEntry(ParticipantType, target_default, description='The participant the affordance will target.'), carry_target=OptionalTunable(TunableEnumEntry(ParticipantType, carry_target_default, description='The participant the affordance will set as a carry target.')), locked_args=locked_args), **kwargs)
class ObjectRelationshipInteraction(ImmediateSuperInteraction): INSTANCE_TUNABLES = { 'object_variant_mapping': TunableMapping( description= '\n Map of the possible object relationship tracks, each representing\n an object, and the associated interaction that will be pushed when \n selected.\n ', key_type=TunableReference( manager=services.statistic_manager(), class_restrictions='ObjectRelationshipTrack', pack_safe=True), value_type=TunableReference(manager=services.affordance_manager(), class_restrictions='SuperInteraction', pack_safe=True)), 'custom_name_string_wrapper': OptionalTunable( description= '\n If tuned, if the object relationship name has been set\n with the name component, then the display name of the interaction\n will be wrapped into this string.\n \n It should be written like this, with the object name\n token indexed at 0:\n "Summon 0.String" \n ', tunable=TunableLocalizedStringFactory()) } def __init__(self, aop, context, track=None, **kwargs): super().__init__(aop, context, **kwargs) self.rel_track = track @flexmethod def _get_name(cls, inst, target=DEFAULT, context=DEFAULT, track=None, **interaction_parameters): if track is None: return inst.create_localized_string(inst.display_name) tag_set = None if track in ObjectRelationshipTrack.OBJECT_BASED_FRIENDSHIP_TRACKS: tag_set = ObjectRelationshipTrack.OBJECT_BASED_FRIENDSHIP_TRACKS[ track] if tag_set is not None: relationship_service = services.relationship_service() relationship = relationship_service.get_object_relationship( context.sim.id, tag_set) if relationship is not None: object_name = relationship.get_object_rel_name() if object_name: if cls.custom_name_string_wrapper is not None: return cls.custom_name_string_wrapper( LocalizationHelperTuning.get_raw_text(object_name)) return LocalizationHelperTuning.get_raw_text(object_name) if track in cls.object_variant_mapping: return cls.create_localized_string( cls.object_variant_mapping[track].display_name, context=context, target=target) return inst.create_localized_string(inst.display_name) @classmethod def potential_interactions(cls, target, context, from_inventory_to_owner=False, **kwargs): if not cls.allow_autonomous and context.source == InteractionSource.AUTONOMY: return () yield for (rel_track, affordance) in cls.object_variant_mapping.items(): tag_set = None tag_set = ObjectRelationshipTrack.OBJECT_BASED_FRIENDSHIP_TRACKS[ rel_track] if not rel_track in ObjectRelationshipTrack.OBJECT_BASED_FRIENDSHIP_TRACKS or tag_set is not None: result = affordance.test(target=target, context=context) if result or result.tooltip is not None: yield AffordanceObjectPair( cls, target, cls, None, track=rel_track, from_inventory_to_owner=from_inventory_to_owner) @flexmethod def test(cls, inst, target=DEFAULT, context=DEFAULT, super_interaction=None, skip_safe_tests=False, **interaction_parameters): rel_track = interaction_parameters.get('track') if rel_track is not None and rel_track in cls.object_variant_mapping: affordance = cls.object_variant_mapping[rel_track].affordance if affordance is not None: return affordance.test(target=target, context=context) def _run_interaction_gen(self, timeline): if self.rel_track is None: return False yield context = InteractionContext(self.sim, InteractionContext.SOURCE_PIE_MENU, Priority.High) result = self.sim.push_super_affordance( self.object_variant_mapping[self.rel_track], self.target, context) return True yield
def generate_animation_element_data(*args, zone_id:int=None, **kwargs): affordance_manager = services.affordance_manager() animation_element_data = [] for (animation_element, usage) in get_animation_reference_usage().items(): animation_element_data.append({'animation_element_name': animation_element.__name__, 'count_interaction': usage[InteractionAsmType.Interaction], 'count_outcome': usage[InteractionAsmType.Outcome], 'count_response': usage[InteractionAsmType.Response], 'count_reactionlet': usage[InteractionAsmType.Reactionlet], 'count_total': sum(count for count in usage.values())}) return animation_element_data
AFFORDANCE_FILTER = 'affordance_filter' AFFORDANCE_LIST = 'affordance_list' ANIMATION = 'animation' ANIMATION_LIST = 'animation_list' ANIMATION_TRIPLET = 'animation_triplet' ANIMATION_TRIPLET_LIST = 'animation_triplet_list' COLOR = 'color' POSTURE_TYPE_LIST = 'posture_type_list' OBJECT_LIST = 'objects_list' VENUE_LIST = 'venue_list' SCREEN_SLAM = 'screen_slam' (TunableAffordanceListReference, TunableAffordanceListSnippet) = define_snippet( AFFORDANCE_LIST, TunableList(TunableReference(services.affordance_manager(), needs_tuning=True))) (TunableVenueListReference, TunableVenueListSnippet) = define_snippet( VENUE_LIST, TunableList(TunableReference(manager=services.get_instance_manager( sims4.resources.Types.VENUE), tuning_group=GroupNames.VENUES))) class _TunableAffordanceFilter(TunableFactory, is_fragment=True): __qualname__ = '_TunableAffordanceFilter' @staticmethod def _filter(affordance, default_inclusion, affordance_types=None): affordance = affordance.affordance include_all_by_default = default_inclusion.include_all_by_default
AFFORDANCE_FILTER = 'affordance_filter' AFFORDANCE_LIST = 'affordance_list' ANIMATION = 'animation' ANIMATION_LIST = 'animation_list' ANIMATION_TRIPLET = 'animation_triplet' ANIMATION_TRIPLET_LIST = 'animation_triplet_list' COLOR = 'color' POSTURE_TYPE_LIST = 'posture_type_list' OBJECT_LIST = 'objects_list' VENUE_LIST = 'venue_list' SCREEN_SLAM = 'screen_slam' ANIMATION_ACTOR_MAP = 'animation_actor_map' MUSIC_TRACK_DATA = 'music_track_data' CUSTOM_STATES_SITUATION_STATE = 'custom_states_situation_state' (TunableAffordanceListReference, TunableAffordanceListSnippet) = define_snippet(AFFORDANCE_LIST, TunableList(TunableReference(services.affordance_manager(), pack_safe=True))) (TunableVenueListReference, TunableVenueListSnippet) = define_snippet(VENUE_LIST, TunableList(TunableReference(manager=services.get_instance_manager(sims4.resources.Types.VENUE), pack_safe=True, tuning_group=GroupNames.VENUES))) class _TunableAffordanceFilter(TunableFactory, is_fragment=True): @staticmethod def _filter(affordance, default_inclusion, affordance_types=None, allow_ignore_exclude_all=False): affordance = affordance.affordance if affordance_types is None: if hasattr(affordance, '__mro__'): affordance_types = set(affordance.__mro__) else: affordance_types = {affordance} excluded_affordances = set(default_inclusion.exclude_affordances) for affordance_list in default_inclusion.exclude_lists: excluded_affordances.update(affordance_list)
def __init__(self, description='Holds information about carrying and putting down an object.', **kwargs): super().__init__(put_down_tuning=TunableVariant(reference=TunableReference(description='\n Tuning for how to score where a Sim might want to set an\n object down.\n ', manager=services.get_instance_manager(sims4.resources.Types.STRATEGY)), literal=TunablePutDownStrategy().TunableFactory(), default='literal'), state_based_put_down_tuning=TunableMapping(description='\n A mapping from a state value to a putdownstrategy. If the\n owning object is in any of the states tuned here, it will use\n that state\'s associated putdownstrategy in place of the one\n putdownstrategy tuned in the "put_down_tuning" field. If the\n object is in multiple states listed in this mapping, the\n behavior is undefined.\n ', key_type=TunableReference(description='\n The state value this object must be in in order to use the\n associated putdownstrategy.\n ', manager=services.get_instance_manager(sims4.resources.Types.OBJECT_STATE)), value_type=TunableVariant(reference=TunableReference(description='\n Tuning for how to score where a Sim might want to set\n an object down.\n ', manager=services.get_instance_manager(sims4.resources.Types.STRATEGY)), literal=TunablePutDownStrategy().TunableFactory()), key_name='State', value_name='PutDownStrategy'), carry_affordances=OptionalTunable(TunableList(TunableReference(description='\n The versions of the HoldObject affordance that this object\n supports.\n ', manager=services.affordance_manager())), disabled_name='use_default_affordances', enabled_name='use_custom_affordances'), provided_affordances=TunableList(description='\n A list of affordances that are generated when a Sim holding\n this object selects another Sim to interact with. The generated\n interactions will target the selected Sim but will have this\n object set as their carry target.\n ', tunable=TunableReference(manager=services.affordance_manager())), constraint_pick_up=OptionalTunable(description='\n A list of constraints that must be fulfilled in order to\n interact with this object.\n ', tunable=TunableList(tunable=interactions.constraints.TunableConstraintVariant(description='\n A constraint that must be fulfilled in order to\n interact with this object.\n '))), allowed_hands=TunableVariant(locked_args={'both': (Hand.LEFT, Hand.RIGHT), 'left_only': (Hand.LEFT,), 'right_only': (Hand.RIGHT,)}, default='both'), holster_while_routing=Tunable(description='\n If True, the Sim will holster the object before routing and\n unholster when the route is complete.\n ', tunable_type=bool, default=False), holster_compatibility=TunableAffordanceFilterSnippet(description='\n Define interactions for which holstering this object is\n explicitly disallowed.\n \n e.g. The Scythe is tuned to be holster-incompatible with\n sitting, meaning that Sims will holster the Sctyhe when sitting.\n '), unholster_on_long_route_only=Tunable(description='\n If True, then the Sim will not unholster this object (assuming\n it was previously holstered) unless a transition involving a\n long route is about to happen.\n \n If False, then the standard holstering rules apply.\n ', tunable_type=bool, default=False), prefer_owning_sim_inventory_when_not_on_home_lot=Tunable(description="\n If checked, this object will highly prefer to be put into the\n owning Sim's inventory when being put down by the owning Sim on\n a lot other than their home lot.\n \n Certain objects, like consumables, should be exempt from this.\n ", tunable_type=bool, default=True), description=description, **kwargs)