----------------------------------------------------------------------
--	created:	2005/02/09
--	created:	9:2:2005   18:50
--	filename: 	x:\gamedata\scripts\xr_attendant.script
--	file path:	x:\gamedata\scripts
--	file base:	xr_attendant
--	file ext:	script
--	author:		Тундук Владимир aka Sidorovich
--
--	purpose:	схема сопровождающего.
----------------------------------------------------------------------
-- nv160717 some logic shortcuts
-- nv170330 switched to add_to_binder_q(), removed storage.actions test

-----------------------------------------------------------------------------------
-- Эвалуатор ожидания лидера
class "evaluator_leader" (property_evaluator)
--------
function evaluator_leader:__init (name, storage) super (nil, name)
	self.st = storage
	self.time = 0
end
--------
function evaluator_leader:evaluate ()
	-- Лидер уже есть
	if self.st.leader_id ~= nil then return true end

	-- Если задан story id лидера, то будем его щупать.
	-- В результате должны получить его id
	if self.st.leader ~= nil then
		local sim = alife ()
		 if sim then
			local se_obj = sim:story_object (tonumber(self.st.leader))
			if se_obj then
				if level.object_by_id(se_obj.id) ~= nil then
					self.st.leader_id = se_obj.id
					return true
				end
			end
		end
		return false
	end
	abort ("Don't use followers as patrol soldier in gulag system. Use XR_PATROL scheme")
--[[
	-- В противном случае будем требовать лидера из гулага
	local gulag = xr_gulag.get_npc_gulag (self.object)
	if gulag == nil then return false end

	local t = gulag:getJob (self.object)
	if t ~= nil and t.dependent ~= nil then
		self.st.leader_id = gulag:idNPCOnJob (t.dependent)
		if self.st.leader_id ~= nil then
			self.time = 0
			return true
		 end
	end

	if self.time == 0 then
		 self.time = device ():time_global ()
	else
		 if device ():time_global () - self.time > 10000 then
			if t ~= nil and t.dependent ~= nil then
				printf ("!!! ERROR !!! FOLLOWER %s NOT FOUND LEADER WITH JOB %s", self.object:name (), t.dependent)
			else
				printf ("!!! ERROR !!! FOLLOWER %s NOT FOUND LEADER BECAUSE LEADER NOT DEFINED", self.object:name ())
			end
			abort ("!!! ERROR !!! Leader for object %s not defined from GULAG system", self.object:name ())
			self.time = 0
			xr_gulag.resetJob (gulag, self.object)
		 end
	end
	return false
]]
end
-----------------------------------------------------------------------------------


-----------------------------------------------------------------------------------
-- Эвалуатор следования за лидером
class "evaluator_follow" (property_evaluator)
--------
function evaluator_follow:__init (name, storage) super (nil, name)
	self.st = storage
end
--------
function evaluator_follow:evaluate ()
	-- return xr_logic.is_active (self.object, self.st)
	return self.st.section == db.storage[self.object:id()].active_section -- nv170402 minor simplification: "is active"
end
-----------------------------------------------------------------------------------

-----------------------------------------------------------------------------------
-- Экшен ожидания лидера
class "action_wait" (action_base)
--------
function action_wait:__init (name, storage) super (nil, name)
	self.st = storage
	self.was_reset  = false
	self:reset_scheme()
end
--------
function action_wait:initialize ()
	action_base.initialize (self)
end
--------
function action_wait:execute ()
	action_base.execute (self)

	--printf("follower<wait.execute>: try_switch_to_another_section")
	if xr_logic.try_switch_to_another_section (self.object, self.st, actor) then -- db.actor) then -- nv170321 use global actor
		return
	end

	if not self.was_reset then
		self:reset_scheme ()
	end

end
--------
function action_wait:finalize ()
	action_base.finalize (self)
end
--------
function action_wait:reset_scheme ()
	self.was_reset = true
end
--------
function action_wait:activate_scheme ()
	self.was_reset = false
