Exemplo n.º 1
0
class LightingLiability(Liability, HasTunableFactory, AutoFactoryInit):
    LIABILITY_TOKEN = 'LightingLiability'
    FACTORY_TUNABLES = {'radius_squared': TunableDistanceSquared(description='\n            The distance away from the specified participant that lights will\n            be turned off.\n            ', default=1, display_name='Radius'), 'participant': TunableEnumEntry(description='\n            The participant of the interaction that we will be used as the\n            center of the radius to turn lights off.\n            ', tunable_type=ParticipantType, default=ParticipantType.Actor)}

    def __init__(self, interaction, **kwargs):
        super().__init__(**kwargs)
        self._interaction = interaction
        self._lights = WeakSet()
        self._automated_lights = WeakSet()

    def on_run(self):
        if self._lights:
            return
        participant = self._interaction.get_participant(self.participant)
        position = participant.position
        for obj in services.object_manager().get_all_objects_with_component_gen(objects.components.types.LIGHTING_COMPONENT):
            if get_object_has_tag(obj.definition.id, LightingComponent.MANUAL_LIGHT_TAG):
                continue
            distance_from_pos = obj.position - position
            if distance_from_pos.magnitude_squared() > self.radius_squared:
                continue
            if obj.get_light_dimmer_value() == LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE:
                self._automated_lights.add(obj)
            else:
                self._lights.add(obj)
            obj.set_light_dimmer_value(LightingComponent.LIGHT_DIMMER_VALUE_OFF)

    def release(self):
        for obj in self._lights:
            obj.set_light_dimmer_value(LightingComponent.LIGHT_DIMMER_VALUE_MAX_INTENSITY)
        self._lights.clear()
        for obj in self._automated_lights:
            obj.set_light_dimmer_value(LightingComponent.LIGHT_AUTOMATION_DIMMER_VALUE)
        self._automated_lights.clear()
Exemplo n.º 2
0
class FollowSim:
    __qualname__ = 'FollowSim'
    RETRIES = Tunable(int, 3, description='The maximum number of route attempts the following sim will make.')
    MIN_DISTANCE_SQ = TunableDistanceSquared(5, description='The minimum distance between the two sims to consider the route a success.')
    TARGET_DISTANCE = TunableRange(float, 1.0, minimum=0, description='The distance to which we attempt to route when following another Sim.')

    @staticmethod
    def sim_within_range_of_target(sim, target):
        distance_sq = (target.position - sim.position).magnitude_squared()
        if distance_sq < FollowSim.MIN_DISTANCE_SQ:
            return True
        return False
Exemplo n.º 3
0
    class _MassObjectResurrection(_BaseSimInfoSource):
        FACTORY_TUNABLES = {
            'participant':
            TunableEnumEntry(
                description=
                '\n                The participant of the interaction that will have sims resurrected\n                around their position.\n                ',
                tunable_type=ParticipantType,
                default=ParticipantType.Actor),
            'radius':
            TunableDistanceSquared(
                description=
                '\n                The distance around a participant that will resurrect all of the\n                dead sim objects.\n                ',
                default=1),
            'tag':
            TunableEnumEntry(
                description=
                '\n                Tag the delineates an object that we want to resurrect sims\n                from.\n                ',
                tunable_type=Tag,
                default=Tag.INVALID)
        }

        def get_sim_infos_and_positions(self, resolver, household):
            use_fgl = True
            sim_infos_and_positions = []
            participant = resolver.get_participant(self.participant)
            position = participant.position
            for obj in services.object_manager().get_objects_with_tag_gen(
                    self.tag):
                obj_position = obj.position
                distance_from_pos = obj_position - position
                if distance_from_pos.magnitude_squared() > self.radius:
                    continue
                sim_info = obj.get_stored_sim_info()
                if sim_info is None:
                    continue
                sim_infos_and_positions.append(
                    (sim_info, obj_position, None, use_fgl))
            return tuple(sim_infos_and_positions)

        def do_pre_spawn_behavior(self, sim_info, resolver, household):
            super().do_pre_spawn_behavior(sim_info, resolver, household)
            sims.ghost.Ghost.remove_ghost_from_sim(sim_info)
