def __init__(self, owner=None, width=None, cost=None, get_focus_fn=None, get_enabled_fn=None):
     self._owner = owner.ref() if hasattr(owner, 'ref') else owner
     if get_focus_fn is None and hasattr(owner, 'register_on_location_changed'):
         owner.register_on_location_changed(self.refresh)
     self._get_focus = get_focus_fn or (lambda : get_object_translation(owner))
     self._get_enabled = get_enabled_fn or (lambda : True)
     self._radius = (width or UserFootprintHelper.DEFAULT_DISCOURAGE_AREA_WIDTH)/2
     self._cost = max(cost or UserFootprintHelper.DEFAULT_DISCOURAGE_AREA_COST, routing.get_default_discouragement_cost())
     self._footprints = {}
     self._focus = None
Exemplo n.º 2
0
 def get_waypoint_constraints_gen(self, routing_agent, waypoint_count):
     line_length_offset = sims4.math.Vector3(0, 0, self.line_length)
     object_radius = routing_agent.routing_component.object_radius
     start = routing_agent.position
     initial_orientation = sims4.random.random_orientation()
     end = initial_orientation.transform_vector(line_length_offset) + start
     polygon = build_rectangle_from_two_points_and_radius(
         start, end, object_radius)
     starting_location_for_sample = placement.create_starting_location(
         position=start, routing_surface=self._routing_surface)
     water_constraint = self.get_water_constraint(
         self.fgl_parameters.min_water_depth,
         self.fgl_parameters.max_water_depth)
     fgl_context = placement.FindGoodLocationContext(
         starting_location_for_sample,
         object_polygons=(polygon, ),
         ignored_object_ids=[routing_agent.id, self._context.sim.sim_id],
         max_distance=0,
         min_water_depth=water_constraint.get_min_water_depth(),
         max_water_depth=water_constraint.get_max_water_depth())
     (_, orientation) = placement.find_good_location(fgl_context)
     if orientation is None:
         return
     final_orientation = sims4.math.Quaternion.concatenate(
         orientation, initial_orientation)
     oriented_line_offset = final_orientation.transform_vector(
         line_length_offset)
     waypoint_constraints = []
     for waypoint_index in range(0, waypoint_count):
         percent_down_line = waypoint_index / (waypoint_count - 1)
         goal_positon = oriented_line_offset * percent_down_line + start
         geometry = sims4.geometry.RestrictedPolygon(
             sims4.geometry.CompoundPolygon(
                 sims4.geometry.Polygon((goal_positon, ))), ())
         constraint = SmallAreaConstraint(
             geometry=geometry,
             routing_surface=self._routing_surface,
             min_water_depth=water_constraint.get_min_water_depth(),
             max_water_depth=water_constraint.get_max_water_depth())
         waypoint_constraints.append(constraint)
     end = oriented_line_offset + start
     polygon = build_rectangle_from_two_points_and_radius(
         start, end, object_radius)
     self._footprint = PolygonFootprint(
         polygon,
         routing_surface=self._routing_surface,
         cost=routing.get_default_discouragement_cost(),
         footprint_type=FootprintType.FOOTPRINT_TYPE_OBJECT,
         enabled=True)
     routing_agent.routing_component.pathplan_context.ignore_footprint_contour(
         self._footprint.footprint_id)
     yield from waypoint_constraints
Exemplo n.º 3
0
 def _build_discouragement_footprint(self, start, end, surface, offset):
     fwd = start - end
     fwd.y = 0
     fwd = sims4.math.vector_normalize(fwd)
     cross = sims4.math.vector_cross(fwd, sims4.math.Vector3.Y_AXIS())
     width = 0.05
     length = 0.5
     pos = start - cross*0.25*offset
     vertices = []
     vertices.append(pos - width*cross)
     vertices.append(pos - width*cross + length*fwd)
     vertices.append(pos + width*cross + length*fwd)
     vertices.append(pos + width*cross)
     poly = sims4.geometry.Polygon(vertices)
     return sims4.geometry.PolygonFootprint(poly, routing_surface=surface, cost=routing.get_default_discouragement_cost(), footprint_type=6, enabled=True)