end
-----------------------------------------------------------------------------------


-----------------------------------------------------------------------------------
-- Экшен движения за лидером
class "action_follow" (action_base)
--------
function action_follow:__init (name, storage) super (nil, name)
	self.st = storage
	self.was_reset  = false
end
--------
function action_follow:initialize ()
	action_base.initialize (self)
	self:reset_scheme ()
end
--------
function action_follow:execute ()
	action_base.execute (self)

	if not self.was_reset then
	   self:reset_scheme()
	end

	--printf("follower<follow.execute>: try_switch_to_another_section")
	if xr_logic.try_switch_to_another_section (self.object, self.st, actor) then -- db.actor) then -- nv170321 use global actor
		self.st.leader_id = nil
		return
	end

	-- FIXME !!!
	--xr_sound.set_sound(self.object, self.st.sound_theme)
	--local pos = self.object:position ()
	--printf ("FOLLWER %s POSITION x = %f, y = %f, z = %f", self.object:name (), pos.x, pos.y, pos.z)
	-- проверим, а жив ли еще старшОй.
	local leader = level.object_by_id(self.st.leader_id)
	if leader == nil then
		self:free ()
		return
	end

	if self.st.stub_for_corpse == false then
		if leader == nil or leader:alive () == false then
			self.st.signals["leader_dead"] = true
			--self:free ()
			return
		end
	end

	-- проверим нахождение нас в списке
	self:check_list ()

	if self.st.leader_section == nil then
		self.st.leader_section = db.storage[self.st.leader_id].active_section
		--if self.st.leader_section ~= nil then
		--printf ("FOLLOWER %s, LEADER SCHEME  = %s", self.object:name (), self.st.leader_section)
		--end
	end

	if db.storage[self.st.leader_id].active_section ~= self.st.leader_section then
		self.st.signals["leader_section_changed"] = true
		self:free ()
		return
	end


	--получим целевую точку, дистанцию до нее и позицию.
	local vid, vdist, vpos = self:calculate_position ()
	--printf ("FOLLOWER %s HAVE A %d VERTEX ID", self.object:name (), vid)
	if self.state == 0 then
		if vdist > 1 then
			self.object:set_dest_level_vertex_id (vid)
			self.object:set_desired_position (vpos)
			local dir
			if self.st.stub_for_corpse == true then
				local pos = leader:position ()
				local cen = leader:center ()
				dir = cen:sub (pos)
				dir:normalize ()
			else
				dir = leader:direction ()
			end
			self.object:set_desired_direction (dir)
			self.object:set_detail_path_type (move.line)
			self.object:set_path_type (game_object.level_path)
			if vdist > 20 then
				state_mgr.set_state (self.object, self.st.animation_sprint)
				--printf ("FOLLOWER %s IS SPRINTED", self.object:name ())
			elseif vdist < 3 then
				state_mgr.set_state (self.object, self.st.animation_move)
				--printf ("FOLLOWER %s IS MOVED", self.object:name ())
			elseif vdist > 5 then
				state_mgr.set_state (self.object, self.st.animation_run)
				--printf ("FOLLOWER %s IS RUNNING", self.object:name ())
			end
		else
			self.state = 1
		end
	else
		if vdist > 3 then
			self.state = 0
		else
			-- FIXME (remove stub for corpse)
			if self.st.stub_for_corpse == true then
				if leader == nil or leader:alive () == false then
					self.st.signals["leader_dead"] = true
					--self:free ()
					return
				end
			end

			if xr_meet.is_meet (leader) == true then
				--printf ("LEADER IN MEET STATE")
				state_mgr.set_state (self.object, self.st.state_if_leader_in_meet, nil, nil, actor) -- db.actor) -- nv170321 use global actor
			else
				--printf ("LEADER NOT IN MEET STATE")
				state_mgr.set_state (self.object, "idle")
			end
		end
	end

