extends RefCounted
## PK step 3 (docs/research/DRACONIA_PVP.md 3.1; server/pvp.gd): selftest --only=pk3. Bounty maps (maps.json
## "pvp": "bounty"): entry level, open PvP for every mode but Peace, no aggressor / karma, EXP x2, a death drops part
## of the gathered materials (never gear, no EXP); boss rings shown on the ground around live bosses (client).

const SelfTest = preload("res://scripts/selftest.gd")
const CharacterState = preload("res://scripts/server/character_state.gd")
const Pvp = preload("res://scripts/server/pvp.gd")


static func check(ok: bool, what: String) -> void:
	SelfTest.check(ok, "pk3: " + what)


static func _revive(p) -> void:
	p.dead = false
	p.ch.hp = p.ch.max_hp()
	p.ev({"t": "respawned", "id": p.id})


## Base EXP gained by `pe` for killing one still `mid` next to it on the current map.
static func _kill_exp(main: Node, pe, mid: String) -> int:
	var m = SelfTest.still_mob(main, mid, pe.global_position + Vector3(1.5, 0, 0))
	await SelfTest.frames(main, 2)
	var e0: int = pe.ch.base_exp
	var l0: int = pe.ch.base_lv
	pe._apply_hit(m, {"dmg": 99999999, "miss": false})
	await SelfTest.frames(main, 3)
	return int(pe.ch.base_exp) - e0 if int(pe.ch.base_lv) == l0 else -1


static func run(main: Node) -> void:
	print("SELFTEST pk3")
	var srv = main.server
	var a = main.me()
	var bm := "tuskhollow_wilds"
	check(Pvp.is_bounty(bm) and is_equal_approx(Pvp.exp_mult(bm), 2.0) and int(GameData.map(bm).get("pvp_min_lv", 0)) == 35,
		"Tuskhollow Wilds is a bounty map (EXP x2, Lv 35+)")
	var n_b := 0
	for mp in GameData.maps:
		if Pvp.is_bounty(str(mp)):
			n_b += 1
	check(n_b == 4, "4 bounty maps, one per level band (%d)" % n_b)
	# entry level
	a.ch.base_lv = 30
	var m0 := str(a.map.map_id)
	srv.warp(a, bm, Vector3.ZERO)
	await SelfTest.frames(main, 10)
	check(str(a.map.map_id) == m0 and not a.traveling, "Lv 30: the bounty map is refused (%s)" % Pvp.entry_block(a, bm))
	a.ch.base_lv = 45
	check(Pvp.entry_block(a, bm) == "", "Lv 45: may enter")
	# EXP x2: the same monster on a normal field, then on the bounty map
	main.load_map("aldoria_meadows")
	await SelfTest.frames(main, 4)
	SelfTest.clear_mobs(main)
	main.teleport_player(Vector3(14, 0, 14))
	await SelfTest.frames(main, 3)
	var e_field: int = await _kill_exp(main, a, "pudding_slime")
	main.load_map(bm)
	await SelfTest.frames(main, 6)
	SelfTest.clear_mobs(main)
	var e_bounty: int = await _kill_exp(main, a, "pudding_slime")
	check(e_field > 0 and e_bounty == e_field * 2, "monster EXP x2 on the bounty map (%d -> %d)" % [e_field, e_bounty])
	check(Pvp.zone(a) == "bounty", "zone: bounty (%s)" % Pvp.zone(a))
	# open PvP, no karma, materials drop
	var c := CharacterState.new()
	c.new_character("Prospector")
	var b = srv.add_player(c, true, 98)
	srv.enter_map(b, a.map.map_id, a.global_position + Vector3(2, 0, 0))
	await SelfTest.frames(main, 4)
	for p in [a, b]:
		p.ch.base_lv = 45
		p.ch.pk = {}
		_revive(p)
	Pvp.pk(a.ch)["mode"] = "peace"
	check(not Pvp.can_hit(srv, a, b), "Peace mode never attacks, even here")
	Pvp.pk(a.ch)["mode"] = "guild"
	check(Pvp.can_hit(srv, a, b), "bounty map: Guild mode may attack anyone (open PvP)")
	var etc := ""
	for iid in GameData.items:
		if str(GameData.items[iid].get("type", "")) == "etc":
			etc = str(iid)
			break
	b.ch.add_item(etc, 20)
	b.ch.base_exp = 500
	a._apply_hit(b, {"dmg": 30, "miss": false})
	check(not Pvp.orange(a), "no aggressor mark on a bounty map")
	a._apply_hit(b, {"dmg": 99999999, "miss": false})
	await SelfTest.frames(main, 2)
	check(b.dead and Pvp.karma(a) == 0, "bounty kill: no karma")
	check(int(b.ch.count(etc)) < 20 and int(b.ch.count(etc)) > 0 and int(b.ch.base_exp) == 500, "the victim drops part of its materials (%s %d left), keeps its EXP" % [etc, int(b.ch.count(etc))])
	_revive(b)
	srv.remove_player(b)
	# boss rings on the client
	main.load_map("aldoria_meadows")
	await SelfTest.frames(main, 4)
	SelfTest.clear_mobs(main)
	SelfTest.still_mob(main, "pudding_king", a.global_position + Vector3(8, 0, 0))
	await SelfTest.frames(main, 30)
	var pc = load("res://scripts/pvp_client.gd").inst(main)
	check(int(pc.boss_rings) >= 1, "client: a red ring marks the boss's open-PvP area (%d)" % int(pc.boss_rings))
	for arg in OS.get_cmdline_user_args():
		if arg.begins_with("--shotpk3="):
			Pvp.pk(a.ch)["mode"] = "free"
			Pvp._tick_pk(srv, 0.3)
			var rig = main.rig
			rig.yaw = 15.0
			rig.pitch = -55.0
			rig.dist = 48.0
			rig._apply()
			await SelfTest.frames(main, 40)
			main.get_viewport().get_texture().get_image().save_jpg(arg.substr(10), 0.9)
			print("SHOT pk3")
	SelfTest.clear_mobs(main)
	await SelfTest.frames(main, 20)
	check(int(pc.boss_rings) == 0, "the ring goes when the boss is gone")
	a.ch.pk = {}
	_revive(a)