Exemplo n.º 4
0
class _WaypointStitchingWaypoints(_WaypointStitchingBase,
                                  HasTunableSingletonFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {
        'max_distance':
        TunableDistanceSquared(
            description=
            "\n            If the route's cumulative distance is more than this value, the\n            route is split.\n            ",
            default=500),
        'max_goals':
        TunableRange(
            description=
            '\n            If the route has more than this number of goals, the route is\n            split.\n            ',
            tunable_type=int,
            minimum=0,
            default=200)
    }

    def __call__(self, waypoints, loops=1):
        waypoint_groups = [[]]
        group_dist = 0
        group_num_goals = 0
        previous_centroid = None
        for goals in itertools.chain.from_iterable(
                itertools.repeat(waypoints, loops)):
            num_goals = len(goals)
            centroid = sum([goal.position for goal in goals],
                           sims4.math.Vector3.ZERO()) / num_goals
            dist = (previous_centroid - centroid).magnitude_2d_squared(
            ) if previous_centroid is not None else 0
            current_group = waypoint_groups[-1]
            if len(current_group) > 1:
                if dist + group_dist > self.max_distance:
                    if num_goals + group_num_goals > self.max_goals:
                        waypoint_groups.append(current_group)
                        current_group = [current_group[-1]]
                        group_dist = 0
                        group_num_goals = len(current_group[0])
            current_group.append(goals)
            group_dist += dist
            group_num_goals += num_goals
            previous_centroid = centroid
        yield from waypoint_groups
class _DestroyObjectSelectionRuleTags(_DestroyObjectSelectionRule):
    FACTORY_TUNABLES = {
        'tags':
        TunableTags(
            description=
            '\n            Only objects with these tags are considered.\n            ',
            filter_prefixes=('Func', )),
        'radius':
        TunableDistanceSquared(
            description=
            '\n            Only objects within this distance are considered.\n            ',
            default=1)
    }

    def get_objects(self, obj, target):
        objects = tuple(
            o for o in services.object_manager().get_objects_matching_tags(
                self.tags, match_any=True)
            if (o.position - obj.position).magnitude_squared() <= self.radius)
        return objects
Exemplo n.º 6
0
class ObjectClusterRequest(HasTunableFactory, AutoFactoryInit):
    FACTORY_TUNABLES = {'density_epsilon': TunableDistanceSquared(description='\n            A constant that defines whether or not two objects are reachable.\n            ', default=2.82), 'facing_angle': OptionalTunable(description='\n            If set, then objects in a cluster must satisfy facing requirements.\n            ', tunable=TunableAngle(description='\n                An angle that defines the facing requirements for the purposes\n                of clustering and centroid facing.\n                ', default=sims4.math.PI)), 'minimum_size': TunableRange(description='\n            The minimum required size for clusters. Group of objects less than\n            this constant will not form clusters.\n            ', tunable_type=int, minimum=2, default=3), 'line_of_sight_constraint': TunableLineOfSightData(description='\n            The line of sight parameters for generated clusters.\n            '), 'radius_buffer': TunableRange(description='\n            An additional distance (in meters) that will be added to the radius\n            of a cluster. The size of a cluster is based on the objects in it.\n            We need to add an additional amount to ensure that the object is\n            included in the (non-exact) circle constraint.\n            ', tunable_type=float, minimum=0, default=0.5)}
    FACING_EPSILON = 0.01

    def __init__(self, get_objects_gen, quadtree=None, **kwargs):
        super().__init__(**kwargs)
        self._clusters = []
        self._get_objects_gen = get_objects_gen
        self._cache = None
        self._dirty = True
        self._quadtree = quadtree
        self._rejects = ()
        self._clusters_to_update_cache = None
        self._combination_to_update_cache = None
        self._reachable_cache = {}
        services.current_zone().object_cluster_service.register_cluster_request(self)

    def on_teardown(self):
        self._reachable_cache = {}

    def get_clusters_gen(self, regenerate=False):
        if self._dirty or regenerate:
            self._generate_clusters()
            self._clusters_to_update_cache = list(self._clusters)
            services.current_zone().object_cluster_service.set_cluster_cache_dirty()
        for cluster in self._clusters:
            yield cluster

    def timeslice_cache_generation(self, end_time):
        while time.monotonic() < end_time:
            if self._combination_to_update_cache:
                (a, b) = self._combination_to_update_cache.pop()
                self._is_reachable(a, b)
            elif self._clusters_to_update_cache:
                objects_to_process = self._clusters_to_update_cache.pop().objects_gen()
                self._combination_to_update_cache = list(itertools.combinations(objects_to_process, 2))
            else:
                return True
        return False

    def get_rejects(self):
        return self._rejects

    def _get_score(self, cluster, p):
        return (cluster.position - p).magnitude_squared()

    def get_closest_valid_cluster(self, constraint, radius=None):
        best_score = None
        best_cluster = None
        for cluster in self.get_clusters_gen():
            for sub_constraint in constraint:
                if sub_constraint.routing_surface != cluster.routing_surface:
                    continue
                constraint_position = sub_constraint.average_position
                if radius is not None and (constraint_position - cluster.position).magnitude_2d() > radius:
                    continue
                if not sub_constraint.intersect(cluster.constraint).valid:
                    continue
                score = self._get_score(cluster, constraint_position)
                if not best_score is None:
                    if score < best_score:
                        best_score = score
                        best_cluster = cluster
                best_score = score
                best_cluster = cluster
        return best_cluster

    def set_dirty(self, full_update=False):
        self._dirty = True
        self._clusters_to_update_cache = None
        self._combination_to_update_cache = None
        if full_update:
            self._reachable_cache.clear()

    def set_object_dirty(self, obj):
        object_id = obj.id
        invalid_keys = [cache_key for cache_key in self._reachable_cache if object_id in cache_key]
        for cache_key in invalid_keys:
            del self._reachable_cache[cache_key]
        self.set_dirty()

    def is_dirty(self):
        return self._dirty

    @contextmanager
    def _caching(self):
        self._cache = {}
        try:
            yield None
        finally:
            self._cache = None
            self._dirty = False

    def _is_facing(self, a, b):
        interval = interval_from_facing_angle(vector3_angle(a.position - b.position), self.facing_angle + self.FACING_EPSILON)
        facing = vector3_angle(b.forward)
        return facing in interval

    def _is_reachable(self, a, b):
        if b.id > a.id:
            cache_key = (a.id, b.id)
        else:
            cache_key = (b.id, a.id)
        if cache_key in self._reachable_cache:
            return self._reachable_cache[cache_key]
        result = self._is_reachable_no_cache(a, b)
        self._reachable_cache[cache_key] = result
        return result

    def _is_reachable_no_cache(self, a, b):
        if a.routing_surface != b.routing_surface:
            return False
        if (a.position - b.position).magnitude_squared() <= self.density_epsilon:
            if not (self.facing_angle is not None and self._is_facing(a, b) and self._is_facing(b, a)):
                return False
            else:
                point_constraint = interactions.constraints.Position(a.lineofsight_component.default_position, routing_surface=a.routing_surface)
                test_constraint = point_constraint.intersect(b.lineofsight_component.constraint)
                if test_constraint.valid:
                    return True
        return False

    def _get_reachable_objects(self, obj, objects, can_cache=True):
        reachable_objects = self._cache.get(obj, None)
        if reachable_objects is not None:
            reachable_objects = reachable_objects & objects
        else:
            reachable_objects = set()
            for other_obj in self._get_reachable_objects_candidates(obj, objects):
                if other_obj is not obj:
                    if self._is_reachable(obj, other_obj):
                        reachable_objects.add(other_obj)
        if can_cache:
            self._cache[obj] = reachable_objects.copy()
        return reachable_objects

    def _has_enough_reachable_neighbors(self, obj, neighbor_objects, count):
        reachable_objects = self._cache.get(obj, None)
        if reachable_objects is not None:
            for obj in reachable_objects:
                if obj in neighbor_objects:
                    count -= 1
                    if count == 0:
                        return True
            return len(reachable_objects & neighbor_objects) >= count
        for other_obj in self._get_reachable_objects_candidates(obj, neighbor_objects):
            if other_obj is not obj:
                if self._is_reachable(obj, other_obj):
                    count -= 1
                    if count == 0:
                        return True
        return False

    def _get_reachable_objects_candidates(self, obj, objects):
        if self._quadtree is not None:
            query_bounds = sims4.geometry.QtCircle(sims4.math.Vector2(obj.position.x, obj.position.z), math.sqrt(self.density_epsilon))
            return tuple(quadtree_obj for quadtree_obj in self._quadtree.query(query_bounds) if quadtree_obj in objects)
        return objects

    def _get_cluster_radius(self, position, objects):
        max_obj_dist_sq = 0
        for obj in objects:
            if obj.parts:
                for part in obj.parts:
                    dist_sq = (part.position - position).magnitude_2d_squared()
                    if dist_sq > max_obj_dist_sq:
                        max_obj_dist_sq = dist_sq
                else:
                    dist_sq = (obj.position - position).magnitude_2d_squared()
                    if dist_sq > max_obj_dist_sq:
                        max_obj_dist_sq = dist_sq
            else:
                dist_sq = (obj.position - position).magnitude_2d_squared()
                if dist_sq > max_obj_dist_sq:
                    max_obj_dist_sq = dist_sq
        obj_dist = sims4.math.sqrt(max_obj_dist_sq) + self.radius_buffer
        return min(obj_dist, self.line_of_sight_constraint.max_line_of_sight_radius)

    def _get_cluster_polygon(self, position, objects):
        hull_points = [position]
        for obj in objects:
            if obj.parts:
                for part in obj.parts:
                    hull_points.append(part.position)
                else:
                    hull_points.append(obj.position)
            else:
                hull_points.append(obj.position)
        polygon = Polygon(hull_points)
        polygon = polygon.get_convex_hull()
        if len(polygon) == 2 or polygon.too_thin or polygon.too_small:
            sorted_x = sorted(hull_points, key=lambda p: p.x)
            sorted_z = sorted(hull_points, key=lambda p: p.z)
            delta_x = sorted_x[-1].x - sorted_x[0].x
            delta_z = sorted_z[-1].z - sorted_z[0].z
            extents = sorted_x if delta_x > delta_z else sorted_z
            a = extents[0]
            b = extents[-1]
            polygon = build_rectangle_from_two_points_and_radius(a, b, self.radius_buffer)
        else:
            polygon = sims4.geometry.inflate_polygon(polygon, self.radius_buffer)
        compound_polygon = sims4.geometry.CompoundPolygon([polygon])
        return compound_polygon

    def _generate_cluster(self, position, objects):
        radius = self._get_cluster_radius(position, objects)
        los = LineOfSight(radius, self.line_of_sight_constraint.map_divisions, self.line_of_sight_constraint.simplification_ratio, self.line_of_sight_constraint.boundary_epsilon, debug_str_data=('_generate_cluster LOS Constraint, Cluster Objects: {}', objects))
        routing_surface = next(iter(objects)).routing_surface
        los.generate(position, routing_surface, build_convex=True)
        valid_objects = []
        rejects = []
        if los.constraint_convex.geometry is not None:
            for obj in objects:
                for polygon in los.constraint_convex.geometry.polygon:
                    if test_point_in_polygon(obj.lineofsight_component.default_position, polygon):
                        valid_objects.append(obj)
                        break
                else:
                    rejects.append(obj)
        else:
            rejects = objects
        if not valid_objects:
            (rejected_ne, rejected_nw, rejected_se, rejected_sw) = ([], [], [], [])
            for reject in rejects:
                reject_position = reject.lineofsight_component.default_position
                if reject_position.x >= position.x and reject_position.z >= position.z:
                    rejected_ne.append(reject)
                elif reject_position.z >= position.z:
                    rejected_nw.append(reject)
                elif reject_position.x < position.x and reject_position.z < position.z:
                    rejected_sw.append(reject)
                else:
                    rejected_se.append(reject)
            return (rejected_ne, rejected_nw, rejected_se, rejected_sw)
        convex_hull_poly = self._get_cluster_polygon(position, valid_objects)
        convex_hull_constraint = Constraint(debug_name='ClusterConvexHull', routing_surface=routing_surface, allow_small_intersections=True, geometry=sims4.geometry.RestrictedPolygon(convex_hull_poly, []))
        cluster_constraint = los.constraint_convex.intersect(convex_hull_constraint)
        if cluster_constraint.valid:
            cluster = ObjectCluster(position, cluster_constraint, valid_objects, routing_surface)
            self._clusters.append(cluster)
        return [rejects]

    def _get_clusters(self, objects):
        closed = set()
        clusters = []
        for obj in objects:
            if obj not in closed:
                closed.add(obj)
                pending_neighbors = self._get_reachable_objects(obj, objects)
                if len(pending_neighbors) >= self.minimum_size - 1:
                    cluster = set()
                    cluster.add(obj)
                    all_neighbors = set(pending_neighbors)
                    all_neighbors.add(obj)
                    non_neighbor_objects = objects - all_neighbors
                    while pending_neighbors:
                        neighbor = pending_neighbors.pop()
                        if neighbor not in closed:
                            closed.add(neighbor)
                            new_neighbors = self._get_reachable_objects(neighbor, non_neighbor_objects, False)
                            if new_neighbors:
                                required = self.minimum_size - 1 - len(new_neighbors)
                                if required <= 0 or self._has_enough_reachable_neighbors(neighbor, all_neighbors, required):
                                    all_neighbors.update(new_neighbors)
                                    pending_neighbors.update(new_neighbors)
                                    non_neighbor_objects -= new_neighbors
                        if not any(neighbor in c for c in itertools.chain(self._clusters, clusters)):
                            cluster.add(neighbor)
                    clusters.append(cluster)
        return clusters

    def _generate_clusters(self):
        with self._caching():
            del self._clusters[:]
            objects = [set(self._get_objects_gen())]
            all_rejects = set()
            while objects:
                clusters = self._get_clusters(objects.pop())
                for cluster in clusters:
                    polygon = Polygon([obj.position for obj in cluster])
                    centroid = polygon.centroid()
                    facing_rejects = []
                    for obj in list(cluster):
                        if self.facing_angle is not None:
                            interval = interval_from_facing_angle(vector3_angle(centroid - obj.position), self.facing_angle + self.FACING_EPSILON)
                            facing = vector3_angle(obj.forward)
                            is_facing = facing in interval
                        else:
                            is_facing = True
                        if not is_facing:
                            if not vector3_almost_equal_2d(centroid, obj.position, epsilon=0.01):
                                cluster.remove(obj)
                                facing_rejects.append(obj)
                    if len(cluster) >= self.minimum_size:
                        rejected_sets = self._generate_cluster(centroid, cluster)
                        for rejected_set in itertools.chain((facing_rejects,), rejected_sets):
                            unused_rejects = set(obj for obj in rejected_set if obj not in all_rejects)
                            all_rejects.update(unused_rejects)
                            if len(unused_rejects) >= self.minimum_size:
                                objects.append(unused_rejects)
            self._rejects = [reject for reject in all_rejects if not any(reject in cluster for cluster in self._clusters)]
class LinkedObjectComponent(
        AutoFactoryInit,
        objects.components.Component,
        HasTunableFactory,
        allow_dynamic=True,
        component_name=objects.components.types.LINKED_OBJECT_COMPONENT):
    FACTORY_TUNABLES = {
        '_parent_state_value':
        OptionalTunable(
            description=
            "\n            When enabled, this state will be applied to the parent when\n            it has children.\n            \n            For example, the default link state for the console is unlinked.\n            If you set this to the linked state, then when it becomes the\n            parent to a T.V. it'll change the console to the linked state.\n            When the T.V. is unlinked, the console will revert back to \n            the unlinked state.\n            ",
            tunable=TunablePackSafeReference(
                description=
                '\n                state value to apply to parent objects.\n                Behaves as disabled if state not in installed data.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE),
                class_restrictions=('ObjectStateValue', ))),
        '_child_state_value':
        OptionalTunable(
            description=
            "\n            When enabled, this state will be applied to the children.\n\n            For example, the default link state for a T.V is unlinked.\n            If you set this to the linked state, then when it becomes the\n            child of a console. it'll change the T.V. to the linked state.\n            When the T.V. is unlinked, the T.V. will revert back to \n            the unlinked state.\n            ",
            tunable=TunablePackSafeReference(
                description=
                '\n                state value to apply to child objects.\n                Behaves as disabled if state not in installed data.\n                ',
                manager=services.get_instance_manager(
                    sims4.resources.Types.OBJECT_STATE),
                class_restrictions=('ObjectStateValue', ))),
        '_child_tag':
        TunableEnumWithFilter(
            description=
            '\n            Tag that determines which objects can be linked.\n            ',
            tunable_type=tag.Tag,
            filter_prefixes=['func'],
            default=tag.Tag.INVALID,
            invalid_enums=(tag.Tag.INVALID, )),
        '_distance':
        TunableDistanceSquared(
            description=
            '\n            Max distance from component owner and still be\n            linkable.\n            ',
            default=3),
        '_count':
        TunableRange(
            description=
            '\n            Max number of children to link.\n            ',
            tunable_type=int,
            default=1,
            minimum=1)
    }

    def __init__(self, *args, parent=True, **kwargs):
        super().__init__(*args, **kwargs)
        self._children = WeakSet()
        self._parent = self.owner
        self._return_state_value = None
        self._is_parent = parent

    def on_add(self):
        self._start()

    def on_remove(self):
        self._stop()

    def on_added_to_inventory(self):
        self._stop()

    def on_removed_from_inventory(self):
        self._start()
        if self._is_parent:
            self._add_all()

    def component_reset(self, reset_reason):
        if reset_reason != ResetReason.BEING_DESTROYED:
            self._relink(from_reset=True)
        elif self._is_parent:
            self.unlink_all_children()

    def on_finalize_load(self):
        self._relink()

    def on_post_load(self):
        if self._is_parent and self._children:
            self._return_state_value = None
            self.link(None, self._parent_state_value)

    def on_location_changed(self, old_location):
        if not services.current_zone(
        ).is_zone_loading and self._parent is not self.owner:
            self._relink(
                update_others=not services.current_zone().is_in_build_buy)

    def on_reset_component_get_interdependent_reset_records(
            self, reset_reason, reset_records):
        owner_users = self.owner.get_users()
        for obj in self.get_linked_objects_gen():
            if self._has_active_link(owner_users, obj):
                reset_records.append(
                    ResetRecord(obj, ResetReason.RESET_EXPECTED, self,
                                'Linked object reset'))

    def _start(self):
        self._parent = None
        if self._is_parent:
            build_buy.register_build_buy_exit_callback(self._relink)

    def _add_all(self):
        self._children = self._get_nearby_objects()
        for child in self._children:
            self._link_child(child)
        if self._children:
            self.link(None, self._parent_state_value)

    def _stop(self):
        if self._parent is self.owner:
            return
        old_parent = self._parent
        self._parent = self.owner
        if self._is_parent:
            build_buy.unregister_build_buy_exit_callback(self._relink)
            self.unlink_all_children()
        elif old_parent is not None:
            old_parent.linked_object_component.child_unlinked(self.owner)

    def unlink_all_children(self, update_others=False):
        for child in self._children:
            self._unlink(child)
        if update_others:
            self._update_others(self._children)
        self._children.clear()
        self.unlink_self()

    def _has_active_link(self, owner_users, severed_object):
        return owner_users & severed_object.get_users()

    def _relink(self, update_others=False, from_reset=False):
        if self._is_parent:
            if not self._children:
                self._add_all()
                return
            new_children = self._get_nearby_objects()
            removed_children = self._children - new_children
            if not from_reset:
                owner_users = self.owner.get_users()
                for child in removed_children:
                    if self._has_active_link(owner_users, child):
                        self.owner.reset(ResetReason.RESET_EXPECTED, None,
                                         'Unlinking child')
                        return
            if not new_children:
                self.unlink_all_children(update_others=update_others)
                return
            for child in removed_children:
                self._unlink(child)
            for child in new_children - self._children:
                self._link_child(child)
            self._children = new_children
            if removed_children and update_others:
                self._update_others(removed_children)
        elif self._parent is not None:
            self._parent.linked_object_component.refresh(self.owner)

    def refresh(self, child):
        if self._is_parent:
            if child not in self._children:
                logger.error(
                    "Refreshing linked child: {} that isn't in parent {}",
                    child, self.owner)
                return
            child.linked_object_component.link(self.owner,
                                               self._child_state_value)

    def unlink_self(self):
        if self._parent is not self.owner:
            self._parent = None
        if self._return_state_value is not None:
            self.owner.state_component.reset_state_to_default(
                self._return_state_value)
        self._return_state_value = None

    def _unlink(self, child):
        if child not in self._children:
            logger.error("Removing linked child: {} that isn't in parent {}",
                         child, self.owner)
            return
        child.reset(ResetReason.RESET_EXPECTED, self.owner,
                    'Unlinking from parent')
        child.linked_object_component.unlink_self()
        child.remove_component(
            objects.components.types.LINKED_OBJECT_COMPONENT)

    def child_unlinked(self, child):
        if child not in self._children:
            logger.error("Removing linked child: {} that isn't in parent {}",
                         child, self.owner)
            return
        self._relink()

    def _link_child(self, child):
        if child.linked_object_component is None:
            child.add_dynamic_component(
                objects.components.types.LINKED_OBJECT_COMPONENT,
                parent=False,
                _parent_state_value=None,
                _child_tag=None,
                _child_state_value=None,
                _count=None,
                _distance=None)
        child.linked_object_component.link(self.owner, self._child_state_value)

    def link(self, parent, state_value):
        self._parent = parent
        if state_value is not None:
            state_component = self.owner.state_component
            if state_component is not None:
                state = state_value.state
                if self._return_state_value is None:
                    if state_component.has_state(state):
                        self._return_state_value = state_component.get_state(
                            state)
                state_component.set_state(state_value.state, state_value)

    @componentmethod_with_fallback(lambda: [])
    def get_linked_objects_gen(self):
        if self._is_parent:
            yield from self._children
        elif self._parent is not None:
            if self._parent is not self.owner:
                yield self._parent
                for child in self._parent.linked_object_component.get_linked_objects_gen(
                ):
                    if child is not self.owner:
                        yield child

    def _get_nearby_objects(self):
        if self.owner.is_hidden():
            return ()
        filtered_near_objects = []
        nearby_objects = services.object_manager().get_objects_with_tag_gen(
            self._child_tag)
        for test_object in nearby_objects:
            if self._is_valid_child(test_object):
                dist_square = (self.owner.position -
                               test_object.position).magnitude_2d_squared()
                if dist_square < self._distance:
                    filtered_near_objects.append((dist_square, test_object))
        filtered_near_objects.sort(key=operator.itemgetter(0))
        return_list = set([x[1] for x in filtered_near_objects[:self._count]])
        return return_list

    def _is_valid_child(self, test_object):
        linked_object_component = test_object.linked_object_component
        if linked_object_component is not None:
            if linked_object_component._is_parent:
                return False
            if linked_object_component._parent is not self.owner and test_object.linked_object_component._parent is not None:
                return False
        if test_object.level != self.owner.level:
            return False
        elif test_object.is_hidden():
            return False
        return True

    def _update_others(self, new_children):
        owner = self.owner
        for obj in services.object_manager().get_valid_objects_gen():
            if obj.linked_object_component is not None:
                if obj is not owner:
                    new_children = obj.linked_object_component._try_add_links(
                        new_children)
                    if not new_children:
                        break

    def _try_add_links(self, new_children):
        if self.owner is self._parent:
            return new_children
        if len(self._children) == self._count:
            return new_children
        else:
            filtered_near_objects = []
            for test_object in new_children:
                if test_object.has_tag(self._child_tag):
                    if test_object.level == self.owner.level:
                        dist_square = (
                            self.owner.position -
                            test_object.position).magnitude_2d_squared()
                        if dist_square < self._distance:
                            filtered_near_objects.append(
                                (dist_square, test_object))
            if filtered_near_objects:
                filtered_near_objects.sort(key=operator.itemgetter(0))
                new_set = set([
                    x[1] for x in filtered_near_objects[:self._count -
                                                        len(self._children)]
                ])
                for child in new_set:
                    self._link_child(child)
                if not self._children:
                    self.link(None, self._parent_state_value)
                self._children |= new_set
                return new_children - new_set
        return new_children
Exemplo n.º 8
0
class TeleportTuning:
    TELEPORT_DATA_MAPPING = TunableMapping(
        description=
        '\n        A mapping from a a teleport style to the animation, xevt and vfx data\n        that the Sim will use when a teleport is triggered.\n        ',
        key_type=TunableEnumEntry(
            description='\n            Teleport style.\n            ',
            tunable_type=TeleportStyle,
            default=TeleportStyle.NONE,
            pack_safe=True,
            invalid_enums=(TeleportStyle.NONE, )),
        value_type=TunableTuple(
            description=
            '\n            Animation and vfx data data to be used when the teleport is \n            triggered.\n            ',
            animation_outcomes=TunableList(
                description=
                '\n                One of these animations will be played when the teleport\n                happens, and weights + modifiers can be used to determine\n                exactly which animation is played based on tests.\n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    A pairing of animation and weights that determine which\n                    animation is played when using this teleport style.  Any\n                    tests in the multipliers will be using the context from\n                    the interaction that plays the teleportStyle.\n                    ',
                    animation=TunableAnimationReference(
                        description=
                        '\n                        Reference of the animation to be played when the teleport is\n                        triggered.\n                        ',
                        pack_safe=True,
                        callback=None),
                    weight=TunableMultiplier.TunableFactory(
                        description=
                        '\n                        A tunable list of tests and multipliers to apply to the \n                        weight of the animation that is selected for the teleport.\n                        '
                    ))),
            start_teleport_vfx_xevt=Tunable(
                description=
                '\n                Xevent when the Sim starts teleporting to play the fade out\n                VFX.\n                ',
                tunable_type=int,
                default=100),
            start_teleport_fade_sim_xevt=Tunable(
                description=
                '\n                Xevent when the sim starts teleporting to start the fading\n                of the Sim.\n                ',
                tunable_type=int,
                default=100),
            fade_out_effect=OptionalTunable(
                description=
                '\n                If enabled, play an additional VFX on the specified \n                fade_out_xevt when fading out the Sim.\n                ',
                tunable=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim fades out before actual\n                    changing its position.\n                    This effect will not be parented to the Sim, but instead will\n                    play on the bone position without attachment.  This will\n                    guarantee the VFX will not become invisible as the Sim \n                    disappears.\n                    i.e. Vampire bat teleport spawns VFX on the Sims position\n                    '
                ),
                enabled_name='play_effect',
                disabled_name='no_effect'),
            tested_fade_out_effect=TunableTestedList(
                description=
                '\n                A list of possible fade out effects to play tested against\n                the Sim that is teleporting.\n                ',
                tunable_type=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim fades out before actual\n                    changing its position.\n                    This effect will not be parented to the Sim, but instead will\n                    play on the bone position without attachment.  This will\n                    guarantee the VFX will not become invisible as the Sim \n                    disappears.\n                    i.e. Vampire bat teleport spawns VFX on the Sims position\n                    '
                )),
            teleport_xevt=Tunable(
                description=
                '\n                Xevent where the teleport should happen.\n                ',
                tunable_type=int,
                default=100),
            teleport_effect=OptionalTunable(
                description=
                '\n                If enabled, play an additional VFX on the specified \n                teleport_xevt when the teleport (actual movement of the \n                position of the Sim) happens.\n                ',
                tunable=PlayEffect.TunableFactory(
                    description=
                    '\n                    The effect to play when the Sim is teleported.\n                    '
                ),
                enabled_name='play_effect',
                disabled_name='no_effect'),
            teleport_min_distance=TunableDistanceSquared(
                description=
                '\n                Minimum distance between the Sim and its target to trigger\n                a teleport.  If the distance is lower than this value, the\n                Sim will run a normal route.\n                ',
                default=5.0),
            teleport_cost=OptionalTunable(
                description=
                '\n                If enabled, the teleport will have an statistic cost every\n                time its triggered. \n                ',
                tunable=TunableTuple(
                    description=
                    '\n                    Cost and statistic to charge for a teleport event.\n                    ',
                    teleport_statistic=TunableReference(
                        description=
                        '\n                        The statistic we are operating on when a teleport \n                        happens.\n                        ',
                        manager=services.get_instance_manager(
                            sims4.resources.Types.STATISTIC),
                        pack_safe=True),
                    cost=TunableRange(
                        description=
                        '\n                        On teleport, subtract the teleport_statistic by this\n                        amount. \n                        ',
                        tunable_type=int,
                        default=1,
                        minimum=0),
                    cost_is_additive=Tunable(
                        description=
                        '\n                        If checked, the cost is additive.  Rather than deducting the cost, it will be added to\n                        the specified teleport statistic.  Additionally, cost will be checked against the max value\n                        of the statistic rather than the minimum value when determining if the cost is affordable\n                        ',
                        tunable_type=bool,
                        default=False)),
                disabled_name='no_teleport_cost',
                enabled_name='specify_cost'),
            fade_duration=TunableSimMinute(
                description=
                '\n                Default fade time (in sim minutes) for the fading of the Sim\n                to happen.',
                default=0.5)))

    @classmethod
    def get_teleport_data(cls, teleport_type):
        return cls.TELEPORT_DATA_MAPPING.get(teleport_type)