end
--------
function action_follow:finalize ()
	action_base.finalize (self)
	if self.index ~= nil then
		--table.remove (db.storage[self.st.leader_id].followers, self.index)
		self.index = nil
	end
	--xr_sound.set_sound(self.object, nil)
end
--------
function action_follow:calculate_position ()
	-- получим от лидера ноду и направление
	local leader = level.object_by_id(self.st.leader_id)
	local l_vid = leader:level_vertex_id ()
	local l_dir = leader:direction ()
	-- а также количество народу.
	local size = #db.storage[self.st.leader_id].followers

	local dir = vector ():set (0, 0, 0)
	local dist = self.st.distance
	-- если идем цепью, то одно, если обычным образом, то другое
	if self.st.formation == true then
		dir = vector_rotate_y (l_dir, 90.0)
		dir.y = 0
		dir:normalize ()
		local half = size / 2
		if bit_and (self.index, 1) == 1 then
			dir.x = -dir.x
			dir.z = -dir.z
		end
		dist = self.st.distance * half
	else
		if size == 1 then
				dir = vector_rotate_y (l_dir, 179.0)
		else
				dir = vector_rotate_y (l_dir, 90.0 + (180 / (size + 1)) * self.index)
		end
	end

	if self.object:accessible (l_vid) == false then
		l_vid = self.object:level_vertex_id ()
		if self.object:accessible (l_vid) == false then
			l_vid = self.object:nearest_accessible_position ()
		else
			l_dir = level.vertex_position (l_vid)
			return l_vid, l_dir:distance_to (self.object:position ()), l_dir
		end
	end

	local need_id = self.object:vertex_in_direction (l_vid, dir, dist)
	if need_id == -1 then
		need_id = l_vid
	else
		if self.object:accessible (need_id) == false then
			abort ("FOLLOWER %s, ACCESSIBLE VERTEX ID ERROR !!!", self.object:name ())
		end
	end

	if self.object:accessible (need_id) == false then
		need_id = leader:level_vertex_id ()
		if self.object:accessible (need_id) == false then
			printf ("!!!ERROR!!! Follower %s can't found accessible vertex id !!!", self.object:name ())
			need_id = self.object:nearest_accessible_position ()
			--abort ("ERROR: can't find vertex id for object %s", self.object:name ())
		end
	end

	l_dir = level.vertex_position (need_id)
	return need_id, l_dir:distance_to (self.object:position ()), l_dir
end
--------
function action_follow:check_list ()
	-- возьмем хранилище лидера
	local storage = db.storage[self.st.leader_id]
	-- и наш айдишник
	local id = self.object:id ()
	-- если у лидера нет таблицы сопровождающих, то создадим ее
	-- и занесем туда себя любимого под номером 1
	if storage.followers == nil then
		-- storage.followers = {}
		-- table.insert (storage.followers, self.object:id ())
		storage.followers = { id }
		--printf ("SET OBJECT %s AS FOLLOWER 1", self.object:name ())
		self.index = 1
		return
	end
	-- иначе шаримся в таблице и ищем себя
	for a = 1, #storage.followers, 1 do
		if storage.followers[a] == id then
			self.index = a
			return
		end
	end
	-- не нашли... добавим.
	-- table.insert (storage.followers, self.object:id ())
	storage.followers[#storage.followers + 1] = id
	self.index = #storage.followers
	--printf ("SET OBJECT %s AS FOLLOWER %d", self.object:name (), self.index)
end
--------
function action_follow:free ()
	-- если мы под гулагом, то запросим другую работу, иначе прямая дорога в logic
	local gulag = xr_gulag.get_npc_gulag (self.object)

	if gulag ~= nil then
		if self.st.dont_change_job == false then
			-- printf ("!!!FOLLOWER %s!!! LEADER SECTION CHANGED - NEED NEW JOB FROM GULAG", self.object:name ())
			xr_gulag.resetJob (self.object)
		end
	else
		--printf("follower<follow.free>: try_switch_to_another_section")
		xr_logic.try_switch_to_another_section (self.object, self.st, actor) -- db.actor) -- nv170321 use global actor
	end