Exemplo n.º 4
0
 def _build_discouragement_footprint(self, start, end, surface, offset):
     fwd = start - end
     fwd.y = 0
     fwd = sims4.math.vector_normalize(fwd)
     cross = sims4.math.vector_cross(fwd, sims4.math.Vector3.Y_AXIS())
     width = 0.05
     length = 0.5
     pos = start - cross*0.5*offset
     vertices = []
     vertices.append(pos - width*cross - length*fwd)
     vertices.append(pos - width*cross + length*fwd)
     vertices.append(pos + width*cross + length*fwd)
     vertices.append(pos + width*cross - length*fwd)
     poly = sims4.geometry.Polygon(vertices)
     return sims4.geometry.PolygonFootprint(poly, routing_surface=surface, cost=routing.get_default_discouragement_cost(), footprint_type=6, enabled=True)
 def __init__(self,
              owner=None,
              width=None,
              cost=None,
              get_focus_fn=None,
              get_enabled_fn=None):
     self._owner = owner.ref() if hasattr(owner, 'ref') else owner
     if get_focus_fn is None and hasattr(owner,
                                         'register_on_location_changed'):
         owner.register_on_location_changed(self.refresh)
     self._get_focus = get_focus_fn or (
         lambda: get_object_translation(owner))
     self._get_enabled = get_enabled_fn or (lambda: True)
     self._radius = (width or
                     UserFootprintHelper.DEFAULT_DISCOURAGE_AREA_WIDTH) / 2
     self._cost = max(
         cost or UserFootprintHelper.DEFAULT_DISCOURAGE_AREA_COST,
         routing.get_default_discouragement_cost())
     self._footprints = {}
     self._focus = None
