Exemplo n.º 1
0
 def get_constraint(self, sim, **kwargs):
     carryable = self._obj.get_component(CARRYABLE_COMPONENT)
     if carryable is not None and carryable.constraint_pick_up is not None:
         constraint_total = Anywhere()
         for constraint_factory in carryable.constraint_pick_up:
             constraint = constraint_factory.create_constraint(sim, target=self._obj, routing_surface=self._obj.routing_surface)
             constraint_total = constraint_total.intersect(constraint)
         return constraint_total
     return super().get_constraint(sim)
Exemplo n.º 2
0
 def constraint_intersection(self):
     if self._constraint_intersection_dirty:
         intersection = Anywhere()
         for constraint in set(self._constraints.values()):
             new_intersection = intersection.intersect(constraint)
             if not self._invalid_expected and not new_intersection.valid:
                 logger.error('Invalid constraint intersection for PostureState:{}', self)
                 intersection = new_intersection
                 break
             intersection = new_intersection
         self._constraint_intersection_dirty = False
         self._constraint_intersection = intersection
     return self._constraint_intersection
class Broadcaster(HasTunableReference,
                  metaclass=HashedTunedInstanceMetaclass,
                  manager=services.get_instance_manager(
                      sims4.resources.Types.BROADCASTER)):
    __qualname__ = 'Broadcaster'
    FREQUENCY_ENTER = 0
    FREQUENCY_PULSE = 1
    INSTANCE_TUNABLES = {
        '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()),
        'effects':
        TunableList(
            description=
            '\n            A list of effects that are applied to Sims and objects affected by\n            this broadcaster.\n            ',
            tunable=TunableBroadcasterEffectVariant()),
        '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,
                    needs_tuning=True,
                    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':
        Tunable(
            description=
            '\n            If checked, then in addition to all instantiated Sims, all objects\n            will be affected by this broadcaster. Some tuned effects might still\n            only apply to Sims (e.g. affordance pushing).\n            \n            Checking this tuning field has performance repercussions, as it\n            means we have to process a lot more data during broadcaster pings.\n            Please use this sporadically.\n            ',
            tunable_type=bool,
            default=False)
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.constraints:
            logger.error('Broadcaster {} does not define any constraints.',
                         cls)

    @classmethod
    def register_static_callbacks(cls, *args, **kwargs):
        for broadcaster_effect in cls.effects:
            broadcaster_effect.register_static_callbacks(*args, **kwargs)

    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, _):
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.remove_broadcaster(self)

    def _on_broadcasting_object_moved(self, *_, **__):
        self.regenerate_constraint()
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.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:
            while 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:
            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 not self.allow_objects and not obj.is_sim:
            return False
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return False
        routing_surface = broadcasting_object.routing_surface
        if routing_surface is None or obj.routing_surface != routing_surface:
            return False
        if obj is broadcasting_object:
            return False
        linked_broadcasters = list(self._linked_broadcasters)
        if any(obj is linked_broadcaster.broadcasting_object
               for linked_broadcaster in linked_broadcasters):
            return False
        return True

    def apply_broadcaster_effect(self, affected_object):
        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:
                broadcaster_effect.apply_broadcaster_effect(
                    self, affected_object)
        for linked_broadcaster in self._linked_broadcasters:
            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:
            while 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):
        if not self._affected_objects[affected_object][1]:
            return
        self._affected_objects[affected_object] = (
            self._affected_objects[affected_object][0], False)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked or not is_linked:
                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
            if 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
            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
            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 self._affected_objects.items():
                linked_broadcaster._apply_linked_broadcaster_data(obj, data)

    @property
    def has_linked_broadcasters(self):
        if self._linked_broadcasters:
            return True
        return False

    def get_linked_broadcasters_gen(self):
        yield self._linked_broadcasters

    def regenerate_constraint(self, *_, **__):
        self._constraint = None

    def get_constraint(self):
        if 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
Exemplo n.º 4
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
Exemplo n.º 5
0
class ReactionTriggerElement(XevtTriggeredElement):
    __qualname__ = 'ReactionTriggerElement'
    FACTORY_TUNABLES = {
        'description':
        'At the specified timing, push an affordance on other Sims on the lot.',
        'reaction_affordance':
        TunableReference(services.affordance_manager(),
                         description='The affordance to push on other Sims.'),
        'reaction_target':
        TunableEnumEntry(
            ParticipantType,
            ParticipantType.Actor,
            description=
            'The subject of this interaction that will be set as the target of the pushed reaction_affordance.'
        ),
        'reaction_constraints':
        TunableList(
            TunableGeometricConstraintVariant(),
            description=
            'The constraints that Sims on the lot have to satisfy such that reaction_affordance is pushed on them.'
        ),
        'trigger_on_late_arrivals':
        Tunable(
            bool,
            False,
            needs_tuning=True,
            description=
            '\n                                                                      If checked, Sims entering the reaction area after the reaction is first triggered will\n                                                                      also react, up until when the interaction is canceled.\n                                                                      '
        )
    }

    def __init__(self, interaction, *args, sequence=(), **kwargs):
        super().__init__(interaction, sequence=sequence, *args, **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):
        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)

    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)
