--[[------------------------------------------------------------------------------------------------------------------
автор: Диденко Руслан (Stohe)

порядок вызова методов биндера:
reload
reinit
load
net_spawn
--------------------------------------------------------------------------------------------------------------------]]


memtsg = {} --' временная таблица
lasthealth = 0
lastime = nil

local watchdog = {}
local watchdog_max = 6
local warn_check_timer = {}
local snd_check_timer = {}
local armor_check = {}

function arm_hit(npc, amount, bone_index)
	if npc and IsStalker(npc) then
		if amount > 0 then
			local r = -1
			local need_drop = false
			local drop_hit = 0.5
			local drop_prob_mod = amount/drop_hit
			if (bone_index == 33) then
				r = math.random()*100
				need_drop = (r<10)
			end
			if ((bone_index >= 34) and (bone_index <= 38)) then
				-- right arm or fingers hit
				r = math.random()*100
				need_drop = (r<30)
			end
			if need_drop and npc then
				local act_item = npc:active_item()
				if act_item then 
					act_item:set_condition(math.random(25,65)/100)
					npc:drop_item(act_item) 
				end
			end
		end
	end
end

function look_to_shot(object, who)
	if object and who then
		if IsStalker(object) and 
		(object:alive() and who:alive()) and
		not xr_wounded.is_wounded(object) and
		((IsStalker(who) and object:relation(who) ~= game_object.friend) or IsMonster(who)) then
			if object:best_enemy() then
				local be = object:best_enemy()
				local enemy_dist = object:position():distance_to_sqr(be:position())
				local who_dist = object:position():distance_to_sqr(who:position())
				if who_dist < enemy_dist then
					object:set_sight(look.danger,who:bone_position("bip01_head"))
				end		
			else	
				object:set_sight(look.danger,who:bone_position("bip01_head"))
			end
		end
	end
end

function recognize_sound(sound_type)
	-- Выясняем тип звука
	local sn_t = "NIL"
	
	if bit_and(sound_type, snd_type.weapon) == snd_type.weapon then
		sn_t = "WPN"
		if bit_and(sound_type, snd_type.weapon_shoot) == snd_type.weapon_shoot then
			sn_t = "WPN_shoot"
		elseif bit_and(sound_type, snd_type.weapon_empty) == snd_type.weapon_empty then
			sn_t = "WPN_empty"
		elseif bit_and(sound_type, snd_type.weapon_bullet_hit) == snd_type.weapon_bullet_hit then
			sn_t = "WPN_hit"
		elseif bit_and(sound_type, snd_type.weapon_reload) == snd_type.weapon_reload then
			sn_t = "WPN_reload"
		end
	elseif bit_and(sound_type, snd_type.item) == snd_type.item then
		sn_t = "ITM"
		if bit_and(sound_type, snd_type.item_pick_up) == snd_type.item_pick_up then
			sn_t = "ITM_pckup"
		elseif bit_and(sound_type, snd_type.item_drop) == snd_type.item_drop then
			sn_t = "ITM_drop"
		elseif bit_and(sound_type, snd_type.item_hide) == snd_type.item_hide then
			sn_t = "ITM_hide"
		elseif bit_and(sound_type, snd_type.item_take) == snd_type.item_take then
			sn_t = "ITM_take"
		elseif bit_and(sound_type, snd_type.item_use) == snd_type.item_use then
			sn_t = "ITM_use"
		end
	elseif bit_and(sound_type, snd_type.monster) == snd_type.monster then
		sn_t = "MST"
		if bit_and(sound_type, snd_type.monster_die) == snd_type.monster_die then
			sn_t = "MST_die"
		elseif bit_and(sound_type, snd_type.monster_injure) == snd_type.monster_injure then
			sn_t = "MST_damage"
		elseif bit_and(sound_type, snd_type.monster_step) == snd_type.monster_step then
			sn_t = "MST_step"
		elseif bit_and(sound_type, snd_type.monster_talk) == snd_type.monster_talk then
			sn_t = "MST_talk"
		elseif bit_and(sound_type, snd_type.monster_attack) == snd_type.monster_attack then
			sn_t = "MST_attack"
		elseif bit_and(sound_type, snd_type.monster_eat) == snd_type.monster_eat then
			sn_t = "MST_eat"
		end
	end
	
	return sn_t
end

