--[[------------------------------------------------------------------------------------------------
 File       : m_xrs_medic.script (ver: 2.0)
 Description: Лечим нерадивых товарищей...
 Copyright  : 2008 © xStream
 Author     : xStream, Red75, Sokol_jack
 Last edit  : 13.06.2008 (by Artos)
--]]------------------------------------------------------------------------------------------------

--function printf(fmt, ...)
	--dbg.log("xrs_medic:"..fmt,...)
--end

----------------------------------------------------------------------
--/ SETs
----------------------------------------------------------------------

mk_types = {
	[1] = "medkit",
	[2] = "medkit_army",
	[3] = "medkit_scientic",
	[4] = "bandage"
}

local excluded_npcs = {
	esc_fox                    = true,
	esc_vagon_wounded          = true,
	gar_bandit_stroyka3        = true,
	gar_wounded_bandit         = true,
	gar_dm_bandit_1            = true,
	gar_dm_bandit_2            = true,
	gar_dm_bandit_3            = true,
	gar_sniper_bandit          = true,
	gar_seryi_drug3            = true,	
	val_sos_wounded               = true,
	val_escort_bandit_halfdead    = true,
	val_sacrifice_victim          = true
}

local excluded_healers = {
	esc_shustryi = true
}

local subj_woundeds           = {
	esc_fox                    = true, --/ [5] Лис
	esc_vagon_wounded          = true, --/ [7] Толик
	gar_wounded_bandit         = true, --/ [109]
	gar_bandit_stroyka3         = true, --/ [xxx]
	val_sos_wounded            = true, --/ [422]
	val_escort_bandit_halfdead = true  --/ [424]
}

local tbl_woundeds = {} --/ список раненых
local tbl_healers  = {} --/ список лекарей
local friends_r = 50 --/ дистанция доступных для лечения друзей
local neutral_r = 30 --/ дистанция доступных для лечения нейтралов
local dist_max_f = 50 --/ дистанция (радиус) для лечения друзей
local dist_max_n = 30 --/ дистанция (радиус) для лечения нейтралов

----------------------------------------------------------------------
--/ SCHEME PART
----------------------------------------------------------------------
evid_medic       = 18680
evid_self_medic  = evid_medic+1

actid_medic      = evid_medic
actid_self_medic = actid_medic+1

----------------------------------------------------------------------------------------------------------------------
--/ EVALUATORs
----------------------------------------------------------------------------------------------------------------------
class "evaluator_medic" (property_evaluator) --/ лечим других

function evaluator_medic:__init(name, storage) super (nil, name)
	self.a = storage
	self.a.next_medic_change_pos = 0
end

function evaluator_medic:evaluate()
	local npc = self.object
	if tbl_woundeds[npc:id()] or excluded_healers[npc:name()] then --/ если npc сам ранен ...
		block_scheme(self.a) --/ Сбрасываем схему
		return false
	end
	if self.a.target_wounded and self.a.have_medkit then --/ уже есть назначенный пациент и есть медикаменты
		local target_wounded = get_npc(self.a.target_wounded)
--		printf("evaluator_medic:NPC=[%s]-лекарь->пациент=[%s]-есть_аптечки:[1%s]", npc:name(), target_wounded:name(), ">>" ) --/#~#
		if target_wounded and target_wounded:alive() and tbl_woundeds[target_wounded:id()] then --/ пока жив и в списке раненых
--			printf("evaluator_medic:NPC=[%s]-лекарь->пациент=[%s]-в_списках:[2%s]", npc:name(), target_wounded:name(), ">>" ) --/#~#
			--if ( target_wounded:wounded() or target_wounded:critically_wounded() ) then --/ еще ранен (не вылечен)
			if xr_wounded.is_wounded(target_wounded) then --/ еще ранен (не вылечен)
--				printf("evaluator_medic:NPC=[%s]-лекарь->пациент=[%s]-ранен:[4%s]", npc:name(), target_wounded:name(), ">>" ) --/#~#
				if not ( tbl_healers[target_wounded:id()] and tbl_healers[target_wounded:id()] ~= npc:id() ) then --/ назначен другой лекарь