Exemplo n.º 9
0
class NearbyFloorFeatureTest(HasTunableSingletonFactory, AutoFactoryInit, BaseTest):
    FACTORY_TUNABLES = {'floor_feature': TunableEnumEntry(description="\n            The floor feature type that is required to be inside the radius_actor's\n            radius.\n            ", tunable_type=FloorFeatureType, default=FloorFeatureType.BURNT), 'radius': TunableDistanceSquared(description="\n            The radius, with the radius actor's position, that defines the area\n            within which the floor feature is valid.\n            ", default=5.0), 'radius_actor': TunableEnumEntry(description='\n            The Actor within whose radius the tuned floor feature must be in\n            for consideration.\n            ', tunable_type=ParticipantType, default=ParticipantType.Actor)}

    def floor_feature_exists_in_object_radius(self, radius_actors):
        floor_features = build_buy.list_floor_features(self.floor_feature)
        if floor_features is None:
            return False
        for actor in radius_actors:
            for (ff_position, _) in floor_features:
                delta = ff_position - actor.position
                if delta.magnitude_squared() < self.radius:
                    return True
        return False

    def get_expected_args(self):
        return {'radius_actors': self.radius_actor}

    @cached_test
    def __call__(self, radius_actors=()):
        radius_objects = []
        SimInfo = sims.sim_info.SimInfo
        for radius_actor in radius_actors:
            if isinstance(radius_actor, SimInfo):
                radius_actor_object = radius_actor.get_sim_instance(allow_hidden_flags=ALL_HIDDEN_REASONS)
                if radius_actor_object is None:
                    logger.error('{} has a None value and cannot be used to determine a nearby floor feature test.', radius_actor)
                else:
                    radius_objects.append(radius_actor_object)
                    radius_objects.append(radius_actor)
            else:
                radius_objects.append(radius_actor)
        result = self.floor_feature_exists_in_object_radius(radius_objects)
        if not result:
            return TestResult(False, 'No Found Floor Features', tooltip=self.tooltip)
        return TestResult.TRUE
