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

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


----------------------------------------------------------------------------------------------------------------------
class "motivator_binder" (object_binder)

function motivator_binder:__init (obj) super(obj)
	self.loaded = false
	self.last_update = 0
	----memusage.collect_info(self.object, "__init")
	self.first_update = false
--	self.need_relation_update = false

	self.opened_doors = {} --Хранит таблицу дверей, открытых данным персонажем (чтобы потом закрыть)
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())
	local visual = utils.cfg_get_string(system_ini(), self.object:section(), "set_visual", obj, false, "")
	if visual ~= nil and visual ~= "" then
		if visual == "actor_visual" then
			self.object:set_visual_name(db.actor:get_visual_name())
		else
			self.object:set_visual_name(visual)
		end
	end

	xrs_dyn_music.npc_table[self.object:id()] = self.object:id()
	--memusage.collect_info(self.object, "netspawn_start")
	if not object_binder.net_spawn(self, sobject) then
		return false
	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)
	self.object:set_callback(callback.sound, motivator_binder.hear_callback, self)
	--memusage.collect_info(self.object, "after__callback")

	-- ставим дефолтное расстояние входа в смарткаверы

	self.object:apply_loophole_direction_distance(1.0)

		--' Загрузка историй для лагеря.
	if self.loaded == false then
		local char_ini = ""
		local spawn_ini = self.object:spawn_ini()
		local filename = nil
		if spawn_ini then
			filename = utils.cfg_get_string(spawn_ini, "logic", "cfg", self.object, false, "")
			printf("filename is [%s]", tostring(filename))
		end
		if filename ~= nil then
			char_ini = ini_file(filename)
		else
			char_ini = self.object:spawn_ini() or ini_file("scripts\\dummy.ltx")
		end

		printf("xr_info loaded!!!")
		xr_info.loadInfo(self.object, char_ini)
	end

	if not self.object:alive() then
		self.object:death_sound_enabled(false)

		release_body_manager.get_release_body_manager():moving_dead_body(self.object) ---НЕ ЗАБЫТЬ УБРАТЬ
		return true
	end

-- setting npc relation and sympathy from storage
	local relation = db.goodwill.relations and db.goodwill.relations[self.object:id()]
	if relation ~= nil and db.actor then
		game_relations.set_npcs_relation(self.object, db.actor, relation)
	end
	local sympathy = db.goodwill.sympathy and db.goodwill.sympathy[self.object:id()]
	if sympathy ~= nil then
		game_relations.set_npc_sympathy(self.object, sympathy)
	end
--	game_relations.set_level_faction_community(self.object)
	db.add_enemy( self.object )
	self.e_index = db.heli_enemy_count - 1

	--' загрузка озвучки
	--memusage.collect_info(self.object, "before_soundmgr")
	sound_theme.init_npc_sound(self.object)
	--memusage.collect_info(self.object, "after_soundmgr_netspawn_end")


	--' Для зомбированных чуваков говорим что аномалий не существует
	if get_object_story_id(self.object:id()) == "zat_b53_artefact_hunter_1" 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

	xr_reach_task.add_reach_task_action(self.object)
--******************************* Телепорт на первую точку пути работы смарттеррейна...*****************************
	local se_obj = alife():object(self.object:id())
	if se_obj ~= nil then
		if db.spawned_vertex_by_id[se_obj.id] ~= nil then
			self.object:set_npc_position(level.vertex_position(db.spawned_vertex_by_id[se_obj.id]))
			db.spawned_vertex_by_id[se_obj.id] = nil
		elseif db.offline_objects[se_obj.id] ~= nil and db.offline_objects[se_obj.id].level_vertex_id ~= nil then
			printf("changing position for object[%s] from %s to %s : level vertex [%s] to [%s]", se_obj:name(), vec_to_str(se_obj.position), vec_to_str(level.vertex_position(db.offline_objects[se_obj.id].level_vertex_id)), tostring(se_obj.m_level_vertex_id), tostring(db.offline_objects[se_obj.id].level_vertex_id))
			self.object:set_npc_position(level.vertex_position(db.offline_objects[se_obj.id].level_vertex_id))
		elseif se_obj.m_smart_terrain_id ~= 65535 then
			local smart_terrain = alife():object(se_obj.m_smart_terrain_id)
			if smart_terrain.arriving_npc[se_obj.id] == nil then
				local smart_task = smart_terrain.job_data[smart_terrain.npc_info[se_obj.id].job_id].alife_task
				self.object:set_npc_position(smart_task:position())
			end
		end
	end