--					printf("evaluator_medic:Лекарь=[%s],[%s]->назначен->Пациенту=[%s]->:[%s]", npc:name(), npc:character_name(), target_wounded:name(), target_wounded:character_name(), ">>" ) --/#~#
					return true
				end
			end
		end
--	printf("evaluator_medic:NPC=[%s],пациент=[%s],id=[%s]-Исключен:[%s]", npc:name(), get_npc(self.a.target_wounded):name(), self.a.target_wounded, ">>X") --/#~#
	else --/ объекта лечения нет, надо найти (ищем не чаще 1 раза в ~5 секунд)
		if (self.next_check_time or 0) > time_global() then return false end --/ timeout
		self.next_check_time = time_global() + 5000 + math.random(0,500) --/ set timeout
		
		if not npc_have_medkit(npc) then --/ у npc нет аптечек - прекращаем поиск
			self.a.have_medkit = nil --/ флаг наличия аптечек
			block_scheme(self.a) --/ Сбрасываем схему
			return false
		end
		self.a.have_medkit = true --/ флаг наличия аптечек
		--/ проверяем есть ли раненые доступные для npc
		local best_target_wnd = nil
		local best_target_relation = game_object.neutral --/ врагов не лечим
		local best_target_dist = dist_max_f --/ за пределами не ищем
		local table_woundeds = get_table_wounded()
		for k,v in pairs(table_woundeds) do
			if not tbl_healers[k] then --/ проверяем только тех, кому не назначен лекарь
				local obj = get_npc(k)
--				printf("evaluator_medic:NPC=[%s]:раненый=[%s]-не_имеет_лекаря:[1%s]", npc:name(), obj:name(), ">" ) --/#~#
				if obj and obj:clsid() == clsid.script_stalker and obj:alive() and not excluded_npcs[obj:name()] then --/ объект пока жив и не в исключениях
--					printf("evaluator_medic:NPC=[%s]:раненый=[%s]-жив&не_исключен:[2%s]", npc:name(), obj:name(), ">" ) --/#~#
					--if npc:see(obj) and npc:accessible(obj:level_vertex_id()) then --/ видим его и он доступен
					if npc:accessible(obj:level_vertex_id()) then --/ объект доступен
--						printf("evaluator_medic:NPC=[%s]:раненый=[%s]-доступен:[4%s]", npc:name(), obj:name(), ">" ) --/#~#
						local dist = npc:position():distance_to(obj:position())
						local rel = npc:relation(obj)
						if rel >= best_target_relation and dist < best_target_dist then
--							printf("evaluator_medic:NPC=[%s]:раненый=[%s]-не_враг&недалеко:[3%s]", npc:name(), obj:name(), ">" ) --/#~#
							best_target_wnd = k
							best_target_relation = rel
							best_target_dist = dist
							--break
						end
					end
				end
			end
		end
		
		if best_target_wnd then
			self.a.target_wounded = best_target_wnd
			if self.a.next_medic_change_pos and self.a.next_medic_change_pos < time_global() then
				self.a.from_point = npc:position()
				self.a.dist = npc:position():distance_to(get_npc(best_target_wnd):position())
			end
			tbl_healers[best_target_wnd] = npc:id() --/ заносимся в список лекарей
--			printf("evaluator_medic:Лекарь=[%s],[%s]->нашел->Пациента=[%s],[%s]:[%s]", npc:name(), npc:character_name(), get_npc(best_target_wnd):name(), get_npc(best_target_wnd):character_name(), ">" ) --/#~#
			return true
		end
	end
	--/ Сброс схемы
--	printf("evaluator_medic:NPC=[%s]:Сброс_схемы:[%s]", npc:name(), "X") --/#~#
	block_scheme(self.a) --/ Сбрасываем схему
	return false --/ нет доступных нам объектов лечения -> выход
end

function block_scheme(st)
	if st.target_wounded then
		tbl_healers[st.target_wounded] = nil
	end
	st.target_wounded = nil
	st.from_point = nil
	st.dist = nil
end

--------------------------------------------------
class "evaluator_self_medic" (property_evaluator) --/ лечимся сами

function evaluator_self_medic:__init(name, storage) super (nil, name)
	self.a = storage
	self.check_time = time_global() + math.random(5000,20000)