end
--------
function action_follow:reset_scheme ()
	self.was_reset = true
	self.vertex_id = nil
	self.index = nil
	self.state = 0 -- 0 - идем, 1 - стоим
	-- self.st.signals = {}
	-- self.st.signals["leader_section_changed"] = false
	-- self.st.signals["leader_dead"] = false
	-- self.st.signals["leader_section_changed"] = false --nv170319 sheesh
	-- self.st.signals["leader_dead"] = false -- ditto
	self.st.signals = {leader_section_changed = false, leader_dead = false}
	--xr_sound.set_sound(self.object, self.st.sound_theme)
end
--------
function action_follow:activate_scheme ()
	self.was_reset = false
end
-----------------------------------------------------------------------------------


-- nv150629 hoisted constant data out of function, reworked for efficiency
-- local operators		= {}
-- local properties	= {}
-- properties["attendant_leader"]	  =   xr_evaluators_id.sidor_attendant_eva + 0
-- properties["attendant_follow"]	  =   xr_evaluators_id.sidor_attendant_eva + 1

-- operators["attendant_wait"] 		=   xr_actions_id.sidor_attendant_act + 0
-- operators["attendant_follow"]	   =   xr_actions_id.sidor_attendant_act + 1

local prop_attendant_leader = xr_evaluators_id.sidor_attendant_eva + 0
local prop_attendant_follow = xr_evaluators_id.sidor_attendant_eva + 1
local operator_attendant_wait = xr_actions_id.sidor_attendant_act + 0
local operator_attendant_follow = xr_actions_id.sidor_attendant_act + 1
local xr_actions_id_alife = xr_actions_id.alife
local stalker_ids_property_alive = stalker_ids.property_alive
local stalker_ids_property_danger = stalker_ids.property_danger
local stalker_ids_property_enemy = stalker_ids.property_enemy
-- local wp_stalker_ids_property_alive_true = world_property(stalker_ids.property_alive, true)
-- local wp_stalker_ids_property_danger_false = world_property(stalker_ids.property_danger, false)
-- local wp_stalker_ids_property_enemy_false = world_property(stalker_ids.property_enemy, false)
-- local wp_prop_attendant_leader_false = world_property(prop_attendant_leader, false)
-- local wp_prop_attendant_leader_true = world_property(prop_attendant_leader, true)
-- local wp_prop_attendant_follow_true = world_property(prop_attendant_follow, true)
-- local wp_prop_attendant_follow_false = world_property(prop_attendant_follow, false)

-- function  add_to_binder(npc, ini, scheme, section, storage)
function add_to_binder_q(npc, storage)
	local manager = npc:motivation_action_manager ()

	manager:add_evaluator (prop_attendant_leader, this.evaluator_leader ("attendant_leader", storage))
	manager:add_evaluator (prop_attendant_follow, this.evaluator_follow ("attendant_follow", storage))

	local action = this.action_wait ("attendant_wait", 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(stalker_ids_property_danger, false))
	action:add_precondition		(world_property(prop_attendant_leader, false))
	action:add_effect (world_property(prop_attendant_leader, true))
	manager:add_action (operator_attendant_wait, action)
	--nv160831 imported xr_logic.subscribe_action_for_events() logic
	--xr_logic.subscribe_action_for_events (npc, storage, action)
	-- if not storage.actions then storage.actions = {} end -- nv170330 now precondition
	storage.actions[ action ] = true

	action = this.action_follow ("attendant_follow", 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(stalker_ids_property_danger, false))
	action:add_precondition		(world_property(prop_attendant_leader, true))
	action:add_precondition		(world_property(prop_attendant_follow, true))
	action:add_effect (world_property(prop_attendant_follow, false))
	manager:add_action (operator_attendant_follow, action)
	--nv160831 imported xr_logic.subscribe_action_for_events() logic
	--xr_logic.subscribe_action_for_events (npc, storage, action)
	-- if not storage.actions then storage.actions = {} end -- this was already checked above
	storage.actions[ action ] = true

	-- --xr_logic.subscribe_action_for_events (npc, storage, action)

	action = manager:action (xr_actions_id_alife)
	action:add_precondition		(world_property(prop_attendant_follow, false))