--******************************************************************************************************************


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

	--' Универсальный тормоз на выход из комбата
	if character_community(self.object) ~= "zombied" then
		post_combat_idle.add_post_combat_idle(self.object)
	end


	self.object:group_throw_time_interval(2000)

	return true
end

function motivator_binder:net_destroy()
	--printf("motivator_binder:net_destroy(): self.object:name()='%s'", self.object:name())
	xrs_dyn_music.npc_table[self.object:id()] = nil

	xr_combat_ignore.fighting_with_actor_npcs[self.object:id()] = nil
--	xrs_dyn_music.stop_combat(self.object:id())
	xr_sound.stop_sounds_by_id(self.object:id())
	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
	if self.st.reach_task then
		xr_logic.issue_event(self.object, self.st.reach_task, "net_destroy", self.object)
	end

	local on_offline_condlist = db.storage[self.object:id()] and db.storage[self.object:id()].overrides and db.storage[self.object:id()].overrides.on_offline_condlist
	if on_offline_condlist ~= nil then
		xr_logic.pick_section_from_condlist(db.actor, self.object, on_offline_condlist)
	end
-- Запоминаем позицию и активную секцию --------
if db.offline_objects[self.object:id()] then
	db.offline_objects[self.object:id()].level_vertex_id = self.object:level_vertex_id()
	db.offline_objects[self.object:id()].active_section = db.storage[self.object:id()].active_section
end
------------------------------------------------n

	db.del_obj(self.object)

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

	self:clear_callbacks()

	if self.e_index ~= nil then
		db.delete_enemy( self.e_index )
	end

	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)
	self.object:set_callback(callback.sound, nil)
end

function motivator_binder:hit_callback(obj, amount, local_direction, who, bone_index)
	-- FIXME: коллбеки неплохо было бы регистрировать в общем storage, а не посхемно...
	-- просто всегда ставить их при включении схемы и снимать при отключении.
	if(who:id()==db.actor:id()) then
		xr_statistic.set_best_weapon(amount)

--[[
		local se_obj = alife():object(obj:id())
		if se_obj and se_obj.m_smart_terrain_id ~= 65535 and amount > 0 then
			local smart_obj = alife():object(se_obj.m_smart_terrain_id)
			smart_obj:set_alarm()

			if smart_obj.base_on_actor_control ~= nil then
				smart_obj.base_on_actor_control:actor_attack()
			end
		end
]]
		-- Ищем смарты по расстоянию
		if amount > 0 then
			for k,v in pairs(sim_board.get_sim_board().smarts) do
				local smart = v.smrt 
				if smart.base_on_actor_control ~= nil then
					local level_id = game_graph():vertex(smart.m_game_vertex_id):level_id()
					local actor_level_id = game_graph():vertex(alife():actor().m_game_vertex_id):level_id()
					if level_id == actor_level_id and db.actor:position():distance_to_sqr(smart.position) <= 6400 then
						if self.object:relation(db.actor) ~= game_object.enemy then
							smart.base_on_actor_control:actor_attack()
						end
					end				
				end
			end
		end
	end
	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 bone_index ~= 15 and amount > self.object.health*100 then
		--printf("DEADLY HIT %s %s", amount, self.object.health*100)
		self.object.health = 0.15
	end


	if amount > 0 then
		printf("HIT_CALLBACK: %s amount=%s bone=%s", obj:name(), amount, tostring(bone_index))
--		self.need_relation_update = who:id() == db.actor:id()
		xr_wounded.hit_callback(obj:id())
	end
end

function motivator_binder:death_callback(victim, who)
	self:hit_callback(victim, 1, vector():set(0,0,0), who, "from_death_callback")

	xrs_dyn_music.npc_table[self.object:id()] = nil
	xr_combat_ignore.fighting_with_actor_npcs[self.object:id()] = nil