end

function evaluator_self_medic:evaluate()
	if self.a.self_healing then return true end --/ самоисцеление уже разрешено
	
	if (self.next_check_time or 0) > time_global() then return false end
	self.next_check_time = time_global() + 5000
	
	local npc = self.object
	if excluded_npcs[npc:name()] or tbl_woundeds[npc:id()] or tbl_healers[npc:id()] then return false end --/ в исключениях или в списке раненых или уже имеем лекаря
	if self.check_time < time_global() then
		self.check_time = time_global() + math.random(3000,10000)
		if not npc_have_medkit(npc) then
			self.a.have_medkit = nil --/ флаг наличия аптечек
			return false --/нет аптечек - нет самоисцеления
		end
		self.a.have_medkit = true --/ флаг наличия аптечек
		if npc.health < 0.5 then
			self.a.self_healing = true --/ мы ранены, самоисцеление => true
--			printf("evaluator_self_medic:NPC=[%s]-ранен:[!]", npc:name() ) --/#~#
			return true
		end
	end
	return false
end

----------------------------------------------------------------------------------------------------------------------
--/ ACTIONs
----------------------------------------------------------------------------------------------------------------------

class "action_medic" (action_base)

function action_medic:__init (npc,action_name,storage) super (nil,action_name)
	self.a = storage
end

function action_medic:initialize()
	action_base.initialize(self)
	local npc = self.object
	--/ Ориентируемся
	npc:set_desired_position()
	npc:set_desired_direction()
	--/ Задаем параметры движения
	npc:set_detail_path_type(move.line)
	npc:set_path_type(game_object.level_path)
	--/ Освободим сталкера от всех ограничений
	npc:remove_all_restrictions()
	local wounded_npc = get_npc(self.a.target_wounded)
	--/ используем стэйт-менеджер для настройки анимации | TODO: тут нужно еще поработать!
	local state_mng = "search" --/ "idle"|"assault"|"rush"
	--local dist = self.object:position():distance_to(wounded_npc:position())
	if self.a.dist then
		if self.a.dist < 1.3 then
			state_mng = "assault"
		elseif self.a.dist < 8 then
			state_mng = "run"
		else
			state_mng = "sprint"
		end
	end
	if state_mng == "sprint" then
		state_mgr.set_state(npc, state_mng, nil, nil, nil, {animation = true})
	else
		state_mgr.set_state(npc, state_mng, nil, nil, {look_object = wounded_npc}, {animation = true})
	end
	self.a.state_mng = state_mng
	self.lsct = time_global() + 1500
	self.lvid = npc:level_vertex_id()
	
	xr_sound.set_sound(npc, nil)
	stop_play_sound(npc)
end

function action_medic:execute()
	action_base.execute (self)
	local npc = self.object
--	printf("action_medic:npc=[%s]:[>]", npc:name())
	local wounded_npc = get_npc(self.a.target_wounded)
	local dist = npc:position():distance_to(wounded_npc:position())
	self.a.dist = dist
	if dist > 1.2 then
		--
		local v_id = wounded_npc:level_vertex_id()
		if self.delay and self.delay > time_global() then return end
		self.delay = time_global()+1000
		--/ Проверим не завис ли NPC
		local state_mng = self.a.state_mng
		if not state_mng then
			state_mng = "idle"
		end
		if self.lsct < time_global() then
			self.lsct = time_global()+1500
			if self.lvid == npc:level_vertex_id() then
				state_mgr.set_state(npc,"idle")
				npc:set_dest_level_vertex_id(self.lvid)
				self.lvid = 0
				return
			end
			self.lvid = npc:level_vertex_id()
			state_mgr.set_state(npc,state_mng)
		end
		--/ Отправляем нашего NPC в в ближайшую разрешенную ему по направлению к заданной
		local tmp = utils.send_to_nearest_accessible_vertex( npc, v_id )
		--[[
		utils.send_to_nearest_accessible_vertex(npc, wounded_npc:level_vertex_id())
		npc:set_desired_position(wounded_npc:position())
		npc:set_desired_direction(wounded_npc:position():sub(npc:position()))
		--]]