Exemplo n.º 6
0
class Privacy(LineOfSight):
    __qualname__ = 'Privacy'
    _PRIVACY_FOOTPRINT_TYPE = 5
    _PRIVACY_DISCOURAGEMENT_COST = routing.get_default_discouragement_cost()
    _SHOO_CONSTRAINT_RADIUS = Tunable(
        description=
        '\n        The radius of the constraint a Shooed Sim will attempt to route to.\n        ',
        tunable_type=float,
        default=2.5)
    _UNAVAILABLE_TOOLTIP = TunableLocalizedStringFactory(
        description=
        '\n        Tooltip displayed when an object is not accessible due to being inside\n        a privacy region.\n        '
    )
    _EMBARRASSED_AFFORDANCE = TunableReference(
        description=
        '\n        The affordance a Sim will play when getting embarrassed by walking in\n        on a privacy situation.\n        ',
        manager=services.affordance_manager())

    def __init__(self, interaction, tests, max_line_of_sight_radius,
                 map_divisions, simplification_ratio, boundary_epsilon,
                 facing_offset):
        super().__init__(max_line_of_sight_radius, map_divisions,
                         simplification_ratio, boundary_epsilon)
        self._max_line_of_sight_radius = max_line_of_sight_radius
        self._interaction = interaction
        self._tests = tests
        self._privacy_constraints = []
        self._allowed_sims = WeakSet()
        self._disallowed_sims = WeakSet()
        self._violators = WeakSet()
        self._late_violators = WeakSet()
        self.is_active = False
        self.has_shooed = False
        self.central_object = None
        self._pushed_interactions = []
        services.privacy_service().add_instance(self)

    @property
    def unavailable_tooltip(self):
        return self._UNAVAILABLE_TOOLTIP

    @property
    def interaction(self):
        return self._interaction

    @property
    def is_active(self) -> bool:
        return self._is_active

    @is_active.setter
    def is_active(self, value):
        self._is_active = value

    def _is_sim_allowed(self, sim):
        if self._tests:
            resolver = self._interaction.get_resolver(target=sim)
            if self._tests and self._tests.run_tests(resolver):
                return True
        if self._interaction.can_sim_violate_privacy(sim):
            return True
        return False

    def evaluate_sim(self, sim):
        if self._is_sim_allowed(sim):
            self._allowed_sims.add(sim)
            return True
        self._disallowed_sims.add(sim)
        return False

    def build_privacy(self, target=None):
        self.is_active = True
        target_object = self._interaction.get_participant(
            ParticipantType.Object)
        target_object = None if target_object.is_sim else target_object
        self.central_object = target_object or (target
                                                or self._interaction.sim)
        self.generate(self.central_object.position,
                      self.central_object.routing_surface)
        for poly in self.constraint.geometry.polygon:
            self._privacy_constraints.append(
                PolygonFootprint(
                    poly,
                    routing_surface=self._interaction.sim.routing_surface,
                    cost=self._PRIVACY_DISCOURAGEMENT_COST,
                    footprint_type=self._PRIVACY_FOOTPRINT_TYPE,
                    enabled=True))
        self._allowed_sims.update(
            self._interaction.get_participants(ParticipantType.AllSims))
        for sim in services.sim_info_manager().instanced_sims_gen():
            while sim not in self._allowed_sims:
                self.evaluate_sim(sim)
        violating_sims = self.find_violating_sims()
        self._cancel_unavailable_interactions(violating_sims)
        self._add_overrides_and_constraints_if_needed(violating_sims)

    def cleanup_privacy_instance(self):
        if self.is_active:
            self.is_active = False
            for sim in self._allowed_sims:
                self.remove_override_for_sim(sim)
            for sim in self._late_violators:
                self.remove_override_for_sim(sim)
            del self._privacy_constraints[:]
            self._allowed_sims.clear()
            self._disallowed_sims.clear()
            self._violators.clear()
            self._late_violators.clear()
            self._cancel_pushed_interactions()

    def remove_privacy(self):
        self.cleanup_privacy_instance()
        services.privacy_service().remove_instance(self)

    def intersects_with_object(self, obj):
        if obj.routing_surface != self.central_object.routing_surface:
            return False
        delta = obj.position - self.central_object.position
        distance = delta.magnitude_2d_squared()
        if distance > self.max_line_of_sight_radius * self.max_line_of_sight_radius:
            return False
        object_footprint = obj.footprint_polygon
        if object_footprint is None:
            object_footprint = sims4.geometry.Polygon([obj.position])
        for poly in self.constraint.geometry.polygon:
            intersection = poly.intersect(object_footprint)
            while intersection is not None and intersection.has_enough_vertices:
                return True
        return False

    def find_violating_sims(self):
        if not self.is_active:
            return []
        nearby_sims = placement.get_nearby_sims(
            self.central_object.position,
            self.central_object.routing_surface.secondary_id,
            radius=self.max_line_of_sight_radius,
            exclude=self._allowed_sims,
            only_sim_position=True)
        violators = []
        for sim in nearby_sims:
            if any(sim_primitive.is_traversing_portal()
                   for sim_primitive in sim.primitives
                   if isinstance(sim_primitive, FollowPath)):
                pass
            if sim not in self._disallowed_sims and self.evaluate_sim(sim):
                pass
            while sims4.geometry.test_point_in_compound_polygon(
                    sim.position, self.constraint.geometry.polygon):
                violators.append(sim)
        return violators

    def _add_overrides_and_constraints_if_needed(self, violating_sims):
        for sim in self._allowed_sims:
            self.add_override_for_sim(sim)
        for sim in violating_sims:
            self._violators.add(sim)
            liabilities = ((SHOO_LIABILITY, ShooLiability(self, sim)), )
            result = self._route_sim_away(sim, liabilities=liabilities)
            while result:
                self._pushed_interactions.append(result.interaction)

    def _cancel_unavailable_interactions(self, violating_sims):
        for sim in violating_sims:
            interactions_to_cancel = set()
            if sim.queue.running is not None:
                interactions_to_cancel.add(sim.queue.running)
            for interaction in sim.si_state:
                while interaction.is_super and interaction.target is not None and sim.locked_from_obj_by_privacy(
                        interaction.target):
                    interactions_to_cancel.add(interaction)
            for interaction in sim.queue:
                if interaction.target is not None and sim.locked_from_obj_by_privacy(
                        interaction.target):
                    interactions_to_cancel.add(interaction)
                else:
                    while interaction.target is not None:
                        break
            for interaction in interactions_to_cancel:
                interaction.cancel(
                    FinishingType.INTERACTION_INCOMPATIBILITY,
                    cancel_reason_msg=
                    'Canceled due to incompatibility with privacy instance.')

    def _route_sim_away(self, sim, liabilities=()):
        context = InteractionContext(sim,
                                     InteractionContext.SOURCE_SCRIPT,
                                     Priority.High,
                                     insert_strategy=QueueInsertStrategy.NEXT)
        from interactions.utils.satisfy_constraint_interaction import BuildAndForceSatisfyShooConstraintInteraction
        result = sim.push_super_affordance(
            BuildAndForceSatisfyShooConstraintInteraction,
            None,
            context,
            liabilities=liabilities,
            privacy_inst=self,
            name_override='BuildShooFromPrivacy')
        if not result:
            logger.debug(
                'Failed to push BuildAndForceSatisfyShooConstraintInteraction on Sim {} to route them out of a privacy area.  Result: {}',
                sim,
                result,
                owner='tastle')
            self.interaction.cancel(
                FinishingType.TRANSITION_FAILURE,
                cancel_reason_msg='Failed to shoo Sims away.')
        return result

    def _cancel_pushed_interactions(self):
        for interaction in self._pushed_interactions:
            interaction.cancel(
                FinishingType.AUTO_EXIT,
                cancel_reason_msg='Privacy finished and is cleaning up.')
        self._pushed_interactions.clear()

    def handle_late_violator(self, sim):
        self._cancel_unavailable_interactions((sim, ))
        self.add_override_for_sim(sim)
        liabilities = ((LATE_SHOO_LIABILITY, LateShooLiability(self, sim)), )
        result = self._route_sim_away(sim, liabilities=liabilities)
        if not result:
            return
        if not self._violators:
            context = InteractionContext(
                sim,
                InteractionContext.SOURCE_SCRIPT,
                Priority.High,
                insert_strategy=QueueInsertStrategy.NEXT)
            result = sim.push_super_affordance(
                self._EMBARRASSED_AFFORDANCE,
                self.interaction.get_participant(ParticipantType.Actor),
                context)
            if not result:
                logger.error(
                    'Failed to push the embarrassed affordance on Sim {}. Interaction {}. Result {}. Context {} ',
                    sim,
                    self.interaction,
                    result,
                    context,
                    owner='tastle')
                return
        self._late_violators.add(sim)

    def add_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.ignore_footprint_contour(
                footprint.footprint_id)

    def remove_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.remove_footprint_contour_override(
                footprint.footprint_id)

    @property
    def allowed_sims(self):
        return self._allowed_sims

    @property
    def disallowed_sims(self):
        return self._disallowed_sims

    @property
    def violators(self):
        return self._violators

    def remove_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._violators.discard(sim)

    @property
    def late_violators(self):
        return self._late_violators

    def remove_late_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._late_violators.discard(sim)
