267 lines
8.8 KiB
GDScript
267 lines
8.8 KiB
GDScript
extends Node
|
|
signal spawn_player(pos)
|
|
signal spawn_npc(xform)
|
|
|
|
enum states {STATE_PREINIT, STATE_INIT, 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()
|
|
|
|
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)
|
|
|
|
func _physics_process(delta):
|
|
match state:
|
|
states.STATE_INIT:
|
|
print("starting AI")
|
|
var endings = []
|
|
var connections = {}
|
|
for e in get_tree().get_nodes_in_group("path"):
|
|
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]
|
|
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)
|
|
# for pk in connections.keys():
|
|
# var id1 = pk
|
|
# var id2 = connections[pk]
|
|
# if astar.are_points_connected(id1, id2):
|
|
# 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.8, 0), p1 + Vector3(0, 0.8, 0), [], 0x1)
|
|
# if front_result.has("collider"):
|
|
# var collider = front_result.collider
|
|
# var ok = false
|
|
# if collider.is_in_group("furniture"):
|
|
# ok = true
|
|
# elif collider.is_in_group("door"):
|
|
# ok = true
|
|
# astar.disconnect_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
|
|
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
|