--		printf("action_medic:Лекарь=[%s],[%s]->к->Пациенту=[%s]:дистанция=[%s]:[%s]", npc:name(), npc:character_name(), wounded_npc:name(), npc:position():distance_to(wounded_npc:position()), ">")
	else
--		printf("action_medic:Лекарь=[%s],[%s]->лечит->Пациента=[%s],[%s]:[%s]", npc:name(), npc:character_name(), wounded_npc:name(), wounded_npc:character_name(), ">>>")
		if not self.step1 and npc:animation_count() == 0 then
			local tmp = utils.send_to_nearest_accessible_vertex(npc, npc:level_vertex_id())
			npc:set_desired_position()
			npc:set_desired_direction()
			npc:clear_animations()
			npc:set_movement_type(move.stand)
			state_mgr.set_state(npc, "dynamite",nil,nil,{ look_object = wounded_npc }, {animation = true})
			self.step1 = time_global() + 4000
			if npc:object("medkit") ~= nil then
				npc:transfer_item(npc:object("medkit"), wounded_npc)
				self.medsec = "medkit"
			elseif npc:object("medkit_army") ~= nil then
				npc:transfer_item(npc:object("medkit_army"), wounded_npc)
				self.medsec = "medkit_army"
			elseif npc:object("medkit_scientic") ~= nil then
				npc:transfer_item(npc:object("medkit_scientic"), wounded_npc)
				self.medsec = "medkit_scientic"
			end
		end
		if self.step1 and not self.step2 and self.step1 < time_global() then
			npc:clear_animations()
			--wounded_npc:eat(wounded_npc:object(self.medsec))
			xr_wounded.unlock_medkit(wounded_npc)
			
			if wounded_npc:relation(npc) ~= game_object.enemy then
				wounded_npc:set_relation(game_object.friend, npc)
			end
			npc:change_character_reputation(10);
			self.step2 = time_global() + 2000
		end
		if self.step1 and self.step2 and self.step2 < time_global() then
		
			xr_logic.initialize_obj(wounded_npc, db.storage[wounded_npc:id()], true, db.actor, modules.stype_stalker)
			--db.storage[wounded_npc:id()].wounded.wound_manager.medkit_eaten = true
			--db.storage[wounded_npc:id()].wounded.wound_manager:update()
			
			tbl_healers[self.a.target_wounded] = nil
			self.a.target_wounded = nil
			self.a.from_point = nil
			self.a.dist = nil
			self.a.state_mng = nil
		end
	end
end

function action_medic:finalize()
	action_base.finalize (self)
	self.a.next_medic_change_pos = time_global() + 30000
end

--------------------------------------------------
class "action_self_medic" (action_base)

function action_self_medic:__init (npc,action_name,storage) super (nil,action_name)
	self.a = storage
end

function action_self_medic:initialize()
	action_base.initialize(self)
	local npc = self.object
	if not npc_have_medkit(npc) then
		self.a.self_healing = false
		return
	end
	
	npc:set_item(object.idle,nil)
	npc:set_movement_type(move.stand)
	npc:set_desired_position()
	npc:set_desired_direction()
	utils.send_to_nearest_accessible_vertex(npc, npc:level_vertex_id())
	
	state_mgr.set_state(npc, "idle")
	self.dir = npc:direction()
	
	xr_sound.set_sound(npc, nil)
	stop_play_sound(npc)
end