Exemplo n.º 6
0
 def get_combined_constraint(self, existing_constraint=None, priority=None, group_id=None, to_exclude=None, include_inertial_sis=False, force_inertial_sis=False, existing_si=None, posture_state=DEFAULT, allow_posture_providers=True, include_existing_constraint=True, participant_type=ParticipantType.Actor):
     included_sis = set()
     if include_inertial_sis:
         if existing_si is not None and any(si.id == existing_si.continuation_id for si in self):
             sis_must_include = set()
         else:
             sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         if force_inertial_sis:
             for si in self._super_interactions:
                 if si in sis_must_include:
                     pass
                 if not allow_posture_providers and self.sim.posture_state.is_source_interaction(si):
                     pass
                 if not self._common_included_si_tests(si):
                     pass
                 sis_must_include.add(si)
         to_consider = set()
         for non_guaranteed_si in self._super_interactions:
             if non_guaranteed_si in sis_must_include:
                 pass
             if not allow_posture_providers and self.sim.posture_state.is_source_interaction(non_guaranteed_si):
                 pass
             to_consider.add(non_guaranteed_si)
     else:
         sis_must_include = self._get_must_include_sis(priority, group_id, existing_si=existing_si)
         to_consider = set()
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in sis_must_include:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             if owned_posture is None:
                 pass
             if owned_posture.track != postures.PostureTrack.BODY:
                 pass
             if owned_posture.source_interaction.is_finishing:
                 pass
             additional_posture_sis.add(owned_posture.source_interaction)
         sis_must_include.update(additional_posture_sis)
         additional_posture_sis.clear()
     total_constraint = Anywhere()
     included_carryables = set()
     for si_must_include in sis_must_include:
         if si_must_include.is_finishing:
             pass
         while not si_must_include is to_exclude:
             if si_must_include is existing_si:
                 pass
             if existing_si is not None and existing_si.group_id == si_must_include.group_id:
                 pass
             my_role = si_must_include.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si_must_include, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     return (Nowhere(), sis_must_include)
             si_constraint = si_must_include.constraint_intersection(participant_type=my_role, posture_state=posture_state)
             if existing_si is not None:
                 if (existing_si.should_rally or existing_si.relocate_main_group) and (si_must_include.is_social and si_must_include.social_group is not None) and si_must_include.social_group is si_must_include.sim.get_main_group():
                     si_constraint = si_constraint.generate_posture_only_constraint()
                 si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
             if existing_constraint is not None:
                 si_constraint = si_constraint.apply(existing_constraint)
             test_constraint = total_constraint.intersect(si_constraint)
             if not test_constraint.valid:
                 break
             carry_target = si_must_include.targeted_carryable
             if carry_target is not None:
                 if len(included_carryables) == 2 and carry_target not in included_carryables:
                     pass
                 included_carryables.add(carry_target)
             total_constraint = test_constraint
             included_sis.add(si_must_include)
     if len(included_carryables) == 2 and existing_si is not None:
         existing_carry_target = existing_si.carry_target or existing_si.target
         if existing_carry_target is not None and existing_carry_target.carryable_component is not None:
             if existing_carry_target not in included_carryables:
                 total_constraint = Nowhere()
     if included_sis != sis_must_include:
         total_constraint = Nowhere()
     if not total_constraint.valid:
         return (total_constraint, included_sis)
     if total_constraint.tentative or existing_constraint is not None and existing_constraint.tentative:
         return (total_constraint, included_sis)
     if to_consider:
         for si in self._sis_sorted(to_consider):
             if si is to_exclude:
                 pass
             if existing_si is not None and existing_si.group_id == si.group_id:
                 pass
             if not self._common_included_si_tests(si):
                 pass
             my_role = si.get_participant_type(self.sim)
             if existing_si is not None:
                 existing_participant_type = existing_si.get_participant_type(self.sim)
                 if not self.are_sis_compatible(si, existing_si, my_role, existing_participant_type, ignore_geometry=True):
                     pass
             si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=posture_state)
             if existing_si is not None:
                 si_constraint = si_constraint.apply_posture_state(None, existing_si.get_constraint_resolver(None, participant_type=participant_type))
             if si_constraint.tentative:
                 si_constraint = si.constraint_intersection(participant_type=my_role, posture_state=DEFAULT)
             test_constraint = total_constraint.intersect(si_constraint)
             if existing_constraint is not None:
                 test_constraint_plus_existing = test_constraint.intersect(existing_constraint)
                 while not not test_constraint_plus_existing.valid:
                     if test_constraint_plus_existing.tentative:
                         pass
                     test_constraint = test_constraint.apply(existing_constraint)
                     if test_constraint.valid:
                         total_constraint = test_constraint
                         included_sis.add(si)
                     while total_constraint.tentative:
                         break
             if test_constraint.valid:
                 total_constraint = test_constraint
                 included_sis.add(si)
             while total_constraint.tentative:
                 break
     if allow_posture_providers:
         additional_posture_sis = set()
         for si in included_sis:
             owned_posture = self.sim.posture_state.get_source_or_owned_posture_for_si(si)
             while owned_posture is not None and owned_posture.source_interaction not in included_sis and owned_posture.track == postures.PostureTrack.BODY:
                 additional_posture_sis.add(owned_posture.source_interaction)
         included_sis.update(additional_posture_sis)
     if include_existing_constraint and existing_constraint is not None:
         total_constraint = total_constraint.intersect(existing_constraint)
     return (total_constraint, included_sis)