function check_sound(self, who, sound_type, sound_position, sound_power)
	-- secure and optimize
	
	if not self or
		not who or
		not sound_type or
		not sound_position or
		not sound_power then
	return end
	
	local actsch = nil
	
	if db.storage[self:id()] and db.storage[self:id()].active_scheme then
		actsch = db.storage[self:id()].active_scheme
	end
	
	if actsch and (actsch == "remark" or actsch == "wounded" or actsch == "actor_dialogs")
	then return end

	-- Реакция на звуки --
	-- Расстояния проверки
	local who_dist = 1000
	local check_dist = 25*sound_power
	
	-- Отсеиваем свои звуки и звуки друзей
	if (IsMonster(who) or IsStalker(who)) 
	and (who:alive() and self:alive()) 
	and self:id() ~= db.actor:id() 
	and who:id() ~= db.actor:id() then
		who_dist = self:position():distance_to(who:position())
		if who_dist > check_dist 
		or who:id() == self:id() 
		or who:id() == db.actor:id()
		or db.cars[who:id()]
		or self:relation(who) == game_object.friend 
		or self:relation(who) == game_object.neutral
		or xr_wounded.is_wounded(self) then
			return
		end
	else	
		return
	end
	
	local snd_t = recognize_sound(sound_type)
	
	local self_hit = hit()
	self_hit.type = hit.strike
	self_hit.power = 0
	self_hit.impulse = 0.01
	self_hit.draftsman = self
	self_hit.direction = vector():set(0,0,0)	
	
	local hit = hit()
	hit.type = hit.strike
	hit.power = 0
	hit.impulse = 0.01
	hit.draftsman = self
	hit.direction = vector():set(0,0,0)		
	
	-- Если тип задан проверим реакцию на него
	if snd_t and snd_t ~= "NIL" and not self:best_enemy() then
		self:set_sight(look.point,sound_position)
		if IsStalker(who) and (self:relation(who) == game_object.enemy) then
			if snd_t == "WPN_empty" or
			snd_t == "WPN_reload" or 
			snd_t == "ITM_drop" or
			snd_t == "ITM_use" then		
				check_dist = 10*sound_power
				who_dist = self:position():distance_to(who:position())
				if who_dist > check_dist then
					return
				else				
					self:set_mental_state(anim.danger)
				end
			elseif snd_t == "WPN_shoot" then
				self:hit(self_hit)
				self:set_mental_state(anim.danger)			
			end		
		elseif IsMonster(who) then
			if snd_t == "MST_step" or snd_t == "MST_talk" then
				if string.find(who:name(), "gigant") then
					self:hit(hit)
					self:set_mental_state(anim.panic)
				else
					self:set_mental_state(anim.danger)
				end				
			elseif snd_t == "MST_attack" then
				self:hit(self_hit)
				self:set_mental_state(anim.danger)			
			end
		end
	end
end

function protected_loot(object, backup)
	death_manager.drop_manager(object, backup):create_release_item()
end

class "motivator_binder" (object_binder)

function motivator_binder:__init (obj) super(obj)
	self.loaded = false
	self.last_update = 0

    	self.first_update = false

end

function motivator_binder:extrapolate_callback(cur_pt)
	if self.st.active_section then
		xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "extrapolate_callback")
		self.st.move_mgr:extrapolate_callback(self.object)
	end

	if patrol(self.object:patrol()):flags(cur_pt):get() == 0 then
		return true
	end
	return false
end

function motivator_binder:reinit()
    object_binder.reinit(self)
    --memusage.collect_info(self.object, "reinit_start")
    --printf("motivator_binder:reinit(): self.object:name()='%s'", self.object:name())

    -- КОЛЛБЕКИ ЗДЕСЬ СТАВИТЬ НЕЛЬЗЯ! Здесь еще неизвестна активная схема.
    -- Ставьте коллбеки в методе update в отмеченном комментарием месте.

    local char_ini = self.object:spawn_ini() or ini_file("scripts\\dummy.ltx")

----------------------------------------------------------------------------------------------------------------------
-- Общие скрипты
----------------------------------------------------------------------------------------------------------------------
    db.storage[self.object:id()] = {  followers = {} }
    self.st = db.storage[self.object:id()]

    --' Создание менеджера контроля положений тела
    --memusage.collect_info(self.object, "before_statemgr")
	self.st.state_mgr = state_mgr.bind_manager(self.object)

	self.st.move_mgr = move_mgr.move_mgr(self.object)
	self.st.move_mgr:initialize()

	--memusage.collect_info(self.object, "after_statemgr")
	--memusage.collect_info(self.object, "reinit_end")
end
----------------------------------------------------------------------------------------------------------------------
function motivator_binder:net_spawn(sobject)
	printf("motivator_binder:net_spawn(): self.object:name()='%s'", self.object:name())
	--memusage.collect_info(self.object, "netspawn_start")
	if not object_binder.net_spawn(self, sobject) then
		return false
	end

