extends Node signal spawn_player(pos) signal spawn_npc(xform) enum states {STATE_PREINIT, STATE_INIT, STATE_INIT_ASTAR, STATE_EXECUTE, STATE_SLEEP} var state = states.STATE_PREINIT var astar = null var sleep_delay = 0.0 var npcs func start(): print("ai starting") state = states.STATE_INIT func _ready(): astar = AStar.new() global.astar = astar func calc_visible_lists(): for g in get_tree().get_nodes_in_group("npc"): if g.get_meta("smart_object"): continue var bb = g.get_node("blackboard") var enemy_list = [] var visibility_list = [] var visible_to_list = [] var distance_all = {} var dist2char = {} for gn in get_tree().get_nodes_in_group("characters"): if g == gn: continue if gn.get_meta("smart_object"): continue if gn.get_meta("grabbed"): continue distance_all[gn] = g.global_transform.origin.distance_to(gn.global_transform.origin) if !dist2char.has(distance_all[gn]): dist2char[distance_all[gn]] = [gn] else: dist2char[distance_all[gn]].push_back(gn) if rpg.is_enemy(g, gn): enemy_list.push_back(gn) if global.visibility_check(g, gn): visibility_list.push_back(gn) if global.visibility_check(gn, g): visible_to_list.push_back(gn) var all_distances = dist2char.keys().duplicate() all_distances.sort() var closest var closest_male var closest_female var closest_visible var closest_visible_male var closest_visible_female var closest_enemy var closest_visible_enemy for k in all_distances: if !closest: closest = dist2char[k][0] if !closest_visible && dist2char[k][0] in visibility_list: closest_visible = dist2char[k][0] if !closest_male && dist2char[k][0].is_in_group("male"): closest_male = dist2char[k][0] if !closest_female && dist2char[k][0].is_in_group("female"): closest_female = dist2char[k][0] if !closest_visible_male && dist2char[k][0].is_in_group("male") && dist2char[k][0] in visibility_list: closest_visible_male = dist2char[k][0] if !closest_visible_female && dist2char[k][0].is_in_group("female") && dist2char[k][0] in visibility_list: closest_visible_female = dist2char[k][0] if !closest_enemy: if rpg.is_enemy(g, dist2char[k][0]): closest_enemy = dist2char[k][0] if !closest_visible_enemy: if rpg.is_enemy(g, dist2char[k][0]) && dist2char[k][0] in visibility_list: closest_visible_enemy = dist2char[k][0] bb.set("visible", visibility_list) bb.set("visible_to", visible_to_list) bb.set("closest", closest) bb.set("closest_female", closest_female) bb.set("closest_male", closest_male) bb.set("closest_visible", closest_visible) bb.set("closest_visible_female", closest_visible_female) bb.set("closest_visible_male", closest_visible_male) bb.set("closest_enemy", closest_enemy) bb.set("closest_visible_enemy", closest_visible_enemy) if closest_enemy: bb.set("last_enemy", closest_enemy) bb.set("closest_enemy_distance", distance_all[closest_enemy]) if closest_female: bb.set("closest_female_distance", distance_all[closest_female]) if closest_male: bb.set("closest_male_distance", distance_all[closest_male]) if closest_visible_female: bb.set("closest_visible_female_distance", distance_all[closest_visible_female]) if closest_visible_male: bb.set("closest_visible_male_distance", distance_all[closest_visible_male]) func global_distance(a: Spatial, b:Spatial): var posa = a.global_transform.origin var posb = b.global_transform.origin return posa.distance_to(posb) func spawn_characters(): for e in get_tree().get_nodes_in_group("spawn"): if e.name == "player-spawn": print("player: ", e.global_transform.origin) emit_signal("spawn_player", e) if global.player: for e in get_tree().get_nodes_in_group("spawn"): if global_distance(global.player, e) < 5.0: print("spawn ", e.name) if e.name.begins_with("npc-spawn"): print("npc") emit_signal("spawn_npc", e) var path_nodes = [] var endings = [] var connections = {} func _physics_process(delta): match state: states.STATE_INIT: print("starting AI") path_nodes = get_tree().get_nodes_in_group("path") endings = [] connections = {} state = states.STATE_INIT_ASTAR states.STATE_INIT_ASTAR: if path_nodes.size() > 0: var e = path_nodes.pop_front() print(e.name) var new_points = Array(e.curve.get_baked_points()) var new_ids = [] for r in new_points: var pt = r + e.global_transform.origin var id = astar.get_closest_point(r) if id >= 0: var spos = astar.get_point_position(id) if spos.distance_to(pt) > 0.15: id = astar.get_available_point_id() astar.add_point(id, pt) else: id = astar.get_available_point_id() astar.add_point(id, pt) if !id in new_ids: new_ids.push_back(id) endings.push_back(new_ids[0]) endings.push_back(new_ids[new_ids.size() - 1]) for s in range(new_ids.size() - 1): if !astar.are_points_connected(new_ids[s], new_ids[s + 1]): if new_ids[s] != new_ids[s + 1]: astar.connect_points(new_ids[s], new_ids[s + 1]) connections[new_ids[s]] = new_ids[s + 1] connections[new_ids[s + 1]] = new_ids[s] else: print("path nodes passed") var space_state = get_parent().get_world().direct_space_state for id1 in endings: for id2 in endings: if id1 == id2: continue if astar.are_points_connected(id1, id2): continue var p0 = astar.get_point_position(id1) var p1 = astar.get_point_position(id2) var front_result = space_state.intersect_ray(p0 + Vector3(0, 0.5, 0), p1 + Vector3(0, 0.5, 0), [], 0x1) if !front_result.has("collider"): astar.connect_points(id1, id2) print("astar complete") spawn_characters() print("characters spawned") $blackboard.set("metaai", self) state = states.STATE_EXECUTE states.STATE_EXECUTE: if randf() > 0.7: calc_visible_lists() for g in get_tree().get_nodes_in_group("npc"): var bb = g.get_node("blackboard") bb.set("delta", delta) bb.set("metaai", self) if g.get_meta("smart_object"): continue if g.get_meta("grabbed"): continue var space_state = g.get_world().direct_space_state var up = Vector3(0, 1, 0) var npc_pos = g.global_transform.origin var front = -g.global_transform.basis[2] var right = g.global_transform.basis[0] var vl = g.velocity vl.y = 0 var v = vl.length() * 0.2 var npc_raycasts = [ { "name": "front_result", "from": npc_pos + up, "to": npc_pos + up + front * (0.1 + v), "mask": 0xffff }, { "name": "front_right_result", "from": npc_pos + up + right * 0.1, "to": + npc_pos + up + front * (0.2 + v) + right * (0.1 + v * 0.5), "mask": 0xffff }, { "name": "front_left_result", "from": npc_pos + up - right * 0.1, "to": npc_pos + up + front * (0.2 + v) - right * (0.1 + v * 0.5), "mask": 0xffff }, { "name": "low_obstacle_result", "from": npc_pos + up * 0.3, "to": npc_pos + up * 0.3 + front * (0.1 + v), "radius": 0.2, "mask": 0xffff }, ] for r in npc_raycasts: if r.has("radius"): var shape : = SphereShape.new() shape.radius = r.radius var query = PhysicsShapeQueryParameters.new() query.set_shape(query) query.collision_mask = r.mask query.exclude = [g] query.transform.origin = r.from var motion = space_state.cast_motion(shape, r.to - r.from) if motion.size() == 0: bb.set(r.name, {}) else: query.transform.origin = r.from + (r.to - r.from) * motion[1] var results = space_state.intersect_shape(query, 1) if results.size() > 0: var d = results[0].duplicate() d.motion = motion bb.set(r.name, results[0]) else: bb.set(r.name, {}) else: var result = space_state.intersect_ray(r.from, r.to, [g], r.mask) if result.has("collider"): print(r.name, " ", result, " ", v, " ", r.from.distance_to(r.to), " ", r.from.distance_to(result.position)) bb.set(r.name, result) if g.has_meta("target_loc"): var loc = g.get_meta("target_loc") var target_result = space_state.intersect_ray(npc_pos + up, loc + up, [g], 0xffff) bb.set("target_result", target_result) else: bb.set("target_result", {}) $behavior_tree.tick(g, bb) sleep_delay = 0.3 + randf() * 0.4 state = states.STATE_SLEEP states.STATE_SLEEP: sleep_delay -= delta if sleep_delay <= 0.0: state = states.STATE_EXECUTE