diff --git a/scripts/modules/character_hurtboxes.gd b/scripts/modules/character_hurtboxes.gd index c5a7097..6a4b324 100644 --- a/scripts/modules/character_hurtboxes.gd +++ b/scripts/modules/character_hurtboxes.gd @@ -104,8 +104,9 @@ func update_physics(tick, delta): # decal.global_transform.basis = Basis(normal).scaled(Vector3(scale, 1.0, scale)).rotated(normal, rot) return ERR_BUSY func stop(tick): - printerr("Why stopped?") - assert(false) + pass +# printerr("Why stopped?") +# assert(false) func update(tick, delta): return ERR_BUSY diff --git a/scripts/modules/npc_bandit.gd b/scripts/modules/npc_bandit.gd index c0dda65..2a7a881 100644 --- a/scripts/modules/npc_bandit.gd +++ b/scripts/modules/npc_bandit.gd @@ -1,34 +1,21 @@ extends AIScriptModule +const garments_female_lingerie = ["female-panties1", "female-bra1"] +const garments_head_female = [] +const garments_male_lingerie = ["male-panties1"] +const garments_head_male = [] +const garments_female_main = ["female-shirt_skirt1"] +const garments_male_main = ["male-pants1", "male-shoes1"] +const basedir = "res://scenes/clothes/" +const material_female = preload("res://scenes/clothes/nun-clothes.material") +const material_male = preload("res://scenes/clothes/clothes-male.material") -# Declare member variables here. Examples: -# var a = 2 -# var b = "text" - - -# Called when the node enters the scene tree for the first time. -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 = { +const init_blackboard = { "stamina": 100.0, "health": 100.0, "melee_weapon_equipped": false, @@ -40,6 +27,13 @@ var init_blackboard = { "dot": 0.0 } +class AITreeBuilder: + var parent + func seq(a: Array): + var e = AITreeBuilder.new() + e.parent = self + return e + func look_at(tick, ch): var root = get_character(tick) var current = root.global_transform @@ -77,21 +71,376 @@ func combat_event(ev, data, blackboard): blackboard["melee_damage"] = true # blackboard_set(tick, "melee_damage", true) +class take_weapon extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + get_memory(tick).skel = get_memory(tick).root.get_meta("skeleton") + func update_physics(tick, delta): + var root = get_memory(tick).root + assert(root) + if blackboard_get(tick, "melee_weapon_equipped"): + return FAILED + var wslot = get_memory(tick).skel.get_node("wrist_r/weapon_right") + wslot.set_meta("owner", root) + inventory.equip(wslot, get_memory(tick).weapon) + blackboard_set(tick, "melee_weapon_equipped", true) + assert(blackboard_get(tick, "melee_weapon_equipped")) + return OK + func stop(tick): + # can't re-run this one + var root = get_memory(tick).root + assert(root) + +class guard extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + get_memory(tick).state = 0 + func update_physics(tick, delta): + var root = get_memory(tick).root + assert(root) + if blackboard_get(tick, "guard_cooldown") > 0: + return FAILED + if blackboard_get(tick, "guard"): + blackboard_set(tick, "guard", false) + match get_memory(tick).state: + 0: + # guard + var anim + if blackboard_get(tick, "dot") < 0: + anim = get_memory(tick).anim1 + else: + anim = get_memory(tick).anim2 + characters.animation_node_travel(root, anim) + get_memory(tick).state = 1 + return ERR_BUSY + 1: + get_memory(tick).state = 2 + return ERR_BUSY + 2: + blackboard_set(tick, "guard_cooldown", 1.5) + return OK + return OK + func stop(tick): + get_memory(tick).state = 0 +class rest extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + func update_physics(tick, delta): + var root = get_memory(tick).root + assert(root) + if blackboard_get(tick, "stamina") < 100.0: + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") + 15500.0 * delta) + return ERR_BUSY + else: + return OK + func stop(tock): + pass +class idle extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + func update_physics(tick, delta): + var root = get_memory(tick).root + assert(root) + if blackboard_get(tick, "stamina") < 100.0: + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") + 0.5 * delta) + return ERR_BUSY + else: + return OK + func stop(tock): + pass + + +class unconcious extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + get_memory(tick).state = 0 + + func update_physics(tick, delta): + var root = get_memory(tick).root + var anim1 = get_memory(tick).anim1 + var anim2 = get_memory(tick).anim2 + assert(root) + assert(anim1) + assert(anim2) + match get_memory(tick).state: + 0: + characters.animation_node_travel(root, anim1) + get_memory(tick).state = 1 + return ERR_BUSY + return ERR_BUSY + func stop(tick): + pass + +class get_damage extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + get_memory(tick).state = 0 + + func update_physics(tick, delta): + var root = get_memory(tick).root + var anim1 = get_memory(tick).anim1 + var anim2 = get_memory(tick).anim2 + var cost = get_memory(tick).cost + assert(root) + if blackboard_get(tick, "guard_cooldown") > 0: + return FAILED + if blackboard_get(tick, "melee_damage"): + blackboard_set(tick, "melee_damage", false) + match get_memory(tick).state: + 0: + # guard + var anim + if blackboard_get(tick, "dot") < 0: + anim = get_memory(tick).anim1 + else: + anim = get_memory(tick).anim2 + characters.animation_node_travel(root, anim) + if blackboard_get(tick, "stamina") > 50.0: + blackboard_set(tick, "health", blackboard_get(tick, "health") - cost) + else: + blackboard_set(tick, "health", blackboard_get(tick, "health") - 5.0 * cost) + blackboard_set(tick, "stamina", 5.0) + get_memory(tick).state = 1 + return ERR_BUSY + 1: + get_memory(tick).state = 2 + return ERR_BUSY + 2: + blackboard_set(tick, "guard_cooldown", 1.5) + return OK + func stop(tick): + get_memory(tick).state = 0 + +class walkto extends AIScriptModule: + func init(tick): + get_memory(tick).state = 0 + get_memory(tick).pdist = INF + get_memory(tick).root = get_character(tick) + get_memory(tick).timeout = get_memory(tick).config_timeout + func look_at(root, where): + 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 update_physics(tick, delta): + var root = get_memory(tick).root + assert(root) + var flee = get_memory(tick).flee + var where: Vector3 + var adest = blackboard_get(tick, "enemy_pos") + var away = (root.global_transform.origin - adest).normalized() + var fdest = root.global_transform.origin + away * 4.0 + + if !flee: + where = adest + else: + where = fdest + var gpos = root.global_transform.origin + var dst = gpos.distance_squared_to(where) + var finished = false + var pdist = get_memory(tick).pdist + var speed = get_memory(tick).speed + var cost = get_memory(tick).cost + var gap = get_memory(tick).gap +# get_memory(tick).timeout -= delta + match get_memory(tick).state: + 0: + look_at(root, where) + characters.animation_node_travel(root, "locomotion") + characters.set_walk_speed(root, speed, 0) + get_memory(tick).state = 1 + 1: + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") - cost * delta) + get_memory(tick).state = 2 + 2: + if dst < gap * gap: + get_memory(tick).state = 10 + elif get_memory(tick).timeout < 0.0: + root.global_transform.origin = where + get_memory(tick).state = 20 +# elif dst > pdist && dst > gap * gap: +# root.global_transform.origin = where +# get_memory(tick).state = 30 +# printerr("missed the target: ", sqrt(dst), gap) +# elif dst < 3.0 * gap * gap && get_memory(tick).timeout > 0.0: +# characters.set_walk_speed(root, speed, 0) +# get_memory(tick).state = 3 + else: + get_memory(tick).state = 0 + 10: + finished = true + 20: + finished = true + 30: + finished = true +# 200: +# get_memory(tick).timeout -= delta +# if get_memory(tick).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 + get_memory(tick).pdist = dst + if !finished: + return ERR_BUSY + else: + return OK + func stop(tick): + var root = get_memory(tick).root + characters.set_walk_speed(root, 0, 0) + get_memory(tick).state = 0 + get_memory(tick).timeout = get_memory(tick).config_timeout +class attack extends AIScriptModule: + func init(tick): + get_memory(tick).root = get_character(tick) + get_memory(tick).state = 0 + func look_at(tick, root): + var current = root.global_transform + var at = blackboard_get(tick, "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 update_physics(tick, delta): + var root = get_memory(tick).root + var event = get_memory(tick).event + var anim = get_memory(tick).anim + var finished = false + assert(root) + if blackboard_get(tick, "attack_cooldown") > 0.0: + blackboard_set(tick, "attack_cooldown", blackboard_get(tick, "attack_cooldown") - delta) + return FAILED + match get_memory(tick).state: + 0: + combat.emit_signal("event", get_memory(tick).event, [root]) + look_at(tick, root) + get_memory(tick).state = 1 + 1: + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") - 0.5 * delta) + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") - 3500 * delta) + characters.animation_node_travel(root, anim) + get_memory(tick).state = 2 + 2: + blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") - 5 * delta) + blackboard_set(tick, "attack_cooldown", 1.8) + finished = true + if finished: + return OK + else: + return ERR_BUSY + func stop(tick): + get_memory(tick).state = 0 + func init(tick): assert(tick) assert(!get_memory(tick).has("initialized")) - name = "bandit_ai" + get_memory(tick).name = "bandit_ai" + var considerations_data = { + "take_melee_weapon": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": no_melee_weapon_equipped_consideration.new(), "score": 1.0}, + {"c": threat_consideration.new(), "score": 1.0} + ], + "score": 180.0, + "conf": {"weapon": "s_dagger"}, + }, + "approach_far": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": enemy_very_far_consideration.new(), "score": 1.0}, + {"c": has_melee_weapon_equipped_consideration.new(), "score": 1.0}, + {"c": half_or_more_stamina_consideration.new(), "score": 1.0}, + {"c": threat_consideration.new(), "score": 1.0} + ], + "score": 120.0, + "conf": {"gap": 0.6, "config_timeout": 3.0, "speed": 4.0, "cost": 6.0, "flee": false}, + }, + "approach_near": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": enemy_far_consideration.new(), "score": 1.0}, + {"c": has_melee_weapon_equipped_consideration.new(), "score": 1.0}, + {"c": half_or_more_stamina_consideration.new(), "score": 1.0}, + {"c": threat_consideration.new(), "score": 1.0} + ], + "score": 150.0, + "conf": {"gap": 0.6, "config_timeout": 1.5, "speed": 2.0, "cost": 6.0, "flee": false}, + }, + "melee_attack": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": has_melee_weapon_equipped_consideration.new(), "score": 1.0}, + {"c": melee_attack_ready_consideration.new(), "score": 1.0}, + {"c": twenty_or_more_stamina_consideration.new(), "score": 1.0}, + {"c": melee_attack_distance_consideration.new(), "score": 1.0} + ], + "score": 50.0, + "conf": {"anim": "attack-melee1", "event": "about_to_attack"}, + }, + "flee": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": ready_to_flee_consideration.new(), "score": 1.0}, + {"c": has_energy_consideration.new(), "score": 1.0}, + {"c": dangerous_distance_consideration.new(), "score": 1.0}, + ], + "score": 300.0, + "conf": {"gap": 0.6, "config_timeout": 1.5, "speed": 20.0, "cost": 6.0, "flee": true}, + }, + "guard": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": ready_to_guard_consideration.new(), "score": 1.0}, + {"c": guarding_consideration.new(), "score": 1.0}, + ], + "score": 900.0, + "conf": {"anim1": "guard-melee1", "anim2": "guard-front-melee1"}, + }, + "unconcious": { + "considerations": [ + {"c": critical_health_consideration.new(), "score": 1.0} + ], + "score": 2000.0, + "conf": {"anim1": "fall-front", "anim2": "fall-back"}, + }, + "rest": { + "considerations": [ + {"c": critical_stamina_consideration.new(), "score": 1.0}, + {"c": flee_or_more_distance_consideration.new(), "score": 1.0} + ], + "score": 150.0, + "conf": {}, + }, + "get_melee_damage": { + "considerations": [ + {"c": health_consideration.new(), "score": 1.0}, + {"c": melee_damage_consideration.new(), "score": 1.0}, + ], + "score": 3000.0, + "conf": {"anim1": "guard-melee1", "anim2": "guard-front-melee1", "cost": 10.0}, + }, + "idle": { + "considerations": [ + ], + "score": 1.0, + "conf": {}, + } + } + var root = get_character(tick) for e in init_blackboard.keys(): - blackboard_set(tick, e, init_blackboard[e]) + if !blackboard_is_set(tick, e): + blackboard_set(tick, e, init_blackboard[e]) blackboard_set(tick, "self", root) assert(root.has_meta("skeleton")) - rnd = RandomNumberGenerator.new() - rnd.randomize() + get_memory(tick).rnd = RandomNumberGenerator.new() + get_memory(tick).rnd.randomize() root.add_to_group("bandits") root.add_to_group("bandit") - combat.connect("event", self, "combat_event", [blackboard_get_dict(tick)]) - var character_data = root.get_meta("character_data") if character_data.sex == "female": var g = garments_female_lingerie @@ -105,343 +454,45 @@ func init(tick): g += garments_male_main h += garments_head_male characters.call_deferred("setup_garments", root, g, h, material_male) - state = 0 + + var ai_runner: AIUtilityRunner = AIUtilityRunner.new() + get_memory(tick).runner = ai_runner + _register_module(tick, get_memory(tick).runner) + combat.connect("event", self, "combat_event", [blackboard_get_dict(tick)]) + if !get_memory(tick).has("run_behaviors"): + get_memory(tick).run_behaviors = {} for e in conf_behaviors.keys(): - if !run_behaviors.has(e): - run_behaviors[e] = conf_behaviors[e].new() + if !get_memory(tick).run_behaviors.has(e): + get_memory(tick).run_behaviors[e] = conf_behaviors[e].new() + _register_module(tick, get_memory(tick).run_behaviors[e]) + var conf = considerations_data[e].conf + get_memory(tick).run_behaviors[e].setup(tick, conf) + ai_runner.add_choice(tick, e, considerations_data[e].score, get_memory(tick).run_behaviors[e]) + for c in considerations_data[e].considerations: + ai_runner.add_consideration(tick, e, c.c, c.score) get_memory(tick).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, + "approach_far": walkto, + "approach_near": walkto, "flee": walkto, "melee_attack": attack, "guard": guard, "rest": rest, + "idle": idle, "get_melee_damage": get_damage, "unconcious": unconcious } -var pdst = INF class health_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "health") <= 0.0: return 0.0 var ret = blackboard_get(tick, "health") / 100.0 - return clamp(ret, 0.0, 1.0) + ret = clamp(ret, 0.0, 1.0) + return ret * ret class no_melee_weapon_equipped_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if !blackboard_get(tick, "melee_weapon_equipped"): @@ -453,11 +504,22 @@ class threat_consideration extends AIUtilityScriptedConsideration: if blackboard_get(tick, "enemy_distance") < engage_distance * engage_distance: return 1.0 return 0.0 +class safe_consideration extends AIUtilityScriptedConsideration: + const engage_distance = 20.0 + func evalute(tick, delta): + if blackboard_get(tick, "enemy_distance") >= engage_distance * engage_distance: + return 1.0 + return 0.0 class enemy_far_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "enemy_distance") < 1.6 * 1.6: return 0.0 return 1.0 +class enemy_very_far_consideration extends AIUtilityScriptedConsideration: + func evalute(tick, delta): + if blackboard_get(tick, "enemy_distance") > 5.0 * 5.0: + return 1.0 + return 0.0 class half_or_more_stamina_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "stamina") > 50.0: @@ -492,9 +554,12 @@ class need_healing_consideration extends AIUtilityScriptedConsideration: return clamp(ret, 0.0, 1.0) class need_rest_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): - if blackboard_get(tick, "stamina") >= 100.0: + if blackboard_get(tick, "stamina") >= 80.0: return 0.0 - return clamp(1.0 - blackboard_get(tick, "stamina") / 100.0, 0.0, 1.0) + var e = blackboard_get(tick, "stamina") / 100.0 + e = clamp(e, 0.0, 1.0) + var d = (1.0 / (e + 1.0) - 0.5) * 0.5 + return clamp(d, 0.0, 1.0) class critical_health_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "health") < 10.0: @@ -542,263 +607,37 @@ class melee_damage_consideration extends AIUtilityScriptedConsideration: if blackboard_get(tick, "melee_damage"): return 1.0 return 0.0 -var considerations_data = { - "take_melee_weapon": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": no_melee_weapon_equipped_consideration.new(), "score": 1.0}, - {"c": threat_consideration.new(), "score": 1.0} - ], - }, - "approach": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": enemy_far_consideration.new(), "score": 1.0}, - {"c": half_or_more_stamina_consideration.new(), "score": 1.0}, - {"c": threat_consideration.new(), "score": 1.0} - ], - }, - "melee_attack": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": has_melee_weapon_equipped_consideration.new(), "score": 1.0}, - {"c": melee_attack_ready_consideration.new(), "score": 1.0}, - {"c": twenty_or_more_stamina_consideration.new(), "score": 1.0}, - {"c": melee_attack_distance_consideration.new(), "score": 1.0} - ], - }, - "flee": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": ready_to_flee_consideration.new(), "score": 1.0}, - {"c": has_energy_consideration.new(), "score": 1.0}, - {"c": dangerous_distance_consideration.new(), "score": 1.0}, - ], - }, - "guard": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": ready_to_guard_consideration.new(), "score": 1.0}, - {"c": guarding_consideration.new(), "score": 1.0}, - ], - }, - "unconcious": { - "considerations": [ - {"c": critical_health_consideration.new(), "score": 1.0} - ], - }, - "rest": { - "considerations": [ - {"c": critical_stamina_consideration.new(), "score": 1.0}, - {"c": flee_or_more_distance_consideration.new(), "score": 1.0} - ], - }, - "get_melee_damage": { - "considerations": [ - {"c": health_consideration.new(), "score": 1.0}, - {"c": melee_damage_consideration.new(), "score": 1.0}, - ] - } -} -func calc_utility(tick: AITick, utility: String, delta: float): - var v = 0.0 - if considerations_data.has(utility): - v = 1.0 - for b in considerations_data[utility].considerations: - assert(b.c) - v *= b.c._evalute(tick, delta) * b.score - if v <= 0.0: - break - match utility: - "take_melee_weapon": - v *= 110.0 -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "melee_weapon_equipped"): -# return 0.0 -# else: -# if blackboard_get(tick, "enemy_distance") < engage_distance * engage_distance: -# return 110.0 - "approach": - v *= 100.0 -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "enemy_distance") < 1.6 * 1.6: -# return 0.0 -# if blackboard_get(tick, "stamina") > 50.0 && blackboard_get(tick, "enemy_distance") < engage_distance * engage_distance: -# return 10.0 + (blackboard_get(tick, "enemy_distance") - 10.0) / 6.0 - "melee_attack": - v *= 50.0 -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "melee_weapon_equipped"): -# if blackboard_get(tick, "attack_cooldown") <= 0.0: -# var d = attack_distance * attack_distance -# if blackboard_get(tick, "stamina") > 20.0 && blackboard_get(tick, "enemy_distance") <= d: -# return 50.0 - "flee": - v *= 300.0 -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "flee_cooldown") > 0.0: -# return 0.0 -# if blackboard_get(tick, "stamina") <= 50.0 && blackboard_get(tick, "stamina") > 10.0 && blackboard_get(tick, "enemy_distance") < flee_distance * flee_distance: -# return 100.0 + clamp((100.0 - blackboard_get(tick, "stamina")), 0, 90.0) * 2.0 -# if blackboard_get(tick, "stamina") <= 10.0 && blackboard_get(tick, "enemy_distance") <= flee_distance * flee_distance * 0.5: -# return 250.0 -# if blackboard_get(tick, "stamina") <= 50.0 && blackboard_get(tick, "enemy_distance") < engage_distance * engage_distance: -# if blackboard_get(tick, "randf") < 0.3: -# return 100.0 -# if blackboard_get(tick, "enemy_distance") < 1.4 * 1.4: -# return 150.0 -# if blackboard_get(tick, "health") < 50 && blackboard_get(tick, "health") > 15: -# return 160 - "guard": - if blackboard_get(tick, "guard"): - blackboard_set(tick, "guard", false) - v *= 900.0 -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "guard_cooldown") > 0.0: -# return 0.0 -# elif blackboard_get(tick, "guard"): -# blackboard_set(tick, "guard", false) -# return 400.0 -# elif blackboard_get(tick, "enemy_distance") < guard_distance * guard_distance * 0.3 && blackboard_get(tick, "stamina") > 10.0: -# return 80 -# elif blackboard_get(tick, "enemy_distance") < attack_distance * attack_distance * 0.3: -# return 10.0 - "unconcious": - v *= 2000.0 -# if blackboard_get(tick, "health") < 10: -# return 2000 - "rest": - v *= 150.0 -# if blackboard_get(tick, "stamina") >= 100.0: -# return 0.0 -# if blackboard_get(tick, "stamina") <= 10.0 && blackboard_get(tick, "enemy_distance") >= flee_distance * flee_distance: -# return 100.0 + blackboard_get(tick, "enemy_distance") / 10.0 - "get_melee_damage": - v *= 3000.0 - if blackboard_get(tick, "melee_damage"): - blackboard_set(tick, "melee_damage", false) -# if blackboard_get(tick, "health") <= 0.0: -# return 0.0 -# if blackboard_get(tick, "melee_damage"): -# blackboard_set(tick, "melee_damage", false) -# print("DAMAGE2 ", blackboard_get(tick, "health")) -# return 1000 - _: - assert(false) - return v -var last_behavior -func select_behavior(tick, delta): - var best_behavior - var best_utility - best_behavior = "rest" - best_utility = 0.0 - for e in conf_behaviors.keys(): - var utility = calc_utility(tick, e, delta) - 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(get_memory(tick).initialized) var root = get_character(tick) assert(root) var cam = root.get_viewport().get_camera() if !cam: - return + return FAILED if !cam.has_meta("player"): - return + return FAILED blackboard_set(tick, "self", get_character(tick)) 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) + var pdst = o.distance_squared_to(p.origin) blackboard_set(tick, "enemy_distance", pdst) - blackboard_set(tick, "randf", rnd.randf()) + blackboard_set(tick, "randf", get_memory(tick).rnd.randf()) blackboard_set(tick, "enemy_pos", p.origin) blackboard_set(tick, "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 + blackboard_set(tick, "away_pos", fdest) 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(tick, "dot", dot) - var prev_state = state - match state: - 0: - last_running = current_running.duplicate() - current_running = [] - var bhv = select_behavior(tick, delta) - 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(tick)) - #FIXME: mess :( - if e == run_behaviors["flee"]: - blackboard_set(tick, "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 + var result = get_memory(tick).runner._update_physics(tick, delta) + if blackboard_get(tick, "stamina") < 100.0: blackboard_set(tick, "stamina", blackboard_get(tick, "stamina") + delta) if blackboard_get(tick, "attack_cooldown") > 0.0: @@ -813,7 +652,8 @@ func update_physics(tick, delta): func update(tick, delta): return ERR_BUSY -## -# if prev_state != state: -# print("new state: ", state) -# print("running ", current_running.size(), " behaviors") +func stop(tick): + for e in conf_behaviors.keys(): + if get_memory(tick).run_behaviors.has(e): + _unregister_module(tick, get_memory(tick).run_behaviors[e]) + _unregister_module(tick, get_memory(tick).runner) diff --git a/scripts/modules/player_controls.gd b/scripts/modules/player_controls.gd index 1d701cb..20f65f0 100644 --- a/scripts/modules/player_controls.gd +++ b/scripts/modules/player_controls.gd @@ -20,6 +20,10 @@ var equipped = false var vjump = Vector3() func update(tick, delta): + return ERR_BUSY + + +func update_physics(tick, delta): if !controls.is_gui: if !attack: if Input.is_action_just_pressed("attack"): @@ -28,10 +32,6 @@ func update(tick, delta): if Input.is_action_just_pressed("jump"): jump = true vjump = Vector3.UP * 10.0 - return ERR_BUSY - - -func update_physics(tick, delta): var orientation: Transform var root = get_character(tick) if !root.has_meta("cam"):