--	if self.object:spawn_ini():section_exist("dont_spawn_online") then
--		printf("!!!OFFLINE")
--		alife():object(self.object:id()).dont_spawn_online = true
--	end

	db.add_obj(self.object)

	-- Все CALLBACK-и ставить здесь:
	self.object:set_patrol_extrapolate_callback(motivator_binder.extrapolate_callback, self)
	self.object:set_callback(callback.hit, motivator_binder.hit_callback, self)
	self.object:set_callback(callback.death, motivator_binder.death_callback, self)
	self.object:set_callback(callback.use_object, motivator_binder.use_callback, self)
	--memusage.collect_info(self.object, "after__callback")

	--self.object:set_callback(callback.sound, motivator_binder.hear_callback, self)

    --' Загрузка историй для лагеря.
	if self.loaded == false then
		local char_ini = self.object:spawn_ini() or ini_file("scripts\\dummy.ltx")
		xr_info.loadInfo(self.object, char_ini)
	end

	if not self.object:alive() then
		return true
	end

	--' Для зомбированных чуваков говорим что аномалий не существует
--	if self.object:character_community() == "zombied" then
		local manager = self.object:motivation_action_manager()
		manager:remove_evaluator	(stalker_ids.property_anomaly)
		manager:add_evaluator		(stalker_ids.property_anomaly, property_evaluator_const(false))
--	end

	if xr_companion then
		local manager = self.object:motivation_action_manager()
		manager:add_evaluator(xr_companion.property_need_companion, property_evaluator_const(false))
		manager:add_evaluator(xr_companion.property_need_run, property_evaluator_const(false))

	end

  if ogsm_hideout then
	local manager = self.object:motivation_action_manager()
      manager:add_evaluator(ogsm_hideout.property_blowout, property_evaluator_const(false))
      manager:add_evaluator(ogsm_hideout.property_hideout_lost, property_evaluator_const(false))
      manager:add_evaluator(ogsm_hideout.property_inhide, property_evaluator_const(false))
  end
	
    --' загрузка озвучки
    --memusage.collect_info(self.object, "before_soundmgr")
    xr_sound.load_sound(self.object)
    --memusage.collect_info(self.object, "after_soundmgr_netspawn_end")

	xr_gulag.setup_gulag_and_logic_on_spawn( self.object, self.st, sobject, modules.stype_stalker, self.loaded )

	--// xr_companion
	if xr_companion.is_companion(self.object:id()) then
		sobject:smart_terrain_task_deactivate()
	end	
	--// xr_companion

	return true
end

function motivator_binder:net_destroy()
	if xrs_ai then xrs_ai.npc_net_destroy(self.object) end
	--printf("motivator_binder:net_destroy(): self.object:name()='%s'", self.object:name())

	local st = db.storage[self.object:id()]
	if st.active_scheme then
		xr_logic.issue_event(self.object, st[st.active_scheme], "net_destroy", self.object)
	end

	db.del_obj(self.object)

	db.storage[self.object:id()] = nil

	self:clear_callbacks()
	self.object:set_callback(callback.use_object, nil)

	object_binder.net_destroy(self)
end

function motivator_binder:clear_callbacks()
	self.object:set_patrol_extrapolate_callback(nil)
	self.object:set_callback(callback.hit, nil)
	self.object:set_callback(callback.death, nil)
end

function motivator_binder:hit_callback(obj, amount, local_direction, who, bone_index)
	-- FIXME: коллбеки неплохо было бы регистрировать в общем storage, а не посхемно...
	-- просто всегда ставить их при включении схемы и снимать при отключении.
	if self.st.active_section then
		xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "hit_callback", obj, amount, local_direction, who, bone_index)
	end
	if self.st.combat_ignore then
		xr_logic.issue_event(self.object, self.st.combat_ignore, "hit_callback", obj, amount, local_direction, who, bone_index)
	end
	if self.st.combat then
		xr_logic.issue_event(self.object, self.st.combat, "hit_callback", obj, amount, local_direction, who, bone_index)
	end
	if self.st.hit then
		xr_logic.issue_event(self.object, self.st.hit, "hit_callback", obj, amount, local_direction, who, bone_index)
	end
	if amount > 0 then
		sr_territory.issue_event(self.object, "hit_callback", obj, amount, local_direction, who, bone_index)
		if self.object then
			local drop, err_msg = pcall(xr_wounded.hit_callback,self.object:id())
			if (not drop) and (err_msg) then
				qqq("ERROR: xr_wounded.hit_call_back FAIL "..self.object:name().." reason: "..tostring(err_msg))
			end
		else	
			local drop, err_msg = pcall(xr_wounded.hit_callback,obj:id())
			if (not drop) and (err_msg) then
				qqq("ERROR: xr_wounded.hit_call_back FAIL "..obj:name().." reason: "..tostring(err_msg))
			end
		end
	end

	--// xr_companion
	if xr_companion.is_companion(self.object:id()) then
		xr_companion.we_are_hit(self.object, who, true)
	end

	local drop, err_msg = pcall(arm_hit,obj,amount,bone_index)
	if (not drop) and (err_msg) then
		qqq(err_msg)
	end

	local warn_timer = time_global()
	if not is_quest_npc(obj) and not (xr_companion.is_companion(obj:id()) and xr_companion.being_attacked == false) then
		if not warn_check_timer[obj:id()] then
			warn_check_timer[obj:id()] = warn_timer + math.random(1000, 2000) 
		else
			if warn_check_timer[obj:id()] < warn_timer then
				drop, err_msg = pcall(look_to_shot,obj,who)
				if (not drop) and (err_msg) then
					qqq("ERROR: HIT_CHECK FAIL "..obj:name().." reason: "..tostring(err_msg))
				end
				warn_check_timer[obj:id()] = warn_timer + math.random(1000, 2000)
			end
		end
	end