Exemplo n.º 10
0
class Ensemble(metaclass=HashedTunedInstanceMetaclass, manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE)):
    ENSEMBLE_PRIORITIES = TunableList(description='\n        A list of ensembles by priority.  Those with higher guids will be\n        considered more important than those with lower guids.\n        \n        IMPORTANT: All ensemble types must be referenced in this list.\n        ', tunable=TunableReference(description='\n            A single ensemble.\n            ', manager=services.get_instance_manager(sims4.resources.Types.ENSEMBLE), pack_safe=True))

    @staticmethod
    def get_ensemble_priority(ensemble_type):
        index = 0
        for ensemble in Ensemble.ENSEMBLE_PRIORITIES:
            if ensemble is ensemble_type:
                return index
            index += 1
        logger.error('Ensemble of type {} not found in Ensemble Priorities.  Please add the ensemble to ensemble.ensemble.', ensemble_type)

    INSTANCE_TUNABLES = {'max_ensemble_radius': TunableDistanceSquared(description="\n            The maximum distance away from the center of mass that Sims will\n            receive an autonomy bonus for.\n            \n            If Sims are beyond this distance from the ensemble's center of mass,\n            then they will autonomously consider to run any interaction from\n            ensemble_autonomous_interactions.\n            \n            Any such interaction will have an additional constraint that is a\n            circle whose radius is this value.\n            ", default=1.0), 'ensemble_autonomy_bonus_multiplier': TunableRange(description='\n            The autonomy multiplier that will be applied for objects within the\n            autonomy center of mass.\n            ', tunable_type=float, default=2.0, minimum=1.0), 'ensemble_autonomous_interactions': TunableSet(description="\n            This is a set of self interactions that are generated for Sims part \n            of this ensemble.\n            \n            The interactions don't target anything and have an additional\n            constraint equivalent to the circle defined by the ensemble's center\n            of mass and radius.\n            ", tunable=TunableReference(manager=services.get_instance_manager(sims4.resources.Types.INTERACTION), pack_safe=True)), 'visible': Tunable(description='\n            If this ensemble is visible and displays to the UI.\n            ', tunable_type=bool, default=True), 'rally': Tunable(description='\n            If this is True then this ensemble will offer rallying behavior.\n            ', tunable_type=bool, default=True), 'center_of_mass_multiplier': TunableMultiplier.TunableFactory(description="\n            Define multipliers that control the weight that a Sim has when\n            determining the ensemble's center of mass.\n            "), 'max_limit': OptionalTunable(description='\n            If enabled this ensemble will have a maximum number of Sims that\n            can be a part of it.\n            ', tunable=TunableRange(description='\n                The maximum number of Sims that can be in this ensemble.\n                ', tunable_type=int, default=8, minimum=2)), 'prohibited_species': TunableSet(description='\n            A set of species that cannot be added to this type of ensemble.\n            ', tunable=TunableEnumEntry(description='\n                A species that cannot be added to this type of ensemble.\n                ', tunable_type=Species, default=Species.HUMAN, invalid_enums=(Species.INVALID,)))}

    def __init__(self):
        self._guid = None
        self._sims = WeakSet()

    def __iter__(self):
        yield from self._sims

    def __len__(self):
        return len(self._sims)

    @property
    def guid(self):
        return self._guid

    @classmethod
    def can_add_sim_to_ensemble(cls, sim):
        if sim.species in cls.prohibited_species:
            return False
        return True

    def add_sim_to_ensemble(self, sim):
        if sim in self._sims:
            return
        self._sims.add(sim)
        if self.ensemble_autonomous_interactions:
            sim_info_utils.apply_super_affordance_commodity_flags(sim, self, self.ensemble_autonomous_interactions)
        if self.visible:
            op = UpdateEnsemble(self._guid, sim.id, True)
            Distributor.instance().add_op_with_no_owner(op)

    def remove_sim_from_ensemble(self, sim):
        self._sims.remove(sim)
        if self.ensemble_autonomous_interactions:
            sim_info_utils.remove_super_affordance_commodity_flags(sim, self)
        if self.visible:
            op = UpdateEnsemble(self._guid, sim.id, False)
            Distributor.instance().add_op_with_no_owner(op)

    def is_sim_in_ensemble(self, sim):
        return sim in self._sims

    def start_ensemble(self):
        self._guid = id_generator.generate_object_id()
        if self.visible:
            op = StartEnsemble(self._guid)
            Distributor.instance().add_op_with_no_owner(op)

    def end_ensemble(self):
        if self.ensemble_autonomous_interactions:
            for sim in self._sims:
                sim_info_utils.remove_super_affordance_commodity_flags(sim, self)
        self._sims.clear()
        if self.visible:
            op = EndEnsemble(self._guid)
            Distributor.instance().add_op_with_no_owner(op)

    @cached
    def _get_sim_weight(self, sim):
        return self.center_of_mass_multiplier.get_multiplier(SingleSimResolver(sim.sim_info))

    @cached
    def calculate_level_and_center_of_mass(self):
        sims_per_level = defaultdict(list)
        for sim in self._sims:
            sims_per_level[sim.level].append(sim)
        best_level = max(sims_per_level, key=lambda level: (len(sims_per_level[level]), -level))
        best_sims = sims_per_level[best_level]
        center_of_mass = sum((sim.position*self._get_sim_weight(sim) for sim in best_sims), sims4.math.Vector3.ZERO())/sum(self._get_sim_weight(sim) for sim in best_sims)
        return (best_level, center_of_mass)

    def is_within_ensemble_radius(self, obj):
        (level, center_of_mass) = self.calculate_level_and_center_of_mass()
        if obj.level != level:
            return False
        else:
            distance = (obj.position - center_of_mass).magnitude_squared()
            if distance > self.max_ensemble_radius:
                return False
        return True

    @cached
    def get_ensemble_multiplier(self, target):
        if self.is_within_ensemble_radius(target):
            return self.ensemble_autonomy_bonus_multiplier
        return 1

    def get_center_of_mass_constraint(self):
        if not self:
            logger.warn('No Sims in ensemble when trying to construct constraint.')
            return ANYWHERE
        (level, position) = self.calculate_level_and_center_of_mass()
        routing_surface = routing.SurfaceIdentifier(services.current_zone_id(), level, routing.SurfaceType.SURFACETYPE_WORLD)
        return Circle(position, sqrt(self.max_ensemble_radius), routing_surface)

    def get_ensemble_autonomous_interactions_gen(self, context, **interaction_parameters):
        if self.is_within_ensemble_radius(context.sim):
            return
        for ensemble_affordance in self.ensemble_autonomous_interactions:
            affordance = EnsembleConstraintProxyInteraction.generate(ensemble_affordance, self)
            yield from affordance.potential_interactions(context.sim, context, **interaction_parameters)