--	xrs_dyn_music.stop_combat(self.object:id())
	--' Если игрок был с квестом от механика - фейлить его
	local st = db.storage[self.object:id()]
	local npc = self.object
	stalker_generic.remove_level_spot(npc, st)
	--' end  --
	if who:id() == db.actor:id() then
		xr_statistic.inc_killed_stalkers_counter()
		xr_statistic.set_best_monster(npc)
	end

	--' Загрузка историй для лагеря.
	local known_info = utils.cfg_get_string(st.ini,st.section_logic , "known_info", self.object, false, "", nil)
	printf("xr_info from death loaded!!!")
	xr_info.loadInfo(self.object, st.ini, known_info)

	--' Принудительно обнуляем анимейшн менеджер чтобы отдетачились приатаченные объекты
	if self.st.state_mgr ~= nil then
		self.st.state_mgr.animation:set_state(nil, true)
	end
	
	if self.st.reach_task then
		xr_logic.issue_event(self.object, self.st.reach_task, "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_light.check_light(self.object)
	death_manager.drop_manager(self.object):create_release_item()
	db.delete_enemy( self.e_index )

	self:clear_callbacks()
	--' Наносим небольшой импульс вперед.
	if(actor_stats.remove_from_ranking~=nil)then
		local community = character_community(self.object)
		if community == "zombied" or
			 community == "monolith"
		then
		else
			actor_stats.remove_from_ranking(self.object:id())
		end

	end
	release_body_manager.get_release_body_manager():moving_dead_body(self.object)
end

function motivator_binder:use_callback(obj, who)
	printf("motivator_binder:use_callback(obj, who)")
	if self.object:alive() then
		inventory_upgrades.need_victim(obj)
		xr_meet.notify_on_use(obj, who)
		dialog_manager.disabled_phrases[obj:id()] = nil
		if self.st.active_section then
			xr_logic.issue_event(self.object, self.st[self.st.active_scheme], "use_callback", obj, who)
		end
	else
		utility.get_npc_money(self.object)
	end
end

function motivator_binder:update(delta)
	object_binder.update(self, delta)

	if xr_combat_ignore.fighting_with_actor_npcs[self.object:id()] and self.object:best_enemy() == nil then
		xr_combat_ignore.fighting_with_actor_npcs[self.object:id()] = nil
	end

	local object = self.object
	local object_alive = object:alive()
	local actor = db.actor

	-- Переключение логики.
	update_logic(object)


	if self.first_update == false then
		if object_alive == false then
			death_manager.drop_manager(object):create_release_item()
		end
		self.first_update = true
	end
	if time_global() - self.last_update > 1000 then
		sr_light.check_light(object)
		self.last_update = time_global()
	end

	-- Открываем двери
	if object_alive then
		local opened_doors = ph_door.try_to_open_door(self.object)
		utils.copy_table(self.opened_doors, opened_doors)
		-- Закрываем двери
		local closed_doors = ph_door.try_to_close_door(self.object, self.opened_doors)
		for id,v in pairs(closed_doors) do
			self.opened_doors[id] = nil
		end
	end

	--' Апдейт менеджера состояний тела
	if self.st.state_mgr then
		if object_alive then
			self.st.state_mgr:update()
			-- Апдейт торговли
			if self.st.state_mgr.combat == false and
				 self.st.state_mgr.alife == false --and self.st.state_mgr.planner:current_action_id() == self.st.state_mgr.operators["end"]
			then
				trade_manager.update(object)
			end
		else
			self.st.state_mgr = nil
		end
	end


	if object_alive then
		--' Апдейт саундменеджера
		xr_sound.update(object:id())

		--' Апдейт юзабилити персонажа
		xr_meet.process_npc_usability(object)
		
		-- Апдейт безсмертия
		stalker_generic.update_invulnerability(self.object)
	end




	-- Апдейт отряда
	local squad = get_object_squad(self.object)
	if squad ~= nil then
		if squad:commander_id() == self.object:id() then
			squad:update()
		end
	end

	object:info_clear()
	if object_alive then
		local active_section = db.storage[object:id()].active_section
		if active_section then
			object:info_add("section: " .. active_section)
		end
		local best_enemy = object:best_enemy()
		if best_enemy then
			object:info_add("enemy: " .. best_enemy:name())
		end
		local best_danger = object:best_danger()
		if best_danger then
			object:info_add("danger: " .. xr_danger.get_danger_name(best_danger))
		end
		object:info_add(object:name().." ["..object:team().."]["..object:squad().."]["..object:group().."]")

		if alife():object(object:id()) == nil then
			return
		end

		if squad ~= nil then
			object:info_add("squad_id: " .. squad:section_name())
			if squad.current_action ~= nil then
				local target = squad.assigned_target_id and alife():object(squad.assigned_target_id) and alife():object(squad.assigned_target_id):name()
				self.object:info_add("current_action: " .. squad.current_action.name .."["..tostring(target).."]")
			end
		end
	else
		object:set_tip_text_default()
	end
--[[
-- Полечилось в двигле...
	if self.need_relation_update then
		local squad = get_object_squad(self.object)
		local obj_relation_to_actor =  game_relations.game_relations_by_num[self.object:relation(db.actor)]
		if squad ~= nil and (game_relations.get_squad_goodwill_to_actor_by_id(squad.id) ~= obj_relation_to_actor) then
			game_relations.set_squad_goodwill(squad.id, obj_relation_to_actor)
		end
		self.need_relation_update = false
	end]]--
end

function motivator_binder:reload(section)
	--memusage.collect_info(self.object, "reload_start")
	object_binder.reload(self, section)
	--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)
	set_save_marker(packet, "save", false, "motivator_binder")
	object_binder.save(self, packet)
	xr_logic.save_obj(self.object, packet)
	trade_manager.save(self.object, packet)
