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'
                 }))
     }
示例#2
0
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
示例#3
0
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
示例#4
0
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
示例#6
0
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)