class _WaypointGeneratorPacing(_WaypointGeneratorBase):
    FACTORY_TUNABLES = {
        'constraint_parameters':
        TunableTuple(
            description=
            '\n            Parameters used to generate the constraints that will be used\n            to generate waypoints.\n            ',
            object_constraint_radius=TunableRange(
                description=
                '\n                The radius, in meters, of the generated constraint around the \n                target object where the waypoints will be generated.\n                ',
                tunable_type=float,
                default=2,
                minimum=0),
            waypoint_constraint_radius=TunableRange(
                description=
                '\n                The radius, in meters, for each generated waypoint inside the \n                object constraint radius for the Sim to route to.\n                ',
                tunable_type=float,
                default=1,
                minimum=0.1),
            min_water_depth=OptionalTunable(
                description=
                '\n                If enabled, generate waypoints at locations that are at least\n                this deep.\n                ',
                tunable=TunableRange(
                    description=
                    '\n                    The minimum water depth allowed for each waypoint.\n                    ',
                    tunable_type=float,
                    default=0,
                    minimum=0)),
            max_water_depth=OptionalTunable(
                description=
                '\n                If enabled, generate waypoints at locations that are at most\n                this deep.\n                ',
                tunable=TunableRange(
                    description=
                    '\n                    The maximum water depth allowed for each waypoint.\n                    ',
                    tunable_type=float,
                    default=1000.0,
                    minimum=0,
                    maximum=1000.0))),
        'waypoint_min_distance':
        TunableDistanceSquared(
            description=
            '\n            Minimum distance between the waypoints. We want to space them out\n            as much as possible. If after several tries we still cannot get\n            a waypoint that satisfies this min distance, we pick the furthest. \n            ',
            default=1),
        'outside_only':
        Tunable(
            description=
            '\n            If enabled, we will attempt to place a jig outside to find a\n            starting location, then validate all goals in the constraint radius\n            to ensure they are outside. Otherwise, we will route fail.\n            \n            Note: This will generate points on the world routing surface.\n            ',
            tunable_type=bool,
            default=False)
    }
    MAX_WAYPOINT_RANDOM_TRIES = 5

    @classmethod
    def _verify_tuning_callback(cls):
        if cls.constraint_parameters.object_constraint_radius <= cls.waypoint_min_distance:
            logger.error(
                'Constraint radius is smaller than waypoint minimum. Waypoints will not obey minimum distance for: {}',
                cls)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self._target is None:
            self._start_constraint = Nowhere(
                'No target for _WaypointGeneratorPacing')
            self._los_reference_point = None
            return
        self._los_reference_point = self._target.position
        if self._target.is_terrain:
            self._los_reference_point = None
        water_constraint = self.get_water_constraint(
            self.constraint_parameters.min_water_depth,
            self.constraint_parameters.max_water_depth)
        if self.outside_only:
            self._routing_surface = routing.SurfaceIdentifier(
                services.current_zone_id(), 0,
                routing.SurfaceType.SURFACETYPE_WORLD)
            starting_location = Location(position=self._target.position,
                                         routing_surface=self._routing_surface)
            search_flags = FGLSearchFlagsDefaultForSim | FGLSearchFlag.STAY_OUTSIDE
            fgl_context = placement.FindGoodLocationContext(
                starting_location,
                routing_context=self._context.sim.routing_context,
                additional_avoid_sim_radius=routing.get_default_agent_radius(),
                max_results=1,
                max_steps=10,
                search_flags=search_flags,
                min_water_depth=water_constraint.get_min_water_depth(),
                max_water_depth=water_constraint.get_max_water_depth())
            (trans, _) = placement.find_good_location(fgl_context)
            if trans is not None:
                geometry = sims4.geometry.RestrictedPolygon(
                    sims4.geometry.CompoundPolygon(
                        sims4.geometry.Polygon((trans, ))), ())
                self._start_constraint = SmallAreaConstraint(
                    geometry=geometry,
                    debug_name='WaypointPacingStartingConstraint',
                    routing_surface=self._routing_surface,
                    min_water_depth=water_constraint.get_min_water_depth(),
                    max_water_depth=water_constraint.get_max_water_depth())
            else:
                self._start_constraint = Nowhere(
                    'WaypointGeneratorPacing requires outside, but we failed to find a good location.'
                )
        else:
            self._start_constraint = Circle(
                self._target.position,
                self.constraint_parameters.object_constraint_radius,
                routing_surface=self._routing_surface,
                los_reference_point=self._los_reference_point,
                min_water_depth=water_constraint.get_min_water_depth(),
                max_water_depth=water_constraint.get_max_water_depth())

    def get_start_constraint(self):
        return self._start_constraint

    def get_waypoint_constraints_gen(self, routing_agent, waypoint_count):
        water_constraint = self.get_water_constraint(
            self.constraint_parameters.min_water_depth,
            self.constraint_parameters.max_water_depth)
        debugvis_constraints = []
        target_position = self._target.position
        object_radius_constraint = Circle(
            target_position,
            self.constraint_parameters.object_constraint_radius,
            routing_surface=self._start_constraint.routing_surface,
            los_reference_point=self._los_reference_point,
            min_water_depth=water_constraint.get_min_water_depth(),
            max_water_depth=water_constraint.get_max_water_depth())
        debugvis_constraints.append(
            (target_position,
             self.constraint_parameters.object_constraint_radius))
        area_goals = []
        handles = object_radius_constraint.get_connectivity_handles(
            routing_agent)
        for handle in handles:
            area_goals.extend(
                handle.get_goals(relative_object=self._target,
                                 always_reject_invalid_goals=True))
        area_goals = [
            goal for goal in area_goals if is_location_outside(
                goal.position, goal.location.routing_surface.secondary_id)
        ]
        if not (self.outside_only and area_goals):
            yield Circle(
                target_position,
                self.constraint_parameters.object_constraint_radius,
                routing_surface=self._start_constraint.routing_surface,
                los_reference_point=self._los_reference_point,
                min_water_depth=water_constraint.get_min_water_depth(),
                max_water_depth=water_constraint.get_max_water_depth())
            return
        min_dist_sq = self.waypoint_min_distance
        current_point = None
        for _ in range(waypoint_count):
            if current_point is None:
                current_point = random.choice(area_goals)
                debugvis_constraints.append(
                    (current_point.position,
                     self.constraint_parameters.waypoint_constraint_radius))
                yield Circle(
                    current_point.position,
                    self.constraint_parameters.waypoint_constraint_radius,
                    routing_surface=self._start_constraint.routing_surface,
                    los_reference_point=self._los_reference_point,
                    min_water_depth=water_constraint.get_min_water_depth(),
                    max_water_depth=water_constraint.get_max_water_depth())
            farthest_point = None
            farthest_dist = 0
            for _ in range(self.MAX_WAYPOINT_RANDOM_TRIES):
                try_point = random.choice(area_goals)
                try_dist = (try_point.position -
                            current_point.position).magnitude_squared()
                farthest_point = try_point
                break
                if not (try_dist > min_dist_sq and
                        (farthest_point is None or not farthest_point
                         is not None)) and try_dist > farthest_dist:
                    farthest_point = try_point
                    farthest_dist = try_dist
            current_point = farthest_point
            debugvis_constraints.append(
                (current_point.position,
                 self.constraint_parameters.waypoint_constraint_radius))
            yield Circle(
                current_point.position,
                self.constraint_parameters.waypoint_constraint_radius,
                routing_surface=self._start_constraint.routing_surface,
                los_reference_point=self._los_reference_point,
                min_water_depth=water_constraint.get_min_water_depth(),
                max_water_depth=water_constraint.get_max_water_depth())