--	treasure_manager.save(packet)
	xr_sound.save_npc(packet, self.object:id())
	dialog_manager.save_npc(packet, self.object:id())

	local i = 0
	for k,v in pairs(self.opened_doors) do
		i = i + 1
	end
	packet:w_u8(i)
	for k,v in pairs(self.opened_doors) do
		packet:w_u16(k)
	end


	set_save_marker(packet, "save", true, "motivator_binder")
end

function motivator_binder:load(reader)
	self.loaded = true
	set_save_marker(reader, "load", false, "motivator_binder")
	object_binder.load(self, reader)
	xr_logic.load_obj(self.object, reader)
	trade_manager.load(self.object, reader)
--	treasure_manager.load(reader)
	xr_sound.load_npc(reader, self.object:id())
	dialog_manager.load_npc(reader, self.object:id())

	local i = reader:r_u8()
	for k = 1,i do
		local key = reader:r_u16()
		self.opened_doors[key] = true
	end

	set_save_marker(reader, "load", true, "motivator_binder")
end

function motivator_binder:hear_callback(self, who_id, sound_type, sound_position, sound_power)
	if who_id == self:id() then
		return
	end
	xr_hear.hear_callback(self, who_id, sound_type, sound_position, sound_power)
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.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.abuse_base, false))
	action:add_precondition (world_property(xr_evaluators_id.wounded_exist,		false))
	action:add_precondition (world_property(xr_evaluators_id.corpse_exist,		false))
	action:add_precondition (world_property(stalker_ids.property_items,		false))
end


function update_logic(object)
	local object_alive = object:alive()
	local st = db.storage[object:id()]
	local actor = db.actor
	local st_combat = st.combat

	if st ~= nil and st.active_scheme ~= nil and object_alive then
		-- Обрабатываем Override on_combat
		local switched = false
		local manager = object:motivation_action_manager()
		if manager:initialized() and manager:current_action_id() == stalker_ids.action_combat_planner then
			local overrides = xr_logic.generic_scheme_overrides(object)
			if overrides then
				if overrides.on_combat then
					xr_logic.pick_section_from_condlist(actor, object, overrides.on_combat.condlist)
				end
				if st_combat and st_combat.logic then
					if  not xr_logic.try_switch_to_another_section(object, st_combat, actor) then
						if overrides.combat_type then
							set_combat_type(object, actor, overrides)
						end
					else
						switched = true
					end
				end

			else
				set_combat_type(object, actor, st_combat)
			end
		end

		if not switched then
			xr_logic.try_switch_to_another_section(object, st[st.active_scheme], actor)
		end
	else
		-- если нет активной секции, то тип боя взять из секции on_combat
		xr_combat.set_combat_type(object, actor, st_combat)
	end
end