Exemplo n.º 7
0
class Broadcaster(HasTunableReference, metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.BROADCASTER)):
    __qualname__ = 'Broadcaster'
    FREQUENCY_ENTER = 0
    FREQUENCY_PULSE = 1
    INSTANCE_TUNABLES = {'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()), 'effects': TunableList(description='\n            A list of effects that are applied to Sims and objects affected by\n            this broadcaster.\n            ', tunable=TunableBroadcasterEffectVariant()), '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, needs_tuning=True, 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': Tunable(description='\n            If checked, then in addition to all instantiated Sims, all objects\n            will be affected by this broadcaster. Some tuned effects might still\n            only apply to Sims (e.g. affordance pushing).\n            \n            Checking this tuning field has performance repercussions, as it\n            means we have to process a lot more data during broadcaster pings.\n            Please use this sporadically.\n            ', tunable_type=bool, default=False)}

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.constraints:
            logger.error('Broadcaster {} does not define any constraints.', cls)

    @classmethod
    def register_static_callbacks(cls, *args, **kwargs):
        for broadcaster_effect in cls.effects:
            broadcaster_effect.register_static_callbacks(*args, **kwargs)

    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, _):
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.broadcaster_service
            if broadcaster_service is not None:
                broadcaster_service.remove_broadcaster(self)

    def _on_broadcasting_object_moved(self, *_, **__):
        self.regenerate_constraint()
        current_zone = services.current_zone()
        if current_zone is not None:
            broadcaster_service = current_zone.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:
            while 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:
            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 not self.allow_objects and not obj.is_sim:
            return False
        broadcasting_object = self.broadcasting_object
        if broadcasting_object is None:
            return False
        routing_surface = broadcasting_object.routing_surface
        if routing_surface is None or obj.routing_surface != routing_surface:
            return False
        if obj is broadcasting_object:
            return False
        linked_broadcasters = list(self._linked_broadcasters)
        if any(obj is linked_broadcaster.broadcasting_object for linked_broadcaster in linked_broadcasters):
            return False
        return True

    def apply_broadcaster_effect(self, affected_object):
        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:
                broadcaster_effect.apply_broadcaster_effect(self, affected_object)
        for linked_broadcaster in self._linked_broadcasters:
            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:
            while 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):
        if not self._affected_objects[affected_object][1]:
            return
        self._affected_objects[affected_object] = (self._affected_objects[affected_object][0], False)
        for broadcaster_effect in self.effects:
            while broadcaster_effect.apply_when_linked or not is_linked:
                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
            if 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
            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
            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 self._affected_objects.items():
                linked_broadcaster._apply_linked_broadcaster_data(obj, data)

    @property
    def has_linked_broadcasters(self):
        if self._linked_broadcasters:
            return True
        return False

    def get_linked_broadcasters_gen(self):
        yield self._linked_broadcasters

    def regenerate_constraint(self, *_, **__):
        self._constraint = None

    def get_constraint(self):
        if 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