Exemplo n.º 12
0
class _WaypointGeneratorMultipleObjectByTag(_WaypointGeneratorBase):
    FACTORY_TUNABLES = {
        'object_max_distance':
        TunableDistanceSquared(
            description=
            '\n            The maximum distance to check for an object as the next target\n            of our waypoint interaction.\n            ',
            default=5),
        'constrain_radius':
        TunableRange(
            description=
            '\n            The radius of the circle that will be generated around the objects\n            where the waypoints will be generated.\n            ',
            tunable_type=float,
            default=5,
            minimum=0),
        'object_tags':
        TunableTags(
            description=
            '\n            Find all of the objects based on these tags.\n            ',
            filter_prefixes=('func', )),
        'object_search_strategy':
        TunableVariant(
            description=
            '\n            Search strategies to find and soft the possible objects where the\n            waypoints will be generated.\n            ',
            default_waypoints=_WaypointObjectDefaultStrategy.TunableFactory(),
            sorted_by_distance=_WaypointObjectSortedDistanceStrategy.
            TunableFactory(),
            default='default_waypoints'),
        'placement_restriction':
        OptionalTunable(
            description=
            '\n            If enabled the objects where the waypoints will be generated will\n            be restricted to either the inside of outside.\n            ',
            tunable=Tunable(
                description=
                '\n                If checked objects will be restricted to the inside the \n                house, otherwise only objects outside will be considered.\n                ',
                tunable_type=bool,
                default=True),
            enabled_name='inside_only',
            disabled_name='no_restrictions'),
        'randomize_order':
        Tunable(
            description=
            '\n            If checked, the waypoints will be shuffled into a random order each\n            time the route is generated. If not they will be the same (but\n            still non-deterministic) order each time, for a given run.\n            ',
            tunable_type=bool,
            default=False)
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sim = self._context.sim
        self._valid_objects = []
        for obj in services.object_manager().get_objects_matching_tags(
                self.object_tags, match_any=True):
            if self.placement_restriction is not None and self.placement_restriction == obj.is_outside:
                continue
            distance_from_sim = obj.position - self._sim.position
            if distance_from_sim.magnitude_squared(
            ) <= self.object_max_distance:
                if obj.is_connected(self._sim):
                    self._valid_objects.append(obj)
        self._valid_objects = self.object_search_strategy.get_waypoint_objects(
            self._valid_objects)
        if not self._valid_objects:
            self._start_constraint = Circle(
                self._sim.position,
                self.constrain_radius,
                routing_surface=self._sim.routing_surface,
                los_reference_point=None)
            return
        if self.randomize_order:
            random.shuffle(self._valid_objects)
        starting_object = self._valid_objects.pop(0)
        self._start_constraint = Circle(
            starting_object.position,
            self.constrain_radius,
            routing_surface=self._sim.routing_surface,
            los_reference_point=None)
        self._start_constraint = self._start_constraint.intersect(
            self.get_water_constraint())

    def get_start_constraint(self):
        return self._start_constraint

    def get_waypoint_constraints_gen(self, routing_agent, waypoint_count):
        water_constraint = self.get_water_constraint()
        for _ in range(waypoint_count - 1):
            if not self._valid_objects:
                return
            obj = self._valid_objects.pop(0)
            next_constraint_circle = Circle(
                obj.position,
                self.constrain_radius,
                los_reference_point=None,
                routing_surface=obj.routing_surface)
            next_constraint_circle = next_constraint_circle.intersect(
                water_constraint)
            yield next_constraint_circle
class GoToNearestFloorFeatureInteraction(SuperInteraction):
    INSTANCE_TUNABLES = {'terrain_feature': TunableEnumEntry(description='\n            The type of floor feature the sim should route to\n            ', tunable_type=FloorFeatureType, default=FloorFeatureType.BURNT), 'routing_circle_constraint': TunableCircle(1.5, description='\n            Circle constraint around the floor feature\n            '), 'routing_facing_constraint': TunableFacing(description='\n                Controls how a Sim must face the terrain feature\n                '), 'indoors_only': Tunable(description='\n            Indoors Only\n            ', tunable_type=bool, default=False), 'radius_filter': OptionalTunable(description='\n            If enabled, floor features will be filtered out unless they are \n            within a radius of the radius_actor.\n            \n            The purpose of the radius filter is to constrain the set of\n            found floor features only to those within a radius of a tuned\n            participant. For example, this interaction could allow Sims only\n            to route to leaves within the radius of a targeted leaf pile.\n            ', tunable=TunableTuple(radius=TunableDistanceSquared(description="\n                    The radius, with the Saved Actor 1's position, that defines the area\n                    within which the floor feature is valid.\n                    ", default=5.0), radius_actor=TunableEnumEntry(description='\n                    The Actor within whose radius the tuned floor feature must be in\n                    for consideration.\n                    ', tunable_type=ParticipantType, default=ParticipantType.Actor)))}

    @flexmethod
    def _constraint_gen(cls, inst, sim, target, **kwargs):
        inst_or_cls = inst if inst is not None else cls
        yield from super(SuperInteraction, inst_or_cls)._constraint_gen(sim, target, **kwargs)
        floor_feature_constraint = inst_or_cls._create_floor_feature_constraint_set(sim)
        yield floor_feature_constraint

    @flexmethod
    def _create_floor_feature_constraint_set(cls, inst, sim):
        inst_or_cls = inst if inst is not None else cls
        floor_feature_contraints = []
        floor_features_and_surfaces = []
        zone_id = services.current_zone_id()
        floor_features = build_buy.list_floor_features(inst_or_cls.terrain_feature)
        if floor_features is None:
            return Nowhere('No found floor features.')
        radius_object = None
        if inst_or_cls.radius_filter is not None:
            radius_object = inst_or_cls.get_participant(inst_or_cls.radius_filter.radius_actor)
        if inst_or_cls.radius_filter is not None and radius_object is None:
            return Nowhere('Radius filter is enabled but the radius actor has a None value.')
        for floor_feature in floor_features:
            if inst_or_cls.indoors_only and build_buy.is_location_natural_ground(floor_feature[0], floor_feature[1]):
                continue
            routing_surface = routing.SurfaceIdentifier(zone_id, floor_feature[1], routing.SurfaceType.SURFACETYPE_WORLD)
            floor_feature_location = floor_feature[0]
            if inst_or_cls.radius_filter is not None:
                if (radius_object.position - floor_feature_location).magnitude_squared() <= inst_or_cls.radius_filter.radius:
                    floor_features_and_surfaces.append((floor_feature_location, routing_surface))
                    floor_features_and_surfaces.append((floor_feature_location, routing_surface))
            else:
                floor_features_and_surfaces.append((floor_feature_location, routing_surface))
        if floor_features_and_surfaces:
            for floor_feature_and_surface in floor_features_and_surfaces:
                circle_constraint = inst_or_cls.routing_circle_constraint.create_constraint(sim, None, target_position=floor_feature_and_surface[0], routing_surface=floor_feature_and_surface[1])
                facing_constraint = inst_or_cls.routing_facing_constraint.create_constraint(sim, None, target_position=floor_feature_and_surface[0], routing_surface=floor_feature_and_surface[1])
                constraint = circle_constraint.intersect(facing_constraint)
                floor_feature_contraints.append(constraint)
            return create_constraint_set(floor_feature_contraints)
        return Nowhere('With radius filter enabled, no found floor features are within range.')
Exemplo n.º 14
0
class ObjectRouteFromTargetObject(_ObjectRoutingBehaviorBase):
    FACTORY_TUNABLES = {
        'radius':
        TunableDistanceSquared(
            description=
            '\n            Only objects within this distance are considered.\n            ',
            default=1),
        'target_type':
        TunableVariant(
            description=
            '\n            Type of target object to choose (object, sim).\n            ',
            object=_RouteTargetTypeObject.TunableFactory(),
            sim=_RouteTargetTypeSim.TunableFactory(),
            default='object'),
        'target_selection_test':
        TunableTestSet(
            description=
            '\n            A test used for selecting a target.\n            ',
            tuning_group=GroupNames.TESTS),
        'no_target_loot':
        TunableList(
            description=
            "\n            Loot to apply if no target is selected (eg, change state back to 'wander').\n            ",
            tunable=LootActions.TunableReference()),
        'constraints':
        TunableList(
            description=
            '\n            Constraints relative to the relative participant.\n            ',
            tunable=TunableGeometricConstraintVariant(
                description=
                '\n                Use the point on the found object defined by these geometric constraints.\n                ',
                disabled_constraints=('spawn_points',
                                      'spawn_points_with_backup'))),
        'target_action_rules':
        TunableList(
            description=
            '\n            A set of conditions and a list of one or more TargetObjectActions to run\n             on the target object after routing to it. These are applied in sequence.\n            ',
            tunable=_TargetActionRules.TunableFactory())
    }

    @classmethod
    def _verify_tuning_callback(cls):
        if not cls.target_selection_test and not cls.tags:
            logger.error(
                'No selection test tuned for ObjectRouteFromTargetObject {}.',
                cls,
                owner='miking')

    def _find_target(self):
        all_objects = self.target_type.get_objects()
        objects = []
        for o in all_objects:
            dist_sq = (o.position - self._obj.position).magnitude_squared()
            if dist_sq > self.radius:
                continue
            if o == self:
                continue
            if not o.is_sim and not o.may_reserve(self._obj):
                continue
            if self.target_selection_test:
                resolver = DoubleObjectResolver(self._obj, o)
                if not self.target_selection_test.run_tests(resolver):
                    continue
            else:
                objects.append([o, dist_sq])
        if not objects:
            return
        source_handles = [
            routing.connectivity.Handle(self._obj.position,
                                        self._obj.routing_surface)
        ]
        dest_handles = []
        for o in objects:
            obj = o[0]
            parent = obj.parent
            route_to_obj = parent if parent is not None else obj
            constraint = Anywhere()
            for tuned_constraint in self.constraints:
                constraint = constraint.intersect(
                    tuned_constraint.create_constraint(self._obj,
                                                       route_to_obj))
            dests = constraint.get_connectivity_handles(self._obj, target=obj)
            if dests:
                dest_handles.extend(dests)
        if not dest_handles:
            return
        routing_context = self._obj.get_routing_context()
        connections = routing.estimate_path_batch(
            source_handles, dest_handles, routing_context=routing_context)
        if not connections:
            return
        connections.sort(key=lambda connection: connection[2])
        best_connection = connections[0]
        best_dest_handle = best_connection[1]
        best_obj = best_dest_handle.target
        return best_obj

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._target = self._find_target()

    def get_routes_gen(self):
        if self._target is None:
            self.on_no_target()
            return False
            yield
        routing_slot_constraint = Anywhere()
        for tuned_constraint in self.constraints:
            routing_slot_constraint = routing_slot_constraint.intersect(
                tuned_constraint.create_constraint(self._obj, self._target))
        goals = list(
            itertools.chain.from_iterable(
                h.get_goals()
                for h in routing_slot_constraint.get_connectivity_handles(
                    self._obj)))
        routing_context = self._obj.get_routing_context()
        route = routing.Route(self._obj.routing_location,
                              goals,
                              routing_context=routing_context)
        yield route

    def do_target_action_rules_gen(self, timeline):
        if not self.target_action_rules or self._target is None:
            return
        resolver = DoubleObjectResolver(self._obj, self._target)
        for target_action_rule in self.target_action_rules:
            if random.random.random() >= target_action_rule.chance:
                continue
            if not target_action_rule.test.run_tests(resolver):
                continue
            if target_action_rule.actions is not None:
                for action in target_action_rule.actions:
                    result = yield from action.run_action_gen(
                        timeline, self._obj, self._target)
                    if not result:
                        return
            if target_action_rule.abort_if_applied:
                return

    def on_no_target(self):
        resolver = SingleObjectResolver(self._obj)
        for loot_action in self.no_target_loot:
            loot_action.apply_to_resolver(resolver)