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)