Exemplo n.º 7
0
class Privacy(LineOfSight):
    _PRIVACY_SURFACE_BLOCKING_FOOTPRINT_COST = 100000
    _PRIVACY_DISCOURAGEMENT_COST = routing.get_default_discouragement_cost()
    _SHOO_CONSTRAINT_RADIUS = Tunable(description='\n        The radius of the constraint a Shooed Sim will attempt to route to.\n        ', tunable_type=float, default=2.5)
    _UNAVAILABLE_TOOLTIP = TunableLocalizedStringFactory(description='\n        Tooltip displayed when an object is not accessible due to being inside\n        a privacy region.\n        ')
    _EMBARRASSED_AFFORDANCE = TunableReference(description='\n        The affordance a Sim will play when getting embarrassed by walking in\n        on a privacy situation.\n        ', manager=services.get_instance_manager(Types.INTERACTION))

    def __init__(self, *, interaction=None, tests=None, shoo_exempt_tests=None, max_line_of_sight_radius=None, map_divisions=None, simplification_ratio=None, boundary_epsilon=None, facing_offset=None, routing_surface_only=None, shoo_constraint_radius=None, unavailable_tooltip=None, embarrassed_affordance=None, reserved_surface_space=None, vehicle_tests=None, central_object=None, post_route_affordance=None, add_to_privacy_service=True, privacy_cost_override=None, additional_exit_offsets=None, persistent_instance=False, privacy_violators=None):
        super().__init__(max_line_of_sight_radius, map_divisions, simplification_ratio, boundary_epsilon)
        logger.assert_raise(bool(interaction) != bool(central_object), 'Privacy must define either one of interaction or central object, and never both.')
        self._max_line_of_sight_radius = max_line_of_sight_radius
        self._interaction = interaction
        self._tests = tests
        self._shoo_exempt_tests = shoo_exempt_tests
        self._privacy_constraints = []
        self._allowed_sims = WeakSet()
        self._disallowed_sims = WeakSet()
        self._violators = WeakSet()
        self._late_violators = WeakSet()
        self._exempt_sims = WeakSet()
        self.is_active = False
        self.has_shooed = False
        self.central_object = central_object
        self.additional_exit_offsets = additional_exit_offsets
        self._multi_surface = True
        self.persistent_instance = persistent_instance
        self._routing_surface_only = routing_surface_only
        self._shoo_constraint_radius = shoo_constraint_radius
        self._unavailable_tooltip = unavailable_tooltip
        self._embarrassed_affordance = embarrassed_affordance
        self._reserved_surface_space = reserved_surface_space
        self._post_route_affordance = post_route_affordance
        self._privacy_cost_override = privacy_cost_override
        self.privacy_violators = privacy_violators
        self._vehicle_tests = vehicle_tests
        self._pushed_interactions = []
        if add_to_privacy_service:
            self.add_privacy()

    @property
    def shoo_constraint_radius(self):
        return self._shoo_constraint_radius or self._SHOO_CONSTRAINT_RADIUS

    @property
    def unavailable_tooltip(self):
        return self._unavailable_toolip or self._UNAVAILABLE_TOOLTIP

    @property
    def embarrassed_affordance(self):
        return self._embarrassed_affordance or self._EMBARRASSED_AFFORDANCE

    @property
    def privacy_discouragement_cost(self):
        return self._privacy_cost_override or self._PRIVACY_DISCOURAGEMENT_COST

    @property
    def interaction(self):
        return self._interaction

    @property
    def is_active(self) -> bool:
        return self._is_active

    @is_active.setter
    def is_active(self, value):
        self._is_active = value

    def _is_sim_allowed(self, sim):
        if self._tests:
            resolver = SingleSimResolver(sim.sim_info) if self._interaction is None else self._interaction.get_resolver(target=sim)
            if self._tests and self._tests.run_tests(resolver):
                return True
            elif self._interaction is not None and self._interaction.can_sim_violate_privacy(sim):
                return True
        if self._interaction is not None and self._interaction.can_sim_violate_privacy(sim):
            return True
        return False

    def evaluate_sim(self, sim):
        if self._is_sim_allowed(sim):
            self._allowed_sims.add(sim)
            return True
        self._disallowed_sims.add(sim)
        return False

    def build_privacy(self, target=None):
        self.is_active = True
        if self.central_object is None:
            target_object = self._interaction.get_participant(ParticipantType.Object)
            target_object = None if target_object.is_sim else target_object
            self.central_object = target_object or (target or self._interaction.sim)
        if self._routing_surface_only:
            allow_object_routing_surface = True
            routing_surface = self.central_object.provided_routing_surface
            if routing_surface is None:
                return False
        else:
            allow_object_routing_surface = False
            routing_surface = self.central_object.routing_surface
        self.generate(self.central_object.position, routing_surface, allow_object_routing_surface=allow_object_routing_surface)
        for poly in self.constraint.geometry.polygon:
            self._privacy_constraints.append(PolygonFootprint(poly, routing_surface=routing_surface, cost=self.privacy_discouragement_cost, footprint_type=FootprintType.FOOTPRINT_TYPE_PATH, enabled=True))
        if self._reserved_surface_space is not None and target is not None:
            reserved_space = self._reserved_surface_space.reserved_space
            try:
                polygon = _generate_single_poly_rectangle_points(target.position, target.part_owner.orientation.transform_vector(sims4.math.Vector3.Z_AXIS()), target.part_owner.orientation.transform_vector(sims4.math.Vector3.X_AXIS()), reserved_space.left, reserved_space.right, reserved_space.front, reserved_space.back)
            except AttributeError as exc:
                raise AttributeError('Interaction: {} is trying to reserve surface space with sim as target. Exception:{}'.format(self._interaction, exc))
            routing_surface = self.central_object.provided_routing_surface
            if routing_surface is None:
                routing_surface = target.routing_surface
            footprint_cost = self.privacy_discouragement_cost if self._reserved_surface_space.allow_routing else self._PRIVACY_SURFACE_BLOCKING_FOOTPRINT_COST
            self._privacy_constraints.append(PolygonFootprint(polygon, routing_surface=routing_surface, cost=footprint_cost, footprint_type=FootprintType.FOOTPRINT_TYPE_PATH, enabled=True))
        if self.privacy_violators & PrivacyViolators.SIM:
            if self._interaction is not None:
                self._allowed_sims.update(self._interaction.get_participants(ParticipantType.AllSims))
            for sim in services.sim_info_manager().instanced_sims_gen():
                if sim not in self._allowed_sims:
                    self.evaluate_sim(sim)
            violating_sims = self.find_violating_sims()
            self._exempt_sims = set([s for s in violating_sims if self.is_sim_shoo_exempt(s)])
            self._cancel_unavailable_interactions(violating_sims)
            self._add_overrides_and_constraints_if_needed(violating_sims)
        if self.privacy_violators & PrivacyViolators.VEHICLES:
            violating_vehicles = self.find_violating_vehicles()
            for vehicle in violating_vehicles:
                vehicle.objectrouting_component.handle_privacy_violation(self)
        return True

    def cleanup_privacy_instance(self):
        if self.is_active:
            self.is_active = False
            for sim in self._allowed_sims:
                self.remove_override_for_sim(sim)
            for sim in self._late_violators:
                self.remove_override_for_sim(sim)
            del self._privacy_constraints[:]
            self._allowed_sims.clear()
            self._disallowed_sims.clear()
            self._violators.clear()
            self._late_violators.clear()
            self._exempt_sims.clear()
            self._cancel_pushed_interactions()

    def add_privacy(self):
        services.privacy_service().add_instance(self)

    def remove_privacy(self):
        self.cleanup_privacy_instance()
        services.privacy_service().remove_instance(self)

    def intersects_with_object(self, obj):
        if obj.routing_surface != self.central_object.routing_surface:
            return False
        delta = obj.position - self.central_object.position
        distance = delta.magnitude_2d_squared()
        if distance > self.max_line_of_sight_radius*self.max_line_of_sight_radius:
            return False
        object_footprint = obj.footprint_polygon
        if object_footprint is None:
            object_footprint = sims4.geometry.CompoundPolygon([sims4.geometry.Polygon([obj.position])])
        return self.constraint.geometry.polygon.intersects(object_footprint)

    def vehicle_violates_privacy(self, vehicle):
        if vehicle.objectrouting_component is None:
            return False
        if self._vehicle_tests is not None:
            resolver = SingleObjectResolver(vehicle)
            if self._vehicle_tests.run_tests(resolver):
                return False
            elif not self.intersects_with_object(vehicle):
                return False
        elif not self.intersects_with_object(vehicle):
            return False
        return True

    def find_violating_vehicles(self):
        violators = []
        privacy_service = services.privacy_service()
        for vehicle in privacy_service.get_potential_vehicle_violators():
            if self.vehicle_violates_privacy(vehicle):
                violators.append(vehicle)
        return violators

    def find_violating_sims(self, consider_exempt=True):
        if not self.is_active:
            return []
        if not self.privacy_violators & PrivacyViolators.SIM:
            return []
        check_all_surfaces_on_level = not self._routing_surface_only
        nearby_sims = placement.get_nearby_sims_gen(self.central_object.position, self._routing_surface, radius=self.max_line_of_sight_radius, exclude=self._allowed_sims, only_sim_position=True, check_all_surfaces_on_level=check_all_surfaces_on_level)
        violators = []
        for sim in nearby_sims:
            if consider_exempt and sim in self._exempt_sims:
                continue
            if any(sim_primitive.is_traversing_portal() for sim_primitive in sim.primitives if isinstance(sim_primitive, FollowPath)):
                continue
            if sim not in self._disallowed_sims and self.evaluate_sim(sim):
                continue
            if sims4.geometry.test_point_in_compound_polygon(sim.position, self.constraint.geometry.polygon):
                violators.append(sim)
        return violators

    def is_sim_shoo_exempt(self, sim):
        if sim in self._exempt_sims:
            return True
        if self.central_object.provided_routing_surface == sim.location.routing_surface:
            return False
        elif self._shoo_exempt_tests:
            resolver = SingleSimResolver(sim.sim_info)
            if self._shoo_exempt_tests.run_tests(resolver):
                return True
        return False

    def add_exempt_sim(self, sim):
        self._exempt_sims.add(sim)

    def _add_overrides_and_constraints_if_needed(self, violating_sims):
        for sim in self._allowed_sims:
            self.add_override_for_sim(sim)
        for sim in violating_sims:
            self._violators.add(sim)
            if sim in self._exempt_sims:
                continue
            liabilities = ((SHOO_LIABILITY, ShooLiability(self, sim)),)
            result = self._route_sim_away(sim, liabilities=liabilities)
            if result:
                self._pushed_interactions.append(result.interaction)

    def _cancel_unavailable_interactions(self, violating_sims):
        for sim in violating_sims:
            if sim in self._exempt_sims:
                continue
            interactions_to_cancel = set()
            if sim.queue.running is not None:
                interactions_to_cancel.add(sim.queue.running)
            for interaction in sim.si_state:
                if interaction.is_super:
                    if interaction.target is not None:
                        if sim.locked_from_obj_by_privacy(interaction.target):
                            interactions_to_cancel.add(interaction)
            for interaction in sim.queue:
                if interaction.target is not None and sim.locked_from_obj_by_privacy(interaction.target):
                    interactions_to_cancel.add(interaction)
                elif interaction.target is not None:
                    break
            for interaction in interactions_to_cancel:
                interaction.cancel(FinishingType.INTERACTION_INCOMPATIBILITY, cancel_reason_msg='Canceled due to incompatibility with privacy instance.')

    def _route_sim_away(self, sim, liabilities=()):
        context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.NEXT)
        from interactions.utils.satisfy_constraint_interaction import BuildAndForceSatisfyShooConstraintInteraction
        result = sim.push_super_affordance(BuildAndForceSatisfyShooConstraintInteraction, None, context, liabilities=liabilities, privacy_inst=self, name_override='BuildShooFromPrivacy')
        if result:
            if self._post_route_affordance is not None:

                def route_away_callback(_):
                    post_route_context = context.clone_for_continuation(result.interaction)
                    sim.push_super_affordance(self._post_route_affordance, None, post_route_context)

                result.interaction.register_on_finishing_callback(route_away_callback)
        else:
            logger.debug('Failed to push BuildAndForceSatisfyShooConstraintInteraction on Sim {} to route them out of a privacy area.  Result: {}', sim, result, owner='tastle')
            if self.interaction is not None:
                self.interaction.cancel(FinishingType.TRANSITION_FAILURE, cancel_reason_msg='Failed to shoo Sims away.')
        return result

    def _cancel_pushed_interactions(self):
        for interaction in self._pushed_interactions:
            interaction.cancel(FinishingType.AUTO_EXIT, cancel_reason_msg='Privacy finished and is cleaning up.')
        self._pushed_interactions.clear()

    def handle_late_violator(self, sim):
        self._cancel_unavailable_interactions((sim,))
        self.add_override_for_sim(sim)
        liabilities = ((LATE_SHOO_LIABILITY, LateShooLiability(self, sim)),)
        result = self._route_sim_away(sim, liabilities=liabilities)
        if not result:
            return
        if not self._violators:
            context = InteractionContext(sim, InteractionContext.SOURCE_SCRIPT, Priority.High, insert_strategy=QueueInsertStrategy.NEXT)
            if self.interaction is None:
                result = sim.push_super_affordance(self.embarrassed_affordance, sim, context)
            else:
                result = sim.push_super_affordance(self.embarrassed_affordance, self.interaction.get_participant(ParticipantType.Actor), context)
            if not result and not services.sim_spawner_service().sim_is_leaving(sim):
                logger.warn('Failed to push the embarrassed affordance on Sim {}. Interaction {}. Result {}. Context {} ', sim, self.interaction, result, context, owner='tastle')
                return
        self._late_violators.add(sim)

    def add_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.ignore_footprint_contour(footprint.footprint_id)

    def remove_override_for_sim(self, sim):
        for footprint in self._privacy_constraints:
            sim.routing_context.remove_footprint_contour_override(footprint.footprint_id)

    @property
    def allowed_sims(self):
        return self._allowed_sims

    @property
    def disallowed_sims(self):
        return self._disallowed_sims

    def remove_sim_from_allowed_disallowed(self, sim):
        if sim in self._allowed_sims:
            self._allowed_sims.remove(sim)
        if sim in self._disallowed_sims:
            self._disallowed_sims.remove(sim)

    @property
    def violators(self):
        return self._violators

    def remove_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._violators.discard(sim)

    @property
    def late_violators(self):
        return self._late_violators

    def remove_late_violator(self, sim):
        self.remove_override_for_sim(sim)
        self._late_violators.discard(sim)