end

--[[ -- nv160717 some logic shortcuts
function set_scheme (npc, ini, scheme, section, gulag_name)
	local st = xr_logic.assign_storage_and_bind (npc, ini, scheme, section)
	st.logic = xr_logic.cfg_get_switch_conditions (ini, section, npc)

	st.leader = utils.cfg_get_string (ini, section, "leader",  npc, false, "")
	st.leader_section = nil
	st.animation_move = utils.cfg_get_string (ini, section, "anim_walk",  npc, false, "", "patrol")
	st.animation_run = utils.cfg_get_string (ini, section, "anim_run",  npc, false, "", "run")
	st.animation_sprint = utils.cfg_get_string (ini, section, "anim_sprint",  npc, false, "", "sprint")
	st.formation = utils.cfg_get_bool (ini, section, "formation_line", npc, false, false)
	st.distance = utils.cfg_get_number (ini, section, "distance", npc, false)
	if st.distance == nil then
	 if st.formation == false then
		st.distance = 1.5
	 else
		st.distance = 5
	end
	end
	st.sound_theme = utils.cfg_get_string (ini, section, "snd",  npc, false, "", "state")
	if st.sound_theme == "silent" then st.sound_theme = nil end
	st.state_if_leader_in_meet = utils.cfg_get_string (ini, section, "state_if_leader_in_meet", npc, false, "", "idle")
	st.stub_for_corpse = utils.cfg_get_bool (ini, section, "stub_for_corpse", npc, false, false)
	st.dont_change_job = utils.cfg_get_bool (ini, section, "dont_change_job", npc, false, false)
end
--]]

function set_scheme (npc, ini, scheme, section, gulag_name)
	-- for non-mandatory field assignments with possible default value
	local function get_field_string(field, default)
		return (ini:line_exist(section, field) and ini:r_string(section, field)) or default
	end
	local st = xr_logic.assign_storage_and_bind (npc, ini, scheme, section)
	st.logic = xr_logic.cfg_get_switch_conditions (ini, section, npc)

	if section and ini:section_exist(section) then -- let's do common tests only once
		st.leader = get_field_string("leader")
		st.animation_move = get_field_string("anim_walk", "patrol")
		st.animation_run = get_field_string("anim_run", "run")
		st.animation_sprint = get_field_string("anim_sprint", "sprint")
		st.formation = (ini:line_exist(section, "formation_line") and ini:r_bool(section, "formation_line")) or false
		st.distance = (ini:line_exist(section, "distance") and ini:r_float(section, "distance")) or nil
		st.sound_theme = get_field_string("snd", "state")
		if st.sound_theme == "silent" then st.sound_theme = nil end
		st.state_if_leader_in_meet = get_field_string("state_if_leader_in_meet", "idle")
		st.stub_for_corpse = (ini:line_exist(section, "stub_for_corpse") and ini:r_bool(section, "stub_for_corpse")) or false
		st.dont_change_job = (ini:line_exist(section, "dont_change_job") and ini:r_bool(section, "dont_change_job")) or false
	else
		st.leader = nil
		st.animation_move = "patrol"
		st.animation_run = "run"
		st.animation_sprint = "sprint"
		st.formation = false
		st.distance = nil
		st.sound_theme = "state"
		st.state_if_leader_in_meet = "idle"
		st.stub_for_corpse = false
		st.dont_change_job = false
	end
	st.leader_section = nil
	if st.distance == nil then
		if st.formation == false then
			st.distance = 1.5
		else
			st.distance = 5
		end
	end
end