Files
academy2-game/scripts/modules/npc_bandit.gd
2022-01-04 06:45:16 +03:00

630 lines
16 KiB
GDScript3

extends AIScriptModule
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
var root
var skel
var hair_skel
var name
var garments_female_lingerie = ["female-panties1", "female-bra1"]
var garments_head_female = []
var garments_male_lingerie = ["male-panties1"]
var garments_head_male = []
var garments_female_main = ["female-shirt_skirt1"]
var garments_male_main = ["male-pants1", "male-shoes1"]
var basedir = "res://scenes/clothes/"
var material_female = preload("res://scenes/clothes/nun-clothes.material")
var material_male = preload("res://scenes/clothes/clothes-male.material")
var state = 0
var rnd: RandomNumberGenerator
const attack_distance = 2.0
const guard_distance = 2.0
const engage_distance = 10.0
const flee_distance = 5.0
var init_blackboard = {
"stamina": 100.0,
"health": 100.0,
"melee_weapon_equipped": false,
"attack_cooldown": 0.0,
"guard_cooldown": 0.0,
"flee_cooldown": 0.0,
"guard": false,
"melee_damage": false,
"dot": 0.0
}
func look_at(ch):
var current = root.global_transform
var at = ch.global_transform.origin
at.y = current.origin.y
root.global_transform = current.looking_at(at, Vector3.UP)
func combat_event(ev, data):
var cam = get_character().get_viewport().get_camera()
var player
if cam:
player = cam.get_meta("player")
assert(player)
match ev:
"about_to_attack":
var who = data[0]
if who != root:
var where = who.global_transform.origin
var curpos = root.global_transform.origin
var dst = where.distance_squared_to(curpos)
if dst < guard_distance * guard_distance:
if who == player:
blackboard_set("guard", true)
"damage":
var who = data[0]
var weapon = data[1]
if who != root:
var where = who.global_transform.origin
var curpos = root.global_transform.origin
var dst = where.distance_squared_to(curpos)
if dst < attack_distance * attack_distance:
if who == player:
blackboard_set("melee_damage", true)
var initialized = false
func init(tick):
assert(!initialized)
name = "bandit_ai"
root = get_character()
for e in init_blackboard.keys():
blackboard_set(e, init_blackboard[e])
assert(root.has_meta("skeleton"))
rnd = RandomNumberGenerator.new()
rnd.randomize()
root.add_to_group("bandits")
root.add_to_group("bandit")
combat.connect("event", self, "combat_event")
var character_data = root.get_meta("character_data")
if character_data.sex == "female":
var g = garments_female_lingerie
var h = []
g += garments_female_main
h += garments_head_female
characters.call_deferred("setup_garments", root, g, h, material_female)
else:
var g = garments_male_lingerie
var h = []
g += garments_male_main
h += garments_head_male
characters.call_deferred("setup_garments", root, g, h, material_male)
state = 0
for e in conf_behaviors.keys():
if !run_behaviors.has(e):
run_behaviors[e] = conf_behaviors[e].new()
initialized = true
var cooldown = 0.0
class base_bhv:
var active = false
var finished = false
func run(delta: float, bb: Dictionary):
pass
func stop():
pass
func activate():
pass
class walkto extends base_bhv:
signal arrived(where)
var state = 0
var where: Vector3
var speed: = 0.0
var gap: = 0.0
var root
var timeout: = 0.0
var pdist = INF
var cost: = 0.0
func look_at(root):
var current = root.global_transform
var at = where
at.y = current.origin.y
root.global_transform = current.looking_at(at, Vector3.UP)
root.set_meta("orientation", Transform(root.global_transform.basis, Vector3()))
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
var gpos = root.global_transform.origin
var dst = gpos.distance_squared_to(where)
match state:
0:
look_at(root)
characters.animation_node_travel(root, "locomotion")
characters.set_walk_speed(root, speed, 0)
state = 1
1:
bb.stamina -= cost * delta
timeout -= delta
state = 2
2:
if dst < gap * gap:
emit_signal("arrived", gpos)
state = 10
elif timeout < 0.0:
root.global_transform.origin = where
emit_signal("arrived", gpos)
state = 20
elif dst > pdist && dst > gap * gap:
root.global_transform.origin = where
emit_signal("arrived", gpos)
state = 30
printerr("missed the target: ", sqrt(dst), gap)
elif dst < 3.0 * gap * gap && timeout > 0.0:
characters.set_walk_speed(root, 0.5 * speed, 0)
10:
finished = true
20:
finished = true
30:
finished = true
200:
timeout -= delta
if timeout < 0.0:
root.global_transform.origin = where
characters.set_walk_speed(root, 0, 0)
finished = true
state = 300
300:
printerr("should not be here: ", sqrt(dst), gap)
breakpoint
# print("walkto state: ", state)
pdist = dst
func start(root: Node, where: Vector3, speed: float, gap: float, cost: float, timeout: float):
assert(root)
assert(!active)
state = 0
self.root = root
self.where = where
self.gap = gap
self.timeout = timeout
self.speed = speed
self.cost = cost
active = true
func stop():
assert(active)
assert(root)
characters.set_walk_speed(root, 0, 0)
active = false
finished = false
state = 0
# print("walkto stopped")
class attack extends base_bhv:
var root
var anim
var event
var state = 0
func start(root: Node, anim: String, event: String):
self.root = root
self.anim = anim
self.event = event
active = true
func look_at(root, bb):
var current = root.global_transform
var at = bb.enemy_pos
at.y = current.origin.y
root.global_transform = current.looking_at(at, Vector3.UP)
root.set_meta("orientation", Transform(root.global_transform.basis, Vector3()))
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
# print("attack state: ", state, " ", active, " ", finished)
if finished:
return
# print("attack running")
bb.attack_cooldown -= delta
if bb.attack_cooldown > 0.0:
return
match state:
0:
combat.emit_signal("event", event, [root])
look_at(root, bb)
state = 1
1:
bb.stamina -= 0.5 * delta
bb.stamina -= 3500 * delta
characters.animation_node_travel(root, anim)
# print("attack: ", anim)
state = 2
2:
bb.stamina -= 5 * delta
bb.attack_cooldown = 1.8
finished = true
# print("attack ", anim, " ", event, " done")
func stop():
assert(active)
assert(root)
active = false
finished = false
state = 0
class take_weapon extends base_bhv:
var weapon
var root
func start(root: Node, weapon):
self.root = root
self.weapon = weapon
active = true
func activate():
pass
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
if finished:
return
if bb.melee_weapon_equipped:
return
var skel = root.get_meta("skeleton")
var wslot = skel.get_node("wrist_r/weapon_right")
wslot.set_meta("owner", root)
inventory.equip(wslot, weapon)
bb.melee_weapon_equipped = true
finished = true
func stop():
# can't re-run this one
assert(active)
assert(root)
finished = false
active = false
finished = false
class guard extends base_bhv:
var root
var anim1
var anim2
var state = 0
func start(root: Node, anim1, anim2: String):
self.root = root
self.anim1 = anim1
self.anim2 = anim2
active = true
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
# print("guard state: ", state, " ", finished)
if finished:
return
match state:
0:
# guard
var anim
if bb.dot < 0:
anim = anim1
else:
anim = anim2
characters.animation_node_travel(root, anim)
# print("guard playing animation ", anim)
state = 1
1:
state = 2
2:
bb.guard_cooldown = 1.5
finished = true
class get_damage extends base_bhv:
var root
var anim1
var anim2
var state = 0
var cost = 0.0
func start(root: Node, anim1, anim2: String, cost: float):
self.root = root
self.anim1 = anim1
self.anim2 = anim2
self.cost = cost
active = true
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
if finished:
return
match state:
0:
# guard
var anim
if bb.dot < 0:
anim = anim1
else:
anim = anim2
characters.animation_node_travel(root, anim)
if bb.stamina > 50.0:
bb.health -= cost
else:
bb.health -= 5.0 * cost
bb.stamina = 5.0
# print("guard playing animation ", anim)
state = 1
1:
state = 2
2:
bb.guard_cooldown = 1.5
finished = true
func stop():
assert(active)
assert(root)
active = false
finished = false
state = 0
# print("guard stopped")
class rest extends base_bhv:
var root
func start(root: Node):
self.root = root
active = true
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
if finished:
return
if bb.stamina < 100.0:
# print("rest: stamina: ", bb.stamina)
bb.stamina += 15500.0 * delta
else:
finished = true
func stop():
assert(active)
assert(root)
active = false
finished = false
class unconcious extends base_bhv:
var root
var anim1
var anim2
var state = 0
func start(root: Node, anim1: String, anim2: String):
self.root = root
self.anim1 = anim1
self.anim2 = anim2
active = true
state = 0
func run(delta: float, bb: Dictionary):
assert(active)
assert(root)
assert(anim1)
assert(anim2)
match state:
0:
characters.animation_node_travel(root, anim1)
state = 1
# if finished:
# return
# if bb.stamina < 100.0:
# print("rest: stamina: ", bb.stamina)
# bb.stamina += 15500.0 * delta
# else:
# finished = true
func stop():
assert(active)
assert(root)
active = false
finished = false
#var walk_to: walkto
#var flee_to: walkto
#var melee_attack: attack
#var take_melee_weapon: take_weapon
#var guard_melee: guard
#var do_rest: rest
var conf_behaviors: = {
"take_melee_weapon": take_weapon,
"approach": walkto,
"flee": walkto,
"melee_attack": attack,
"guard": guard,
"rest": rest,
"get_melee_damage": get_damage,
"unconcious": unconcious
}
var pdst = INF
func calc_utility(utility: String):
match utility:
"take_melee_weapon":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("melee_weapon_equipped"):
return 0.0
else:
if blackboard_get("enemy_distance") < engage_distance * engage_distance:
return 110.0
"approach":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("enemy_distance") < 1.6 * 1.6:
return 0.0
if blackboard_get("stamina") > 50.0 && blackboard_get("enemy_distance") < engage_distance * engage_distance:
return 10.0 + (blackboard_get("enemy_distance") - 10.0) / 6.0
"melee_attack":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("melee_weapon_equipped"):
if blackboard_get("attack_cooldown") <= 0.0:
var d = attack_distance * attack_distance
if blackboard_get("stamina") > 20.0 && blackboard_get("enemy_distance") <= d:
return 50.0
"flee":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("flee_cooldown") > 0.0:
return 0.0
if blackboard_get("stamina") <= 50.0 && blackboard_get("stamina") > 10.0 && blackboard_get("enemy_distance") < flee_distance * flee_distance:
return 100.0 + clamp((100.0 - blackboard_get("stamina")), 0, 90.0) * 2.0
if blackboard_get("stamina") <= 10.0 && blackboard_get("enemy_distance") <= flee_distance * flee_distance * 0.5:
return 250.0
if blackboard_get("stamina") <= 50.0 && blackboard_get("enemy_distance") < engage_distance * engage_distance:
if blackboard_get("randf") < 0.3:
return 100.0
if blackboard_get("enemy_distance") < 1.4 * 1.4:
return 150.0
if blackboard_get("health") < 50 && blackboard_get("health") > 15:
return 160
"guard":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("guard_cooldown") > 0.0:
return 0.0
elif blackboard_get("guard"):
blackboard_set("guard", false)
return 300.0
# elif blackboard_get("enemy_distance") < guard_distance * guard_distance * 0.3 && blackboard_get("stamina") > 10.0:
# return 80
# elif blackboard_get("enemy_distance") < attack_distance * attack_distance * 0.3:
# return 10.0
"unconcious":
if blackboard_get("health") < 10:
return 2000
"rest":
if blackboard_get("stamina") >= 100.0:
return 0.0
if blackboard_get("stamina") <= 10.0 && blackboard_get("enemy_distance") >= flee_distance * flee_distance:
return 100.0 + blackboard_get("enemy_distance") / 10.0
"get_melee_damage":
if blackboard_get("health") <= 0.0:
return 0.0
if blackboard_get("melee_damage"):
blackboard_set("melee_damage", false)
print("DAMAGE2 ", blackboard_get("health"))
return 1000
_:
assert(false)
return 0.0
var last_behavior
func select_behavior():
var best_behavior
var best_utility
best_behavior = "rest"
best_utility = 0.0
for e in conf_behaviors.keys():
var utility = calc_utility(e)
if e == last_behavior:
utility *= 2.0
if best_utility < utility:
best_utility = utility
best_behavior = e
last_behavior = best_behavior
return best_behavior
var last_bhv
var last_running = []
var current_running = []
var run_behaviors = {}
func update_physics(tick, delta):
assert(initialized)
assert(root)
var cam = root.get_viewport().get_camera()
if !cam:
return
if !cam.has_meta("player"):
return
var space: PhysicsDirectSpaceState = root.get_world().get_direct_space_state()
var o = root.global_transform.origin
var p = cam.get_meta("player").global_transform
pdst = o.distance_squared_to(p.origin)
blackboard_set("enemy_distance", pdst)
blackboard_set("randf", rnd.randf())
blackboard_set("enemy_pos", p.origin)
blackboard_set("space", space)
# var adest = o + (p.origin - o).normalized() * min(2.0, sqrt(pdst))
var adest = p.origin
var away = (o - p.origin).normalized()
var attack_xform = Transform(root.global_transform.basis, o + away * 1.5)
var fdest = o + away * 4.0
var enemy_dir: Vector3 = Transform(p.basis, Vector3()).xform(Vector3(0, 0, -1))
var root_dir: Vector3 = Transform(root.global_transform.basis, Vector3()).xform(Vector3(0, 0, -1))
var dot = root_dir.dot(enemy_dir)
blackboard_set("dot", dot)
var prev_state = state
match state:
0:
last_running = current_running.duplicate()
current_running = []
var bhv = select_behavior()
if last_bhv != bhv:
# print("behavior: ", bhv)
last_bhv = bhv
if !run_behaviors[bhv].active:
match bhv:
"take_melee_weapon":
run_behaviors[bhv].start(root, "s_dagger")
"approach":
var dest = adest
if pdst > 25.0:
run_behaviors[bhv].start(root, dest, 0.5, 3.0, 4.0, 6.0)
else:
run_behaviors[bhv].start(root, dest, 0.2, 1.5, 2.0, 6.0)
"melee_attack":
run_behaviors[bhv].start(root, "attack-melee1", "about_to_attack")
"flee":
# walk away from enemy (player)
var dest = fdest
run_behaviors[bhv].start(root, dest, 0.3, 1.5, 20.0, 6.0)
"rest":
run_behaviors[bhv].start(root)
"guard":
run_behaviors[bhv].start(root, "guard-melee1", "guard-front-melee1")
"get_melee_damage":
run_behaviors[bhv].start(root, "guard-melee1", "guard-front-melee1", 10.0)
"unconcious":
run_behaviors[bhv].start(root, "fall-front", "fall-back")
_:
assert(false)
# print("adding behavior: ", bhv)
current_running.push_back(run_behaviors[bhv])
for e in current_running:
if !e in last_running:
if !e.active:
e.activate()
e.run(delta, blackboard_get_dict())
#FIXME: mess :(
if e == run_behaviors["flee"]:
blackboard_set("flee_cooldown", 2.0 + rnd.randf() * 3.0)
if e.finished && e.active:
# print("in current run but finished")
e.stop()
for e in last_running:
if !e in current_running:
if e.active:
# print("not in current run but was in last")
e.stop()
1:
state = 2
2:
state = 3
3:
state = 0
if blackboard_get("stamina") < 100.0:
blackboard_set("stamina", blackboard_get("stamina") + delta)
if blackboard_get("attack_cooldown") > 0.0:
blackboard_set("attack_cooldown", blackboard_get("attack_cooldown") - delta)
if blackboard_get("guard_cooldown") > 0.0:
blackboard_set("guard_cooldown", blackboard_get("guard_cooldown") - delta)
if blackboard_get("flee_cooldown") > 0.0:
blackboard_set("flee_cooldown", blackboard_get("flee_cooldown") - delta)
blackboard_set("stamina", clamp(blackboard_get("stamina"), 0, 100.0))
blackboard_set("health", clamp(blackboard_get("health"), 0, 100.0))
return ERR_BUSY
func update(tick, delta):
return ERR_BUSY
##
# if prev_state != state:
# print("new state: ", state)
# print("running ", current_running.size(), " behaviors")