end

function motivator_binder:death_callback(victim, who)
	if xrs_ai then xrs_ai.npc_death_callback(self.object) end

	if who:id() == db.actor:id() then
		xr_statistic.addKillCount(self.object)
	end

	local name = tostring(victim:name())

  	if actor_need_help then
    		actor_need_help.death_callback(victim,who)
  	end
	
	if self.st.death then
		xr_logic.issue_event(self.object, self.st.death, "death_callback", victim, who)
	end
	if self.st.active_section then
		xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "death_callback", victim, who)
	end
	sr_territory.issue_event(self.object, "death_callback", victim, who)

	smart_terrain.on_death( self.object:id() )
	
	local drop, err_msg = pcall(protected_loot,self.object,victim)
	if (not drop) and (err_msg) then
		qqq("DEATH LOOT FAIL "..name.." "..tostring(err_msg))	
	end

	self:clear_callbacks()

	--' Наносим небольшой импульс вперед.
	local h = hit()
	h.draftsman = self.object
	h.type = hit.fire_wound
	h.direction = db.actor:position():sub(self.object:position())
	h:bone("pelvis")
	h.power = 1
	h.impulse = 10
	self.object:hit(h)

	if(actor_stats.remove_from_ranking~=nil)then
		local community = self.object:character_community()
		if community == "zombied" or
		   community == "monolith" or
		   community == "arena_enemy"
		then
			return
		end
		actor_stats.remove_from_ranking(self.object:id())
	end

	--// xr_companion
	xr_companion.companion_death(self.object)

end

function motivator_binder:use_callback(obj, who)
	if self.object:alive() then
		xr_use.notify_on_use(obj, who)
		if self.st.active_section then
			xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "use_callback", obj, who)
		end
	else
		treasure_manager.get_treasure_manager():use(self.object)
	end
end

function motivator_binder:update(delta)
	object_binder.update(self, delta)
	--if lwc.oau_watchdog ~= 0 then
		--abort("oau_watchdog: "..lwc.oau_watchdog..", watchdog: "..lwc.watchdog)
	--end

	if not watchdog[self.object:id()] then
		watchdog[self.object:id()] = 0
	end

    	if self.first_update == false then
        	if self.object:alive() == false then
			death_manager.drop_manager(self.object):create_release_item()
        	end        
        	self.first_update = true
    	end

	--' Апдейт менеджера состояний тела
	if self.st.state_mgr then
		if self.object:alive() then
			self.st.state_mgr:update()
		else
			self.st.state_mgr = nil
		end
	end
	
	--' Апдейт саундменеджера
	if self.object:alive() then
		xr_sound.update(self.object)
	end

	if self.object:alive() then
		if self.object:is_talk_enabled() then
			self.object:set_tip_text("character_use")
		else
			self.object:set_tip_text("")
		end
	else
		self.object:set_tip_text_default()
	end

	self.object:info_clear()
	local best_enemy = self.object:best_enemy()
	if best_enemy then
		self.object:info_add('enemy -- ' .. best_enemy:name())
	end
	local active_section = db.storage[self.object:id()].active_section
	if active_section then
		self.object:info_add('section -- ' .. active_section)
	end
	self.object:info_add('name -- ' .. self.object:name())

	if watchdog[self.object:id()] == 1 then
		-- Апдейт включения/выключения фонарика
		if time_global() - self.last_update > 1000 then
		   sr_light.check_light(self.object)
		   self.last_update = time_global()
		end	
		-- Апдейт включения/выключения фонарика
	end
	if watchdog[self.object:id()] == 2 then
		-- Апдейт торговли
		if self.object:alive() then
			if self.st.state_mgr.combat == false and
			   self.st.state_mgr.alife == false
			then
				trade_manager.update(self.object)
			end					
		end
		-- Апдейт торговли
	end
	if watchdog[self.object:id()] == 3 then
		--' Апдейт напарника - если у какого-то непися главный враг - главгерой,
		--' то напарник воспринимает это как атаку на самого себя
		xr_companion.process_enemy(self.object)	
		-- Апдейт напарника
	end
	if watchdog[self.object:id()] == 4 then
		-- Обновление схем гранатометания, медика и переодевания
		if xrs_ai then xrs_ai.npc_update(self) end
		-- Обновление схем гранатометания, медика и переодевания
	end
	if watchdog[self.object:id()] == 5 then
		ogse_night.check_night(self.object)
	end
	