function action_self_medic:execute()
	action_base.execute (self)
	local npc = self.object
	if not self.allow_empty and not npc_have_medkit(npc) then
		self.a.self_healing = false
		return
	end
	npc:set_sight(look.direction,self.dir)
	npc:set_desired_direction(self.dir)
	utils.send_to_nearest_accessible_vertex(npc, npc:level_vertex_id())
	
	if not self.initialized and state_mgr.get_state(npc) == "idle" then
		if npc:animation_count() == 0 then
			local yad = npc:object("yad")
			if yad then
				yad:enable_attachable_item(true)
			end
			
			npc:clear_animations()
			--npc:add_animation("stoya_ruje_ 0")
			npc:add_animation("idle_0_to_sit_2")
			npc:add_animation("vodka_3")
			npc:add_animation("sit_2_to_idle_0")
			self.initialized = true
			self.end_time = time_global() + 5000
			self.end_time_total = self.end_time + 2000
		end
		return
	else
		npc:set_item(object.idle,nil)
		local yad = npc:object("yad")
		if yad then
			yad:enable_attachable_item(true)
		end
	end
	if self.end_time and self.end_time < time_global() then
		if npc:object("medkit") ~= nil then
			self.medsec = "medkit"
		elseif npc:object("medkit_army") ~= nil then
			self.medsec = "medkit_army"
		elseif npc:object("medkit_scientic") ~= nil then
			self.medsec = "medkit_scientic"
		elseif npc:object("bandage") ~= nil then
			self.medsec = "bandage"
		end
		
		local yad = npc:object("yad")
		if yad then
			yad:enable_attachable_item(false)
		end
		
		--npc:clear_animations()
		self.eat_obj = npc:object(self.medsec)
		npc:eat(self.eat_obj)
		self.allow_empty = true
	end
	if self.end_time_total and self.end_time_total < time_global() then
		self.a.self_healing = false
	end
end

function action_self_medic:finalize()
	action_base.finalize (self)
	self.a.self_healing = false
	local npc = self.object
	local yad = npc:object("yad")
	if yad then
		yad:enable_attachable_item(false)
	end
end

----------------------------------------------------------------------------------------------------------------------
--/ BINDER
----------------------------------------------------------------------------------------------------------------------
function add_to_binder(object, ini, scheme, section, storage)
	local operators  = {}
	local properties = {}
	
	local manager = object:motivation_action_manager()
	
	operators["medic"]       = actid_medic
	operators["self_medic"]  = actid_self_medic
	
	properties["medic"]      = evid_medic
	properties["self_medic"] = evid_self_medic
	
	--/ исключения:
	local subj_zombi = object:character_community() == "zombied"
						 or object:character_community() == "trader"
						 or object:character_community() == "arena_enemy"
						 or object:name() == "mil_stalker0012"
						 or object:name() == "yantar_ecolog_general"
						 or object:name() == "escape_trader"
	if subj_zombi then --/  or subj_wounded(object:name())
		manager:add_evaluator (properties["medic"],      property_evaluator_const(false))
		manager:add_evaluator (properties["self_medic"], property_evaluator_const(false))
	else
		manager:add_evaluator (properties["medic"],      evaluator_medic("medic", storage))
		manager:add_evaluator (properties["self_medic"], evaluator_self_medic("self_medic", storage))
	end
	
	--/ Лечение раненых
	local action = action_medic (object,"medic", storage)
	action:add_precondition (world_property(stalker_ids.property_alive, true))
	action:add_precondition (world_property(stalker_ids.property_enemy, false))
	action:add_precondition (world_property(xr_evaluators_id.sidor_wounded_base, false))
	action:add_precondition (world_property(properties["medic"], true))
	action:add_effect       (world_property(properties["medic"], false))
	manager:add_action (operators["medic"], action)
	
	--/ Самолечение
	action = action_self_medic (object,"self_medic", storage)
	action:add_precondition (world_property(stalker_ids.property_alive, true))
	action:add_precondition (world_property(stalker_ids.property_enemy, false))
	action:add_precondition (world_property(xr_evaluators_id.sidor_wounded_base, false))
	action:add_precondition (world_property(properties["self_medic"], true))
	action:add_precondition (world_property(properties["medic"], false))
	action:add_effect       (world_property(properties["self_medic"], false))
	manager:add_action (operators["self_medic"], action)
	
	action = manager:action(xr_actions_id.alife)
	action:add_precondition(world_property(properties["medic"], false))
	action:add_precondition(world_property(properties["self_medic"], false))
end

function set_scheme(npc, ini, scheme, section)
	local st = xr_logic.assign_storage_and_bind(npc, ini, scheme, section)
	st.enabled = true
end

function disable_scheme(npc, scheme)
	local st = db.storage[npc:id()][scheme]
	if st then
		st.enabled = false
	end
end

----------------------------------------------------------------------------------------------------------------------
--/ HELPERS
----------------------------------------------------------------------------------------------------------------------

