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") const attack_distance = 2.0 const guard_distance = 2.0 const engage_distance = 10.0 const flee_distance = 5.0 const 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 } 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 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, blackboard): var root = blackboard.self var cam = root.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["guard"] = true # blackboard_set(tick, "melee_damage", 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["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")) 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(): if !blackboard_is_set(tick, e): blackboard_set(tick, e, init_blackboard[e]) blackboard_set(tick, "self", root) assert(root.has_meta("skeleton")) get_memory(tick).rnd = RandomNumberGenerator.new() get_memory(tick).rnd.randomize() root.add_to_group("bandits") root.add_to_group("bandit") 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) 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 !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 var conf_behaviors: = { "take_melee_weapon": take_weapon, "approach_far": walkto, "approach_near": walkto, "flee": walkto, "melee_attack": attack, "guard": guard, "rest": rest, "idle": idle, "get_melee_damage": get_damage, "unconcious": unconcious } 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 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"): return 1.0 return 0.0 class threat_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 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: return 1.0 return 0.0 class has_melee_weapon_equipped_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "melee_weapon_equipped"): return 1.0 return 0.0 class melee_attack_ready_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "attack_cooldown") <= 0.0: return 1.0 return 0.0 class twenty_or_more_stamina_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "stamina") > 20.0: return 1.0 return 0.0 class melee_attack_distance_consideration extends AIUtilityScriptedConsideration: const attack_distance = 2.0 func evalute(tick, delta): if blackboard_get(tick, "enemy_distance") < attack_distance * attack_distance: return 1.0 return 0.0 class need_healing_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "health") >= 100.0: return 0.0 var ret = 1.0 - blackboard_get(tick, "health") / 100.0 return clamp(ret, 0.0, 1.0) class need_rest_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "stamina") >= 80.0: return 0.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: return 1.0 return 0.0 class critical_stamina_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "stamina") < 10.0: return 1.0 return 0.0 class flee_or_more_distance_consideration extends AIUtilityScriptedConsideration: const flee_distance = 5.0 func evalute(tick, delta): if blackboard_get(tick, "enemy_distance") >= flee_distance * flee_distance: return 1.0 return 0.0 class dangerous_distance_consideration extends AIUtilityScriptedConsideration: const flee_distance = 5.0 func evalute(tick, delta): if blackboard_get(tick, "enemy_distance") < flee_distance * flee_distance: return 1.0 return 0.0 class ready_to_flee_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "flee_cooldown") < 0.0: return 1.0 return 0.0 class has_energy_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "stamina") >= 10.0: return 1.0 return 0.0 class ready_to_guard_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "guard_cooldown") < 0.0: return 1.0 return 0.0 class guarding_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "guard"): return 1.0 return 0.0 class melee_damage_consideration extends AIUtilityScriptedConsideration: func evalute(tick, delta): if blackboard_get(tick, "melee_damage"): return 1.0 return 0.0 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 FAILED if !cam.has_meta("player"): 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 var pdst = o.distance_squared_to(p.origin) blackboard_set(tick, "enemy_distance", pdst) blackboard_set(tick, "randf", get_memory(tick).rnd.randf()) blackboard_set(tick, "enemy_pos", p.origin) blackboard_set(tick, "space", space) 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 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: blackboard_set(tick, "attack_cooldown", blackboard_get(tick, "attack_cooldown") - delta) if blackboard_get(tick, "guard_cooldown") > 0.0: blackboard_set(tick, "guard_cooldown", blackboard_get(tick, "guard_cooldown") - delta) if blackboard_get(tick, "flee_cooldown") > 0.0: blackboard_set(tick, "flee_cooldown", blackboard_get(tick, "flee_cooldown") - delta) blackboard_set(tick, "stamina", clamp(blackboard_get(tick, "stamina"), 0, 100.0)) blackboard_set(tick, "health", clamp(blackboard_get(tick, "health"), 0, 100.0)) return ERR_BUSY func update(tick, delta): return ERR_BUSY 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)