-------------------------------------------------------	
-- Проверка брони
-------------------------------------------------------

	watchdog[self.object:id()] = watchdog[self.object:id()] + 1
	if watchdog[self.object:id()] > watchdog_max then
		watchdog[self.object:id()] = 0
	end
end

function motivator_binder:reload(section)
	--memusage.collect_info(self.object, "reload_start")
	object_binder.reload(self, section)
	--printf("motivator_binder:reload(): self.object:name()='%s'", self.object:name())
	--self.object:set_pda_callback(pda_callback)
	--memusage.collect_info(self.object, "reload_end")
end

function motivator_binder:net_save_relevant()
	--printf("motivator_binder:net_save_relevant(): self.object:name()='%s'", self.object:name())
	return true
end

function motivator_binder:save(packet)
	object_binder.save(self, packet)
	packet:w_bool(self.treasure_processed)
	xr_logic.save_obj(self.object, packet)
	dialog_manager.save(self.object, packet)
	trade_manager.save(self.object, packet)
end

function motivator_binder:load(reader)
	self.loaded = true

	printf("motivator_binder:load(): self.object:name()='%s'", self.object:name())
	object_binder.load(self, reader)
	printf("motivator_binder:object_binder.load(): self.object:name()='%s'", self.object:name())

	if reader:r_eof() then
		abort("SAVE FILE IS CORRUPT")
	end


	self.treasure_processed = reader:r_bool()

	 xr_logic.load_obj(self.object, reader)
             self.npc_script_version = 7
             dialog_manager.load(self.object, reader, self.npc_script_version)
              trade_manager.load(self.object, reader)
end

function motivator_binder:hear_callback(self, who, sound_type, sound_position, sound_power)
	if xr_companion.is_companion(self:id()) and xr_companion.being_attacked == false then
		return
	end		
	
	local snd_timer = time_global()
	
	if not snd_check_timer[self:id()] then
		snd_check_timer[self:id()] = snd_timer + math.random(1000, 2000) 
	else
		if snd_check_timer[self:id()] < snd_timer then
			local result, err = pcall(check_sound, self, who, sound_type, sound_position, sound_power)
			if (not result) and (err) then
				qqq("ERROR: SND_CHECK FAIL "..self:name().." reason: "..tostring(err))
			end
			snd_check_timer[self:id()] = snd_timer + math.random(1000, 2000)
		end
	end
end

function AddToMotivator(npc)
	if alife() then
		npc:bind_object(this.motivator_binder(npc))
	end
end

-- Эвалюаторы, которые имеют высший приоритет, и, соответственно, перебивают остальные скрипты собой
function addCommonPrecondition(action)
	--action:add_precondition (world_property(xr_evaluators_id.reaction,false))

	if ogsm_hideout then
		action:add_precondition(world_property(ogsm_hideout.property_blowout, false))
		action:add_precondition(world_property(ogsm_hideout.property_hideout_lost, false))
		action:add_precondition(world_property(ogsm_hideout.property_inhide, false))
	end

	action:add_precondition (world_property(xr_evaluators_id.stohe_meet_base + 1,false))
	action:add_precondition (world_property(xr_evaluators_id.sidor_wounded_base + 0,  false))
	action:add_precondition (world_property(xr_evaluators_id.chugai_heli_hunter_base, false))
	action:add_precondition (world_property(xr_evaluators_id.abuse_base, false))
	action:add_precondition (world_property(xr_companion.property_need_companion, false))
	action:add_precondition (world_property(xr_companion.property_need_run, false))
	if xrs_ai then xrs_ai.addCommonPrecondition(action) end
end