function get_npc(id)
	return level.object_by_id(id)
end

function death_callback(npc)
--	printf("death_callback:[Run]") --/#~#
	tbl_woundeds[npc:id()] = nil
	tbl_healers[npc:id()] = nil
	for k,v in pairs(tbl_healers) do
		if v == npc:id() then
			tbl_healers[k] = nil
			break
		end
	end
	if npc:object("yad") then
		alife():release(alife():object(npc:object("yad"):id()))
	end
	
	if not npc:alive() then
		local remove_medkits = {}
		npc:iterate_inventory(function (dummy, item)
			if item == nil or alife():object(item:id()) == nil then
--				printf("death_callback:iterate_inventory:[ret]") --/#~#
				return
			end
			for k,v in pairs(mk_types) do
				if item:section() == v then
					table.insert(remove_medkits, item:id())
					break
				end
			end
		end, npc)
		for k,v in pairs(remove_medkits) do
			alife():release(alife():object(v),true)
		end
	end
--	printf("death_callback:[End]") --/#~#
end

function npc_update(binder)
	local npc = binder.object
	
	if not npc:alive() then
		tbl_woundeds[npc:id()] = nil
		tbl_healers[npc:id()] = nil
		return
	end
	
	if not tbl_woundeds[npc:id()] then --/ не в списке раненых
--		printf("npc_update:npc=[%s]-не_в_списке", npc:name() ) --/#~#
		--if npc:critically_wounded() or npc:wounded() then
		if xr_wounded.is_wounded(npc) then --/ ранен
--			printf("npc_update:npc=[%s],id=[%s]->ранен,добавлен_в_список:[!]", npc:name(), npc:id() ) --/#~#
			tbl_woundeds[npc:id()] = true
			for k,v in pairs(tbl_healers) do
				if v == npc:id() then
--					printf("npc_update:npc=[%s],id=[%s]->исключен_из_лекарей", npc:name(), npc:id() ) --/#~#
					tbl_healers[k] = nil
					break
				end
			end
		else --/ если не ранен - удаляем из списка
			tbl_woundeds[npc:id()] = nil
		end
	end
	
	if   npc:character_community() == "zombied"
		or npc:character_community() == "trader"
		or npc:character_community() == "arena_enemy"
		or npc:name() == "mil_stalker0012"
		or npc:name() == "yantar_ecolog_general"
		--or subj_woundeds(npc:name()) --/#+#
		then
		return
	end
	
	if not npc:object("yad") then
		alife():create("yad",npc:position(),npc:level_vertex_id(),npc:game_vertex_id(),npc:id())
	end
	
	if not binder.medkit_update_time then
		binder.medkit_update_time = time_global() + 5000
	end
	if time_global() > binder.medkit_update_time then
		if not npc:object("medkit") and not npc:object("medkit_army") and not npc:object("medkit_scientic") then
			local sobj = alife():object(npc:id())
			if sobj and sobj.rank and sobj:rank() ~= 0 then
				if math.random() > 0.2 then
					alife():create(mk_types[3%math.ceil(sobj:rank()/1000)+1],npc:position(),npc:level_vertex_id(),npc:game_vertex_id(),npc:id())
				end
			end
		end
		if not npc:object("bandage") then
			if math.random() > 0.05 then
				alife():create("bandage",npc:position(),npc:level_vertex_id(),npc:game_vertex_id(),npc:id())
			end
		end
		binder.medkit_update_time = time_global() + 300000
	end
end

function pickup(obj)
	if obj:section() == "yad" then alife():release(alife():object(obj:id())) end
end

----------------------------------------------------------------------
function get_table_wounded()
	return tbl_woundeds
end

function add_wounded(npc)
--	printf("add_wounded:npc=[%s]", npc:character_name())
	tbl_woundeds[npc:id()] = true
end

function remove_wounded(npc)
--	printf("remove_wounded:npc=[%s]", npc:character_name())
	tbl_woundeds[npc:id()] = nil
end

function npc_have_medkit(npc)
	return npc:object("medkit") ~= nil
			or npc:object("medkit_army") ~= nil
			or npc:object("medkit_scientic") ~= nil
			or npc:object("bandage") ~= nil
end
