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' })) }
class Broadcaster(HasTunableReference, RouteEventProviderMixin, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager( sims4.resources.Types.BROADCASTER)): class _BroadcasterObjectFilter(HasTunableSingletonFactory, AutoFactoryInit): def is_affecting_objects(self): raise NotImplementedError def can_affect_object(self, obj): raise NotImplementedError class _BroadcasterObjectFilterNone(HasTunableSingletonFactory): def __str__(self): return 'Nothing' def is_affecting_objects(self): return (False, None) def can_affect_object(self, obj): return False class _BroadcasterObjectFilterFlammable(HasTunableSingletonFactory): def __str__(self): return 'Flammable Objects' def is_affecting_objects(self): return (True, {FireTuning.FLAMMABLE_TAG}) def can_affect_object(self, obj): target_object_tags = obj.get_tags() if FireTuning.FLAMMABLE_TAG in target_object_tags: return True return False class _BroadcasterObjectFilterTags(HasTunableSingletonFactory, AutoFactoryInit): def __str__(self): return '{}'.format(', '.join(str(tag) for tag in self.tags)) FACTORY_TUNABLES = { 'tags': TunableSet( description= '\n An object with any tag in this set can be affected by the\n broadcaster.\n ', tunable=TunableEnumEntry( description= '\n A tag.\n ', tunable_type=Tag, default=Tag.INVALID, pack_safe=True)) } def is_affecting_objects(self): return (True, self.tags) def can_affect_object(self, obj): target_object_tags = obj.get_tags() if self.tags & target_object_tags: return True return False FREQUENCY_ENTER = 0 FREQUENCY_PULSE = 1 INSTANCE_TUNABLES = { 'clock_type': TunableEnumEntry( description= "\n Denotes whether broadcasters of this type are managed in real time\n or game time.\n \n Most broadcasters should be managed in Game Time because they will\n update with the speed of the game, including performance dips, and\n speed changes. However, Real Time broadcasters are more performant\n because they will only update based on the frequency of real time.\n You should use real time updates if the broadcaster is enabled for\n the lifetime of the object, there are a lot of that type, or timing\n doesn't matter as much.\n \n One Shot Interaction broadcasters should always be Game Time.\n Environment Score broadcasters should be in Real Time. Consult an\n engineer if you have questions.\n ", tunable_type=BroadcasterClockType, default=BroadcasterClockType.GAME_TIME), 'constraints': TunableList( description= '\n A list of constraints that define the area of influence of this\n broadcaster. It is required that at least one constraint be defined.\n ', tunable=TunableGeometricConstraintVariant( constraint_locked_args={'multi_surface': True}, circle_locked_args={'require_los': False}, disabled_constraints={'spawn_points', 'current_position'}), minlength=1), 'effects': TunableList( description= '\n A list of effects that are applied to Sims and objects affected by\n this broadcaster.\n ', tunable=TunableBroadcasterEffectVariant()), 'route_events': TunableList( description= "\n Specify any route events that are triggered when the Sim follows a\n path that has points within this broadcaster's constraints.\n ", tunable=RouteEvent.TunableReference( description= '\n A Route Event that is to be played when a Sim plans a route\n through this broadcaster.\n ', pack_safe=True)), 'frequency': TunableVariant( description= '\n Define in what instances and how often this broadcaster affects Sims\n and objects in its area of influence.\n ', on_enter=TunableTuple( description= '\n Sims and objects are affected by this broadcaster when they\n enter in its area of influence, or when the broadcaster is\n created.\n ', locked_args={'frequency_type': FREQUENCY_ENTER}, allow_multiple=Tunable( description= "\n If checked, then Sims may react multiple times if they re-\n enter the broadcaster's area of influence. If unchecked,\n then Sims will only react to the broadcaster once.\n ", tunable_type=bool, default=False)), on_pulse=TunableTuple( description= '\n Sims and objects are constantly affected by this broadcaster\n while they are in its area of influence.\n ', locked_args={'frequency_type': FREQUENCY_PULSE}, cooldown_time=TunableSimMinute( description= '\n The time interval between broadcaster pulses. Sims would not\n react to the broadcaster for at least this amount of time\n while in its area of influence.\n ', default=8)), default='on_pulse'), 'clustering': OptionalTunable( description= '\n If set, then similar broadcasters, i.e. broadcasters of the same\n instance, will be clustered together if their broadcasting objects\n are close by. This improves performance and avoids having Sims react\n multiple times to similar broadcasters. When broadcasters are\n clustered together, there is no guarantee as to what object will be\n used for testing purposes.\n \n e.g. Stinky food reactions are clustered together. A test on the\n broadcaster should not, for example, differentiate between a stinky\n lobster and a stinky steak, because the broadcasting object is\n arbitrary and undefined.\n \n e.g. Jealousy reactions are not clustered together. A test on the\n broadcaster considers the relationship between two Sims. Therefore,\n it would be unwise to consider an arbitrary Sim if two jealousy\n broadcasters are close to each other.\n ', tunable=ObjectClusterRequest.TunableFactory( description= '\n Specify how clusters for this particular broadcaster are formed.\n ', locked_args={'minimum_size': 1}), enabled_by_default=True), 'allow_objects': TunableVariant( description= '\n If enabled, then in addition to all instantiated Sims, some objects\n will be affected by this broadcaster. Some tuned effects might still\n only apply to Sims (e.g. affordance pushing).\n \n Setting this tuning field has serious performance repercussions. Its\n indiscriminate use could undermine our ability to meet Minspec\n goals. Please use this sparingly.\n ', disallow=_BroadcasterObjectFilterNone.TunableFactory(), from_tags=_BroadcasterObjectFilterTags.TunableFactory(), from_flammable=_BroadcasterObjectFilterFlammable.TunableFactory(), default='disallow'), 'allow_sims': Tunable( description= '\n If checked then this broadcaster will consider Sims. This is on by\n default. \n \n If neither allow_objects or allow_sims is checked that will result\n in a tuning error.\n ', tunable_type=bool, default=True), 'allow_sim_test': OptionalTunable( description= '\n If enabled, allows for a top level test set to determine which\n sims can be affected by the broadcaster at all.\n ', tunable=TunableTestSet()), 'immediate': Tunable( description= '\n If checked, this broadcaster will trigger a broadcaster update when\n added to the service. This adds a performance cost so please use\n this sparingly. This can be used for one-shot interactions that\n generate broadcasters because the update interval might be excluded\n in the interaction duration.\n ', tunable_type=bool, default=False) } @classmethod def _verify_tuning_callback(cls): (allow_objects, _) = cls.allow_objects.is_affecting_objects() if not cls.allow_sims and not allow_objects: logger.error( 'Broadcaster {} is tuned to not allow any objects as targets.', cls) @classmethod def register_static_callbacks(cls, *args, **kwargs): for broadcaster_effect in cls.effects: broadcaster_effect.register_static_callbacks(*args, **kwargs) @classmethod def get_broadcaster_service(cls): current_zone = services.current_zone() if current_zone is not None: if cls.clock_type == BroadcasterClockType.GAME_TIME: return current_zone.broadcaster_service if cls.clock_type == BroadcasterClockType.REAL_TIME: return current_zone.broadcaster_real_time_service raise NotImplementedError def __init__(self, *args, broadcasting_object, interaction=None, **kwargs): super().__init__(*args, **kwargs) self._broadcasting_object_ref = weakref.ref( broadcasting_object, self._on_broadcasting_object_deleted) self._interaction = interaction self._constraint = None self._affected_objects = weakref.WeakKeyDictionary() self._current_objects = weakref.WeakSet() self._linked_broadcasters = weakref.WeakSet() broadcasting_object.register_on_location_changed( self._on_broadcasting_object_moved) self._quadtree = None self._cluster_request = None @property def broadcasting_object(self): if self._broadcasting_object_ref is not None: return self._broadcasting_object_ref() @property def interaction(self): return self._interaction @property def quadtree(self): return self._quadtree @property def cluster_request(self): return self._cluster_request def _on_broadcasting_object_deleted(self, _): broadcaster_service = self.get_broadcaster_service() if broadcaster_service is not None: broadcaster_service.remove_broadcaster(self) def _on_broadcasting_object_moved(self, *_, **__): self.regenerate_constraint() broadcaster_service = self.get_broadcaster_service() if broadcaster_service is not None: broadcaster_service.update_cluster_request(self) def on_processed(self): for affected_object in self._affected_objects: if affected_object not in self._current_objects: self.remove_broadcaster_effect(affected_object) self._current_objects.clear() def on_removed(self): for affected_object in self._affected_objects: self.remove_broadcaster_effect(affected_object) broadcasting_object = self.broadcasting_object if broadcasting_object is not None: for broadcaster_effect in self.effects: if broadcaster_effect.apply_when_removed: broadcaster_effect.apply_broadcaster_loot(self) broadcasting_object.unregister_on_location_changed( self._on_broadcasting_object_moved) def on_added_to_quadtree_and_cluster_request(self, quadtree, cluster_request): self._quadtree = quadtree self._cluster_request = cluster_request def can_affect(self, obj): if obj.is_sim: if not self.allow_sims: return False elif not self.allow_objects.can_affect_object(obj): return False broadcasting_object = self.broadcasting_object if broadcasting_object is None: return False if obj is broadcasting_object: return False elif any(obj is linked_broadcaster.broadcasting_object for linked_broadcaster in self._linked_broadcasters): return False return True def on_event_executed(self, route_event, sim): super().on_event_executed(route_event, sim) if self.can_affect(sim): self.apply_broadcaster_effect(sim) def is_route_event_valid(self, route_event, time, sim, path): if not self.can_affect(sim): return False constraint = self.get_constraint() if not constraint.valid or constraint.geometry is None: return False else: (transform, routing_surface) = path.get_location_data_at_time(time) if not (constraint.geometry.test_transform(transform) and constraint.is_routing_surface_valid(routing_surface)): return False return True def apply_broadcaster_effect(self, affected_object): if affected_object.is_sim and self.allow_sim_test and not self.allow_sim_test.run_tests( SingleSimResolver(affected_object.sim_info)): return self._current_objects.add(affected_object) if self._should_apply_broadcaster_effect(affected_object): self._affected_objects[affected_object] = ( services.time_service().sim_now, True) for broadcaster_effect in self.effects: if affected_object in self._affected_objects: broadcaster_effect.apply_broadcaster_effect( self, affected_object) for linked_broadcaster in self._linked_broadcasters: if affected_object in self._affected_objects: linked_broadcaster._apply_linked_broadcaster_effect( affected_object, self._affected_objects[affected_object]) def _apply_linked_broadcaster_effect(self, affected_object, data): self._apply_linked_broadcaster_data(affected_object, data) for broadcaster_effect in self.effects: if broadcaster_effect.apply_when_linked: broadcaster_effect.apply_broadcaster_effect( self, affected_object) def _apply_linked_broadcaster_data(self, affected_object, data): if affected_object in self._affected_objects: was_in_area = self._affected_objects[affected_object][1] is_in_area = data[1] if was_in_area and not is_in_area: self.remove_broadcaster_effect(affected_object) self._affected_objects[affected_object] = data def remove_broadcaster_effect(self, affected_object, is_linked=False): affected_object_data = self._affected_objects.get(affected_object) if affected_object_data is None: return if not affected_object_data[1]: return self._affected_objects[affected_object] = (affected_object_data[0], False) self._current_objects.discard(affected_object) for broadcaster_effect in self.effects: if not broadcaster_effect.apply_when_linked: if not is_linked: broadcaster_effect.remove_broadcaster_effect( self, affected_object) broadcaster_effect.remove_broadcaster_effect(self, affected_object) if not is_linked: for linked_broadcaster in self._linked_broadcasters: linked_broadcaster.remove_broadcaster_effect(affected_object, is_linked=True) def _should_apply_broadcaster_effect(self, affected_object): if self.frequency.frequency_type == self.FREQUENCY_ENTER: if affected_object not in self._affected_objects: return True elif self.frequency.allow_multiple and not self._affected_objects[ affected_object][1]: return True return False if self.frequency.frequency_type == self.FREQUENCY_PULSE: last_reaction = self._affected_objects.get(affected_object, None) if last_reaction is None: return True else: time_since_last_reaction = services.time_service( ).sim_now - last_reaction[0] if time_since_last_reaction.in_minutes( ) > self.frequency.cooldown_time: return True else: return False return False def clear_linked_broadcasters(self): self._linked_broadcasters.clear() def set_linked_broadcasters(self, broadcasters): self.clear_linked_broadcasters() self._linked_broadcasters.update(broadcasters) for linked_broadcaster in self._linked_broadcasters: linked_broadcaster.clear_linked_broadcasters() for (obj, data) in linked_broadcaster._affected_objects.items(): if obj not in self._affected_objects: self._affected_objects[obj] = data for linked_broadcaster in self._linked_broadcasters: for (obj, data) in self._affected_objects.items(): linked_broadcaster._apply_linked_broadcaster_data(obj, data) def get_linked_broadcasters_gen(self): yield from self._linked_broadcasters def regenerate_constraint(self, *_, **__): self._constraint = None def get_constraint(self): if not (self._constraint is None or not self._constraint.valid): self._constraint = Anywhere() for tuned_constraint in self.constraints: self._constraint = self._constraint.intersect( tuned_constraint.create_constraint( None, target=self.broadcasting_object, target_position=self.broadcasting_object.position)) return self._constraint def get_resolver(self, affected_object): return DoubleObjectResolver(affected_object, self.broadcasting_object) def get_clustering(self): broadcasting_object = self.broadcasting_object if broadcasting_object is None: return if broadcasting_object.is_sim: return if broadcasting_object.is_in_inventory(): return if broadcasting_object.routing_surface is None: return return self.clustering def should_cluster(self): return self.get_clustering() is not None def get_affected_object_count(self): return sum(1 for data in self._affected_objects.values() if data[1]) @property def id(self): return self.broadcaster_id @property def lineofsight_component(self): return _BroadcasterLosComponent(self) @property def position(self): return self.broadcasting_object.position @property def routing_surface(self): return self.broadcasting_object.routing_surface @property def parts(self): return self.broadcasting_object.parts
class SimsInConstraintTests(HasTunableSingletonFactory, AutoFactoryInit, event_testing.test_base.BaseTest): test_events = () @staticmethod def _verify_tunable_callback(instance_class, tunable_name, source, value): value._validate_recursion(value.test_set, source, tunable_name) FACTORY_TUNABLES = {'verify_tunable_callback': _verify_tunable_callback, 'constraints': TunableList(description='\n A list of constraints that, when intersected, will be used to find\n all Sims we care about.\n ', tunable=TunableGeometricConstraintVariant(description='\n A constraint that will determine what Sims to test.\n '), minlength=1), 'must_be_line_of_sight': Tunable(description="\n If enabled, sims that succeed in the LOS test won't fail other tests when determining\n the test results.\n ", tunable_type=bool, default=False), 'constraints_target': TunableEnumEntry(description='\n The target used to generate constraints relative to.\n ', tunable_type=ParticipantTypeSingle, default=ParticipantTypeSingle.Object), 'test_actor': TunableEnumEntry(description='\n The actor used to test Sims in the constraint relative to.\n ', tunable_type=ParticipantTypeSingle, default=ParticipantTypeSingle.Object), 'test_set': sims4.tuning.tunable.TunableReference(description='\n A test set instance that will be run on all Sims in the tuned\n constraint. If any Sims fail the test set instance, this test will\n fail.\n \n Note: A DoubleSimResolver will be used to run these tests. So the\n Test Actor will be the Actor participant, and Target will be a Sim\n in the constraint.\n ', manager=services.get_instance_manager(sims4.resources.Types.SNIPPET), class_restrictions=('TestSetInstance',))} @classmethod @assertions.not_recursive def _validate_recursion(cls, test_set_instance, source, tunable_name): for test_group in test_set_instance.test: for test in test_group: if isinstance(test, cls): try: cls._validate_recursion(test.test_set, source, tunable_name) except AssertionError: logger.error('{} is a test set instance in {}: {} but that creates a circular dependency', test.test_set, source, tunable_name, owner='rmccord') def get_expected_args(self): return {'constraint_targets': self.constraints_target, 'test_actors': self.test_actor} @cached_test def __call__(self, constraint_targets=(), test_actors=()): test_actor = test_actors[0] if test_actors else None sim_info_manager = services.sim_info_manager() instanced_sims = list(sim_info_manager.instanced_sims_gen()) for target in constraint_targets: if target.is_sim: target = target.get_sim_instance() if target is None: continue else: total_constraint = Anywhere() for tuned_constraint in self.constraints: total_constraint = total_constraint.intersect(tuned_constraint.create_constraint(None, target)) if not total_constraint.valid: return TestResult(False, 'Constraint {} relative to {} is invalid.', tuned_constraint, target, tooltip=self.tooltip) object_constraint = interactions.constraints.Position(target._get_locations_for_posture_internal_forward_wall_padding(), routing_surface=target.routing_surface) for sim in instanced_sims: if not (total_constraint.geometry.test_transform(sim.transform) and (total_constraint.is_routing_surface_valid(sim.routing_surface) and (total_constraint.is_location_water_depth_valid(sim.location) and (total_constraint.is_location_terrain_tags_valid(sim.location) and not self.test_set(DoubleSimResolver(test_actor, sim.sim_info))))) and not (self.must_be_line_of_sight and sim.sim_info is test_actor)): if not object_constraint.intersect(sim.lineofsight_component.constraint).valid: continue return TestResult(False, 'Sims In Constraint Test Failed.', tooltip=self.tooltip) return TestResult.TRUE
class ReactionTriggerElement(XevtTriggeredElement): FACTORY_TUNABLES = { 'reaction_affordance': TunableReference( description= '\n The affordance to push on other Sims.\n ', manager=services.affordance_manager()), 'reaction_target': TunableEnumEntry( description= '\n The subject of this interaction that will be set as the target of the pushed reaction_affordance.\n ', tunable_type=ParticipantType, default=ParticipantType.Actor), 'reaction_constraints': TunableList( description= '\n The constraints that Sims on the lot have to satisfy such that reaction_affordance is pushed on them.\n ', tunable=TunableGeometricConstraintVariant( description= '\n The constraints that Sims on the lot have to satisfy such that reaction_affordance is pushed on them.\n ', constraint_locked_args={'multi_surface': True}, circle_locked_args={'require_los': False}, disabled_constraints={'spawn_points', 'current_position'})), 'trigger_on_late_arrivals': Tunable( description= '\n If checked, Sims entering the reaction area after the reaction is\n first triggered will also react, up until when the interaction is\n canceled.\n ', tunable_type=bool, default=False) } def __init__(self, interaction, *args, sequence=(), **kwargs): super().__init__(interaction, *args, sequence=sequence, **kwargs) self._reaction_target_sim = self.interaction.get_participant( self.reaction_target) self._reaction_constraint = None self._triggered_sims = _instances = weakref.WeakSet() @classmethod def on_affordance_loaded_callback(cls, affordance, reaction_trigger_element, object_tuning_id=DEFAULT): def sim_can_execute_affordance(interaction, sim): context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High) return sim.test_super_affordance( reaction_trigger_element.reaction_affordance, interaction.target, context) affordance.register_sim_can_violate_privacy_callback( sim_can_execute_affordance, object_tuning_id=object_tuning_id) def _build_outer_elements(self, sequence): if self.trigger_on_late_arrivals: return build_critical_section_with_finally( sequence, self._remove_constraints) return sequence def _do_behavior(self): self._reaction_constraint = Anywhere() for tuned_reaction_constraint in self.reaction_constraints: self._reaction_constraint = self._reaction_constraint.intersect( tuned_reaction_constraint.create_constraint( None, target=self._reaction_target_sim)) if self.trigger_on_late_arrivals: self._reaction_target_sim.reaction_triggers[ self.interaction] = self for sim in services.sim_info_manager().instanced_sims_gen(): self.intersect_and_execute(sim) def intersect_and_execute(self, sim): if sim in self._triggered_sims: return participants = self.interaction.get_participants( ParticipantType.AllSims) if sim not in participants: sim_constraint = interactions.constraints.Transform( sim.transform, routing_surface=sim.routing_surface) context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High) if sim_constraint.intersect(self._reaction_constraint).valid: result = sim.push_super_affordance(self.reaction_affordance, self._reaction_target_sim, context) if result: self.interaction.add_liability( JOIN_INTERACTION_LIABILITY, JoinInteractionLiability(result.interaction)) self._triggered_sims.add(sim) def _remove_constraints(self, *_, **__): self._reaction_target_sim.reaction_triggers.pop(self.interaction, None)
class VehicleLiability(HasTunableFactory, AutoFactoryInit, PreparationLiability): LIABILITY_TOKEN = 'VehicleLiability' SOURCE_CONNECTIVITY_HANDLE_RADIUS = 2.5 GET_CLOSE_AFFORDANCE = TunableReference(description='\n The affordance that Vehicle Liabilities use to get close to the\n deployment area. Can be overridden on the liability.\n ', manager=services.affordance_manager()) FACTORY_TUNABLES = {'vehicle': FindVehicleVariant(), 'transfer_to_continuations': Tunable(description='\n If enabled, we will transfer this liability to continuations and\n ensure that the Sim attempts to re-deploy their vehicle.\n ', tunable_type=bool, default=False), 'get_close_affordance': OptionalTunable(description='\n If enabled, we will override the default get close affordance for\n vehicle liabilities. This affordance is pushed to get close to the\n deployment zone.\n ', tunable=TunablePackSafeReference(description='\n The affordance we want to push to get close to the deployment\n zone. We will be passing constraints to satisfy to this\n affordance.\n ', manager=services.affordance_manager()), enabled_name='override', disabled_name='default_affordance'), 'deploy_constraints': OptionalTunable(description="\n If enabled, we will use this set of constraints to find out where\n the Sim actually intends on going to use their vehicle. Without\n this we don't really know where they want to deploy it.\n \n We can't use the interaction constraints because that's most likely\n not where the sim will want to be.\n ", tunable=TunableTuple(description='\n An object and constraints to generate relative to it.\n ', constraints=TunableGeometricConstraintVariant(description='\n The constraint we want to use to get close to our deployment zone.\n \n Note: This is NOT where the Sim will be when they run the\n interaction. We need to get them to deploy the vehicle before the\n interaction actually runs. This constraint gives us an idea of\n where to look.\n ', disabled_constraints=('spawn_points',)), target=TunableEnumEntry(description='\n The object we want to generate the deploy constraint\n relative to.\n ', tunable_type=ParticipantTypeSingle, default=ParticipantTypeSingle.Object))), 'max_vehicle_state': TunableEnumEntry(description='\n The maximum progress we want to make on riding our vehicle.\n ', tunable_type=VehicleTransitionState, default=VehicleTransitionState.DEPLOYING, invalid_enums=(VehicleTransitionState.NO_STATE,))} def __init__(self, interaction, *args, **kwargs): super().__init__(*args, **kwargs) self._vehicle_transition_state = VehicleTransitionState.NO_STATE self._vehicle = None self._interaction = interaction self._deploy_constraint = None def should_transfer(self, _): return self.transfer_to_continuations def on_add(self, interaction): super().on_add(interaction) resolver = interaction.get_resolver() vehicle = self.vehicle(resolver) if vehicle is None: logger.warn("Attempting to deploy a vehicle that we don't have. {}", interaction) return self._vehicle = vehicle def _get_deployment_constraint(self): if self._deploy_constraint is not None and self._deploy_constraint.valid: return self._deploy_constraint sim = self._interaction.sim deploy_constraints = self.deploy_constraints if deploy_constraints is not None: constraint_target = self._interaction.get_participant(deploy_constraints.target) self._deploy_constraint = ANYWHERE self._deploy_constraint = self._deploy_constraint.intersect(deploy_constraints.constraints.create_constraint(sim, target=constraint_target, target_position=constraint_target.position if constraint_target is not None else DEFAULT)) return self._deploy_constraint target = self._interaction.target constraint = self._interaction.constraint_intersection(sim, target, participant_type=ParticipantType.Actor, posture_state=None) constraint = constraint.generate_geometry_only_constraint() return constraint def _get_close_to_deploy(self, timeline, vehicle): sim = self._interaction.sim constraint = self._get_deployment_constraint() if not constraint.valid: return InteractionQueuePreparationStatus.FAILURE yield handles = constraint.get_connectivity_handles(sim) goals = [] for handle in handles: goals.extend(handle.get_goals(single_goal_only=True)) if not goals: return InteractionQueuePreparationStatus.FAILURE yield if sim.posture.unconstrained: source_constraint = Position(sim.position, routing_surface=sim.routing_surface) else: source_constraint = Circle(sim.position, self.SOURCE_CONNECTIVITY_HANDLE_RADIUS, sim.routing_surface) source_handles = source_constraint.get_connectivity_handles(sim) if not source_handles: return InteractionQueuePreparationStatus.FAILURE yield source_goals = source_handles[0].get_goals(single_goal_only=True) if not source_goals: return InteractionQueuePreparationStatus.FAILURE yield source_goal = source_goals[0] if source_goal.position == goals[0].position and source_goal.routing_surface_id == goals[0].routing_surface_id: return InteractionQueuePreparationStatus.SUCCESS yield route = routing.Route(source_goal.location, goals, routing_context=sim.routing_context) plan_primitive = PlanRoute(route, sim, interaction=self._interaction) result = yield from element_utils.run_child(timeline, plan_primitive) if not result and not (not plan_primitive.path.nodes and not plan_primitive.path.nodes.plan_success): return InteractionQueuePreparationStatus.FAILURE yield cur_path = plan_primitive.path if not cur_path.nodes: return InteractionQueuePreparationStatus.FAILURE yield def get_start_node_index_for_path(vehicle, path): nodes = list(path.nodes) object_manager = services.object_manager() prev_node = None for node in nodes[::-1]: portal_obj_id = node.portal_object_id portal_obj = object_manager.get(portal_obj_id) if portal_obj_id else None if node.portal_id: if portal_obj: if not vehicle.vehicle_component.can_transition_through_portal(portal_obj, node.portal_id): break prev_node = node else: return 0 return prev_node.index split_paths = False while cur_path.next_path is not None: split_paths = True cur_path = cur_path.next_path if not cur_path.nodes: return InteractionQueuePreparationStatus.FAILURE yield start_node_index = get_start_node_index_for_path(vehicle, cur_path) start_node = cur_path.nodes[start_node_index] start_location = sims4.math.Location(Transform(Vector3(*start_node.position), Quaternion(*start_node.orientation)), start_node.routing_surface_id) if not split_paths and start_node_index == 0 and (start_location.transform.translation - source_goal.location.position).magnitude_squared() < vehicle.vehicle_component.minimum_route_distance: return InteractionQueuePreparationStatus.SUCCESS yield deploy_constraint = Position(start_location.transform.translation, routing_surface=start_location.routing_surface) depended_on_si = self._interaction affordance = self.get_close_affordance if self.get_close_affordance is not None else VehicleLiability.GET_CLOSE_AFFORDANCE aop = AffordanceObjectPair(affordance, None, affordance, None, route_fail_on_transition_fail=False, constraint_to_satisfy=deploy_constraint, allow_posture_changes=True, depended_on_si=depended_on_si) context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.FIRST, must_run_next=True, group_id=depended_on_si.group_id) if not aop.test_and_execute(context): return InteractionQueuePreparationStatus.FAILURE yield return InteractionQueuePreparationStatus.NEEDS_DERAIL yield def _prepare_gen(self, timeline, *args, **kwargs): if self._vehicle is None: return InteractionQueuePreparationStatus.FAILURE yield sim = self._interaction.sim vehicle_component = self._vehicle.vehicle_component if self._vehicle_transition_state == VehicleTransitionState.NO_STATE: path_result = yield from self._get_close_to_deploy(timeline, self._vehicle) if path_result != InteractionQueuePreparationStatus.SUCCESS: return path_result yield result = vehicle_component.push_deploy_vehicle_affordance(sim, depend_on_si=self._interaction) if not result: self._interaction.cancel(FinishingType.TRANSITION_FAILURE, cancel_reason_msg='Failed to Deploy Vehicle') return InteractionQueuePreparationStatus.FAILURE yield self._vehicle_transition_state = VehicleTransitionState.DEPLOYING return InteractionQueuePreparationStatus.NEEDS_DERAIL yield elif self._vehicle_transition_state == VehicleTransitionState.DEPLOYING and self.max_vehicle_state == VehicleTransitionState.MOUNTING: result = vehicle_component.push_drive_affordance(sim) if not result: self._interaction.cancel(FinishingType.TRANSITION_FAILURE, cancel_reason_msg='Failed to Drive Vehicle') return InteractionQueuePreparationStatus.FAILURE yield self._vehicle_transition_state = VehicleTransitionState.MOUNTING return InteractionQueuePreparationStatus.NEEDS_DERAIL yield return InteractionQueuePreparationStatus.SUCCESS yield
class RouteToLocationElement(elements.ParentElement, HasTunableFactory, AutoFactoryInit): FACTORY_TUNABLES = { 'route_constraints': TunableMapping( description= '\n A list of constraints and the participant they are relative to\n that the Sim will route to fulfill when this element runs. \n ', key_name='relative_participant', key_type=TunableEnumEntry( description= '\n The participant tuned here will have this constraint \n applied to them.\n ', tunable_type=ParticipantTypeObject, default=ParticipantTypeObject.Object), value_name='constraints', value_type=TunableList( description= '\n Constraints relative to the relative participant.\n ', tunable=TunableGeometricConstraintVariant( description= '\n A constraint that must be fulfilled in order to interact\n with this object.\n ' ), minlength=1), minlength=1) } def __init__(self, interaction, sequence=(), *args, **kwargs): super().__init__(*args, **kwargs) self.interaction = interaction self.sequence = sequence def behavior_element(self, timeline): total_constraint = ANYWHERE for (relative_participant, constraints) in self.route_constraints.items(): relative_object = self.interaction.get_participant( relative_participant) if relative_object is None: continue for constraint in constraints: relative_constraint = constraint.create_constraint( self.interaction.sim, relative_object, objects_to_ignore=[relative_object]) total_constraint = total_constraint.intersect( relative_constraint) if not total_constraint.valid: logger.error( 'Routing Element cannot resolve constraints for {}', self.interaction) return False yield sim = self.interaction.sim goals = [] handles = total_constraint.get_connectivity_handles(sim) for handle in handles: goals.extend(handle.get_goals()) if not goals: return False yield route = routing.Route(sim.routing_location, goals, routing_context=sim.routing_context) plan_primitive = PlanRoute(route, sim, interaction=self.interaction) result = yield from element_utils.run_child(timeline, plan_primitive) if not result: return False yield if not (plan_primitive.path.nodes and plan_primitive.path.nodes.plan_success): return False yield route = get_route_element_for_path(sim, plan_primitive.path, interaction=self.interaction) result = yield from element_utils.run_child( timeline, build_critical_section(route)) return result yield def _run(self, timeline): child_element = build_critical_section(self.sequence, self.behavior_element) return timeline.run_child(child_element)
class ObjectRouteFromTargetObject(_ObjectRoutingBehaviorBase): FACTORY_TUNABLES = { 'radius': TunableDistanceSquared( description= '\n Only objects within this distance are considered.\n ', default=1), 'target_type': TunableVariant( description= '\n Type of target object to choose (object, sim).\n ', object=_RouteTargetTypeObject.TunableFactory(), sim=_RouteTargetTypeSim.TunableFactory(), default='object'), 'target_selection_test': TunableTestSet( description= '\n A test used for selecting a target.\n ', tuning_group=GroupNames.TESTS), 'no_target_loot': TunableList( description= "\n Loot to apply if no target is selected (eg, change state back to 'wander').\n ", tunable=LootActions.TunableReference()), 'constraints': TunableList( description= '\n Constraints relative to the relative participant.\n ', tunable=TunableGeometricConstraintVariant( description= '\n Use the point on the found object defined by these geometric constraints.\n ', disabled_constraints=('spawn_points', 'spawn_points_with_backup'))), 'target_action_rules': TunableList( description= '\n A set of conditions and a list of one or more TargetObjectActions to run\n on the target object after routing to it. These are applied in sequence.\n ', tunable=_TargetActionRules.TunableFactory()) } @classmethod def _verify_tuning_callback(cls): if not cls.target_selection_test and not cls.tags: logger.error( 'No selection test tuned for ObjectRouteFromTargetObject {}.', cls, owner='miking') def _find_target(self): all_objects = self.target_type.get_objects() objects = [] for o in all_objects: dist_sq = (o.position - self._obj.position).magnitude_squared() if dist_sq > self.radius: continue if o == self: continue if not o.is_sim and not o.may_reserve(self._obj): continue if self.target_selection_test: resolver = DoubleObjectResolver(self._obj, o) if not self.target_selection_test.run_tests(resolver): continue else: objects.append([o, dist_sq]) if not objects: return source_handles = [ routing.connectivity.Handle(self._obj.position, self._obj.routing_surface) ] dest_handles = [] for o in objects: obj = o[0] parent = obj.parent route_to_obj = parent if parent is not None else obj constraint = Anywhere() for tuned_constraint in self.constraints: constraint = constraint.intersect( tuned_constraint.create_constraint(self._obj, route_to_obj)) dests = constraint.get_connectivity_handles(self._obj, target=obj) if dests: dest_handles.extend(dests) if not dest_handles: return routing_context = self._obj.get_routing_context() connections = routing.estimate_path_batch( source_handles, dest_handles, routing_context=routing_context) if not connections: return connections.sort(key=lambda connection: connection[2]) best_connection = connections[0] best_dest_handle = best_connection[1] best_obj = best_dest_handle.target return best_obj def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._target = self._find_target() def get_routes_gen(self): if self._target is None: self.on_no_target() return False yield routing_slot_constraint = Anywhere() for tuned_constraint in self.constraints: routing_slot_constraint = routing_slot_constraint.intersect( tuned_constraint.create_constraint(self._obj, self._target)) goals = list( itertools.chain.from_iterable( h.get_goals() for h in routing_slot_constraint.get_connectivity_handles( self._obj))) routing_context = self._obj.get_routing_context() route = routing.Route(self._obj.routing_location, goals, routing_context=routing_context) yield route def do_target_action_rules_gen(self, timeline): if not self.target_action_rules or self._target is None: return resolver = DoubleObjectResolver(self._obj, self._target) for target_action_rule in self.target_action_rules: if random.random.random() >= target_action_rule.chance: continue if not target_action_rule.test.run_tests(resolver): continue if target_action_rule.actions is not None: for action in target_action_rule.actions: result = yield from action.run_action_gen( timeline, self._obj, self._target) if not result: return if target_action_rule.abort_if_applied: return def on_no_target(self): resolver = SingleObjectResolver(self._obj) for loot_action in self.no_target_loot: loot_action.apply_to_resolver(resolver)