local SMART_SECTION_NAME = "smart_terrains"
local JOB_POSITION_THRESHOLD = 200
local DEATH_IDLE_TIME = 10*60 -- секунд

local smart_terrains_by_name = {}

local path_fields = { "path_walk", "path_main", "path_home", "center_point" }

local valid_territory = {
	default = true,
	base = true,
	resource = true,
	territory = true
}

--' Проверка, что нпс подходит работе
local function job_avail_to_npc(npc_info, job_info, smart)
	--printf("job_avail_to_npc %s job %s smart %s", npc_info.se_obj:name(), tostring(job_info.job_id), smart:name())
	local job = smart.job_data[job_info.job_id]
	if job ~= nil then
		job = job.section
	end

	if smart.dead_time[job_info.job_id] ~= nil then
		return false
	end

	-- Проверка условия "монстровости"
	if job_info._precondition_is_monster ~= nil and job_info._precondition_is_monster ~= npc_info.is_monster then
		return false
	end

	-- Проверка условия текущего сквад экшна
	if job_info._current_squad_action ~= nil then
		local board = smart.board
		local squad = npc_info.se_obj.squad_id
		if squad == nil then
			return false
		end
		local squad_action = board.squads[squad] and board.squads[squad].current_action
		if squad_action == nil then
			return false
		end
		if job_info._current_squad_action ~= squad_action.name then
			return false
		end

		-- Проверка атакуемой цели
		if job_info._squad_attack_point ~= nil then
			local attack_point = smart.board.smarts[squad_action.dest_smrt_id].smrt:name()
			if job_info._squad_attack_point ~= attack_point then
				return false
			end
		end
	end

	--' Проверяем подходит ли нпс по предикату
	if job_info._precondition_function ~= nil then
		if not job_info._precondition_function(npc_info.se_obj, smart, job_info._precondition_params, npc_info) then
			return false
		end
	end
	return true
end


-- Итерируемся по НПС, начинаем со свободных нпс, потом НПС на низкоприоритетных работах, потом на высокоприоритетных.
-- Для каждого конкретного НПС ищем работу.
-- Отсеиваем в поиске работы, приоритет которых ниже, чем у текущей.
local function job_iterator(jobs, npc_data, selected_job_prior, smart)
	--printf("  iterate")
	-- итерируемся по работам
	local current_job_prior = selected_job_prior
	local selected_job_id = nil
	local selected_job_link = nil
	for k,v in pairs(jobs) do
		-- Если приоритет у проверяемой работы ниже, чем приоритет текущей выбранной работы НПС - завершаем выполнение
		if current_job_prior > v._prior then
			return selected_job_id, current_job_prior, selected_job_link
		end
		-- Проверяем, может ли НПС занять данную работу
		if job_avail_to_npc(npc_data, v, smart) then
			-- Это работа-кластер или работа-описание.
			if v.job_id == nil then
				-- Вызываем рекурсивно себя для списка работ кластера
				selected_job_id, current_job_prior, selected_job_link = job_iterator(v.jobs, npc_data, selected_job_prior, smart)
			else
				-- Если работа пустая или ее занимаем мы сами - выбираем ее.
				--printf("    job %s section %s npc_id %s", v.job_id, self.job_data[v.job_id].section, tostring(v.npc_id))
				if v.npc_id == nil then
					return v.job_id, v._prior, v
				elseif v.job_id == npc_data.job_id then
					return v.job_id, v._prior, v
				end
			end
		end
	end
	return selected_job_id, current_job_prior, selected_job_link
end

----------------------------------------------------------------------------------------------------------------------
-- Класс "se_smart_terrain". Обеспечивает поддержку smart terrain в ОФЛАЙНЕ.
----------------------------------------------------------------------------------------------------------------------
class "se_smart_terrain" (cse_alife_smart_zone)
function se_smart_terrain:__init(section) super(section)
	self.initialized = false
	self.b_registred = false

	self.npc_to_register = {}
	self.npc_by_job_section = {}
	self.dead_time = {}

	-- Таблица для хранения зарегистренных НПС
	self.npc_info  = {}
end
function se_smart_terrain:on_before_register()
	cse_alife_smart_zone.on_before_register(self)
	self.board = sim_board.get_sim_board()
	self.board:register_smart(self)
	self.smart_level	= alife():level_name(game_graph():vertex(self.m_game_vertex_id):level_id())
	--printf("SMARTLEVEL %s level %s", self:name(), tostring(self.smart_level))
end
-- регистрация объекта в симуляторе.
-- вызывается симулятором.
function se_smart_terrain:on_register()
	cse_alife_smart_zone.on_register(self)

	printf("register smart %s", self:name())

	self.board:build_cross_table(self)
	self:show()

	if self.combat_manager == nil then
		self.combat_manager = sim_combat.CCombat_manager(self, self.board)
	end

	smart_terrains_by_name[self:name()] = self
	self.b_registred = true

	self:load_jobs()

	self.board:init_smart(self)

	if self.need_init_npc == true then
		self.need_init_npc = false
		self:init_npc_after_load()
	end

	-- Регистрим персонажей, которые добавили до регистрации смарта. (отложенный список)
	self:register_delayed_npc()

	self.check_time = time_global()
end
-- анрегистрация объекта в симуляторе.
-- вызывается симулятором.
function se_smart_terrain:on_unregister()
	cse_alife_smart_zone.on_unregister(self)
	self.board:unregister_smart(self)
	smart_terrains_by_name[self:name()] = nil
end
-- чтение custom data.
function se_smart_terrain:read_params()
	local ini  = self:spawn_ini()
	local sect = "smart_terrain"

	if not ini:section_exist( sect ) then
		printf( "[smart_terrain %s] no configuration!", self:name() )
		self.disabled = true
		return
	end

	--' Вычитка симуляционных свойств
	self.sim_type		= utils.cfg_get_string(ini, "smart_terrain", "sim_type", self, false, "", "default")

	if valid_territory[self.sim_type] == nil then
		abort("Wrong sim_type value [%s] in smart [%s]", self.sim_type, self:name())
	end

	self.squad_capacity	= utils.cfg_get_number(ini, "smart_terrain", "squad_capacity", self, false, 1)
	self.player_name	= "none"  --' Хранится имя игрока, владеющего данным смартом. Для оптимизации.
	self.squad_id		= utils.cfg_get_number(ini, "smart_terrain", "squad_id", self, false, 0)
	self.respawn_sector = utils.cfg_get_string(ini, "smart_terrain", "respawn_sector", self, false, "")
	self.respawn_radius  = utils.cfg_get_number(ini, "smart_terrain", "respawn_radius", self, false, 150)
	if self.respawn_sector ~= nil then
		if self.respawn_sector == "default" then
			self.respawn_sector = "all"
		end
		self.respawn_sector = xr_logic.parse_condlist(nil, sect, "respawn_sector", self.respawn_sector)
	end


	self.important_point = utils.cfg_get_bool(ini, "smart_terrain", "important_point", self, false)
	self.mutant_lair 		= utils.cfg_get_bool(ini, "smart_terrain", "mutant_lair", self, false)
	self.no_mutant 			= utils.cfg_get_bool(ini, "smart_terrain", "no_mutant", self, false)
	if self.no_mutant == true then
		printf("Found no mutant point %s", self:name())
	end
	self.forbidden_point 	= utils.cfg_get_string(ini, "smart_terrain", "forbidden_point", self, false, "")

	--' Рестрикторы для симуляции
	self.def_restr		= utils.cfg_get_string(ini, "smart_terrain", "def_restr", self, false, "", nil)
	self.att_restr		= utils.cfg_get_string(ini, "smart_terrain", "att_restr", self, false, "", nil)

	self.spawn_point	= utils.cfg_get_string(ini, "smart_terrain", "spawn_point", self, false, "")

	self.sim_avail		= utils.cfg_get_string(ini, "smart_terrain", "sim_avail", self, false, "")
	if self.sim_avail ~= nil then
		self.sim_avail = xr_logic.parse_condlist(nil, sect, "sim_avail", self.sim_avail)
	end

	self.respawn     	= utils.cfg_get_string(ini, "smart_terrain", "respawn", self, false, "", nil)

		self.surge_hide_avaliable = utils.cfg_get_bool(ini, "smart_terrain", "surge_hide_avaliable", self, false, false)
		local hides = utils.cfg_get_string(ini, "smart_terrain", "actor_hides", self, false, "")
	if(hides~=nil) then
		self.actor_hides = utils.parse_names(hides)
	end
end

--*******************************************************
-- МЕТОДЫ ДЛЯ РАБОТЫ С НПС
--*******************************************************
-- заполнить информацию о персонаже
-- у монстров нету метода profile_name()
function se_smart_terrain:fill_npc_info(obj)
	local npc_info = {}
	printf("filling npc_info for obj [%s]", tostring(obj:name()))

	local is_stalker = IsStalker(obj)
	npc_info.se_obj			= obj
	npc_info.is_monster		= not is_stalker
	npc_info.need_job		= "nil"	-- Специально для смены гвардов. Указывает на какую работу хочет данный чувак.
	npc_info.job_prior		= -1
	npc_info.job_id			= -1
	npc_info.begin_job		= false

	if is_stalker then
		npc_info.stype = modules.stype_stalker
	else
		npc_info.stype = modules.stype_mobile
	end

	return npc_info
end

-- добавить npc в smart terrain.
function se_smart_terrain:register_npc(obj)
	printf("[smart_terrain %s] register called obj=%s", self:name(), obj:name())

	if self.b_registred == false then
		table.insert(self.npc_to_register, obj)
		return
	end

	self.npc_info[obj.id] = self:fill_npc_info(obj)

	obj.m_smart_terrain_id = self.id


	-- Затычка на случай если мы регистримся в смарт, из которого только что сами вынесли всех врагов.
	self.dead_time = {}

	-- тут надо найти чуваку работу
	self:update_jobs(obj)
end
-- Регистрация НПС в список отложенных. Осуществляется на загрузке или на регистрации НПС, пока не зарегистрен смарт
function se_smart_terrain:register_delayed_npc()
	for k,v in pairs(self.npc_to_register) do
		self:register_npc(v)
	end
	self.npc_to_register = {}
end
-- отпустить npc
function se_smart_terrain:unregister_npc(obj)
	--callstack()
	printf("smart [%s] unregister npc [%s]", self:name(), obj:name())

	local n = self.npc_info[obj.id]

	if n == nil then
		abort("self.npc_info[obj.id] = nil !!! obj.id=%d", obj.id)
	end

	-- TODO: Тут надо выгнать чувака с занимаемой им работы
	if( self.npc_info[obj.id] ~= nil ) then
		if( self.npc_info[obj.id].job_link ~= nil and
			self.npc_info[obj.id].job_link.npc_id ~= nil ) then
			self.npc_info[obj.id].job_link.npc_id = nil
		end
		self.npc_info[obj.id] = nil
	end

	obj:clear_smart_terrain()
end
-- Убрать убитого
function se_smart_terrain:clear_dead(obj)
	local n = self.npc_info[obj.id]

	if n == nil then
		abort("self.npc_info[obj.id] = nil !!! obj.id=%d", obj.id)
	end

	-- Устанавливаем таймер смерти на работе
	self.dead_time[self.npc_info[obj.id].job_id] = game.get_game_time()

	if( self.npc_info[obj.id] ~= nil ) then
		if( self.npc_info[obj.id].job_link ~= nil and
			self.npc_info[obj.id].job_link.npc_id ~= nil ) then
			self.npc_info[obj.id].job_link.npc_id = nil
		end
		self.npc_info[obj.id] = nil
	end

	obj:clear_smart_terrain()
end
-- выдать объекту задание.
function se_smart_terrain:task(obj)
	local job_data = self.job_data[self.npc_info[obj.id].job_id]

	if not job_data then
		abort("[smart_terrain %s] task: obj=%s job_path=nil, CLEARING. job_id = %s", self:name(), obj:name(), tostring(self.npc_info[obj.id].job_id))
		self:unregister_npc(obj) --'Затычка против вылета.
		return CALifeSmartTerrainTask("simulation_default_path", 0)
	end

	obj.attack_position = job_data.attack_position

	--printf("SEETING TASK PATH %s = %s(%s)", obj:name(), job_data.alife_path, job_data.alife_point)
	return CALifeSmartTerrainTask(job_data.alife_path, job_data.alife_point)
end


--*******************************************************
-- Функции для работы с работами
--*******************************************************
-- Загрузка работ (из gulag_general)
function se_smart_terrain:load_jobs()
	--printf("LOAD JOBS %s", self:name())
	-- Загружаем иерархию работ
	self.jobs = gulag_general.load_job(self)

	-- Загружаем ltx работ.
	self.ltx, self.ltx_name = xr_gulag.loadLtx(self:name())
	-- Сортируем всю иерархию по уменьшению приоритета
	-- Рекурсивная функция сортировки
	local function sort_jobs(jobs)
		for k,v in pairs(jobs) do
			if v.jobs ~= nil then
				sort_jobs(v.jobs)
			end
		end
		table.sort(jobs, function(a,b) return a._prior > b._prior end )
	end

--	printf("before sort")
--	store_table(self.jobs)

	sort_jobs(self.jobs)

--	printf("after sort")
--	store_table(self.jobs)


	-- Надо сделать постобработку работ. Проинитить все неиниченные поля
	-- Для более быстрого доступа нужно вычленить параметры работ в отдельную таблицу вида:
	--self.job_data[job_id] = {}
	local id = 0
	self.job_data = {}
	local function get_jobs_data(jobs)
		for k,v in pairs(jobs) do
			if v.jobs ~= nil then
				get_jobs_data(v.jobs)
			else
				if v.job_id == nil then
					print_table(self.jobs)
					abort("Incorrect job table")
				end
				self.job_data[id] = v.job_id
				if v._attack_job == true then
					self.job_data[id].attack_position = true
				end
				self.job_data[id]._prior = v._prior -- Кешируем для проверки
				v.job_id = id
				id = id + 1
			end
		end
	end

	get_jobs_data(self.jobs)
	-- Пробегаемся по работам и высчитываем для каждой работы alife_path
	for k,v in pairs(self.job_data) do
		local section = v.section
		local ltx = v.ini_file or self.ltx
		if not ltx:line_exist(section, "active") then
			abort("gulag: ltx=%s  no 'active' in section %s", self.ltx_name, section)
		end
		local active_section = ltx:r_string(section, "active")
		local path_field
		for i,vv in pairs(path_fields) do
			if ltx:line_exist(active_section, vv) then
				path_field = vv
				break
			end
		end
		if not path_field then
			abort("gulag: ltx=%s, there is no path in section %s", self.ltx_name, active_section)
		end

		if path_field == "center_point" then --' TODO убрать затык
			local path_name = "_" .. ltx:r_string(active_section, path_field)

			if v.prefix_name ~= nil then
				path_name = v.prefix_name .. path_name
			else
				path_name = self:name() .. path_name
			end

			if level.patrol_path_exists(path_name .. "_task") then
				v.alife_path = path_name .. "_task"
			else
				v.alife_path = path_name
			end

		else
			if v.prefix_name ~= nil then
				v.alife_path = v.prefix_name .. "_" .. ltx:r_string(active_section, path_field)
			else
				v.alife_path = self:name() .. "_" .. ltx:r_string(active_section, path_field)
			end
		end

		local patrol = patrol(v.alife_path)
		--printf("5a %s", tostring(v.alife_path))
		v.game_vertex_id = patrol:game_vertex_id(0)
		--printf("5b")
		v.level_id		 = game_graph():vertex(v.game_vertex_id):level_id()
		--printf("5c")
		v.position       = patrol:point(0)
		--printf("5d")

		-- Высчитываем точку, на которую нужно отправлять в оффлайне
		local parsed_path = utils.path_parse_waypoints(v.alife_path)
		v.alife_point = 0
		local need_check = false
		for kk,vv in pairs(parsed_path) do
			if vv.sig == "arrive_to_wait" then
				v.alife_point = kk
				need_check = true
				break
			end
		end

		if v.attack_position == true then
			v.attack_position = patrol:point(v.alife_point)
		end

--[[
		if v._prior == 55 then
			-- Проверка валидности сигнала
			if need_check == false then
				abort("Incorrect attack path %s", v.alife_path)
			else
				-- Дебаговая проверка. Проверяем расстояние между вершиной патрульного пути и графпоинтом, которому она принадлежит.
				local arrive_vertex_id = patrol:game_vertex_id(v.alife_point)
				local arrive_vertex_position = game_graph():vertex(arrive_vertex_id):level_point()
				if arrive_vertex_position:distance_to_sqr(v.attack_position) > 25 then
					printf("graph [%s][%s][%s] point [%s][%s][%s]",
							arrive_vertex_position.x,
							arrive_vertex_position.y,
							arrive_vertex_position.z,
							v.attack_position.x,
							v.attack_position.y,
							v.attack_position.z)
					abort("!There is no graph in 'arrive_to_wait' point ["..v.alife_path.."] num [%s]", tostring(v.alife_point))
				end
			end
		end
]]
	end
end

-- Апдейт работ смарттеррейна.
-- Если передается object, то значит нужно найти только для него
function se_smart_terrain:update_jobs(object)
	-- Сортируем НПС по увеличению приоритета занимаемой работы
	if object ~= nil then
		--printf("UPDATE NPC %s", object:name())
		local npc_info = self.npc_info[object.id]
		-- Выбираем работу
		local selected_job_id, selected_job_prior, selected_job_link = job_iterator(self.jobs, npc_info, 0, self)
		if selected_job_id == nil then
			--moddified by clp, this could handle the CTD caused by insufficient jobs
			--print_table(self.jobs)
			--abort("Insufficient smart_terrain jobs %s", self:name())--clp
			object:clear_smart_terrain()--clp
			return--clp
		end

		-- Назначаем работу
		--if selected_job_id ~= nil and selected_job_prior > npc_info.job_prior then
		if selected_job_id ~= npc_info.job_id and selected_job_link ~= nil then
			-- Установить себе выбранную работу
			--printf("NPC %s FOUND JOB %s SECTION %s", npc_info.se_obj:name(), selected_job_id, self.job_data[selected_job_link.job_id].section)
			-- Если НПС был на работе - выгоняем его с нее.
			if npc_info.job_link ~= nil then
				self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = nil
				npc_info.job_link.npc_id = nil
			end

			selected_job_link.npc_id = npc_info.se_obj.id
			self.npc_by_job_section[self.job_data[selected_job_link.job_id].section] = selected_job_link.npc_id

			npc_info.job_id = selected_job_link.job_id
			npc_info.job_prior = selected_job_link._prior
			npc_info.begin_job = false
			-- сохраняем ссылку на работу, для облегчения удаления
			npc_info.job_link = selected_job_link

			-- завершаем текущую работу
			local obj_storage = db.storage[npc_info.se_obj.id]
			if obj_storage ~= nil then
				xr_logic.switch_to_section(obj_storage.object, self.ltx, "nil")
				--xr_logic.activate_by_section(obj_storage.object, self.ltx, "nil", self:name(), false)
			end
		end

		if npc_info.begin_job ~= true then
			-- Проверяем, дошел ли персонаж до работы (то есть может ли он начать ее выполнение)
			local job_data = self.job_data[npc_info.job_id]
			--if job_data == nil then
				--printf("Couldnt find job, id %s", tostring(npc_info.job_id))
				--print_table(self.jobs)
			--end
			if self:distance_to_job_location(npc_info.se_obj, job_data) < JOB_POSITION_THRESHOLD then
				-- Начинаем выполнять работу
				--printf("[smart_terrain %s] gulag: beginJob: obj=%s job= %s", self:name(), npc_info.se_obj:name(), job_data.section)

				npc_info.begin_job = true
				if npc_info.stype == modules.stype_mobile then
					npc_info.se_obj:smart_terrain_task_activate()
				end

				local obj_storage = db.storage[npc_info.se_obj.id]
				if obj_storage ~= nil then
					self:setup_logic(obj_storage.object)
				end
			end
		end
		return
	end

	--printf("UPDATE JOBS %s", self:name())
	table.sort(self.npc_info, function(a,b) return a.job_prior < b.job_prior end )
	for k,v in pairs(self.npc_info) do
		-- Выбираем работу
		local selected_job_id, selected_job_prior, selected_job_link = job_iterator(self.jobs, v, 0, self)
		-- Назначаем работу
		--if selected_job_id ~= nil and selected_job_prior > v.job_prior then
		if selected_job_id ~= v.job_id and selected_job_link ~= nil then
			-- Установить себе выбранную работу
			--printf("NPC %s FOUND JOB %s SECTION %s", v.se_obj:name(), selected_job_id, self.job_data[selected_job_link.job_id].section)
			-- Если НПС был на работе - выгоняем его с нее.
			if v.job_link ~= nil then
				self.npc_by_job_section[self.job_data[v.job_link.job_id].section] = nil
				v.job_link.npc_id = nil
			end

			selected_job_link.npc_id = v.se_obj.id
			self.npc_by_job_section[self.job_data[selected_job_link.job_id].section] = selected_job_link.npc_id

			v.job_id = selected_job_link.job_id
			v.job_prior = selected_job_link._prior
			v.begin_job = false
			-- сохраняем ссылку на работу, для облегчения удаления
			v.job_link = selected_job_link

			-- завершаем текущую работу
			local obj_storage = db.storage[v.se_obj.id]
			if obj_storage ~= nil then
				xr_logic.switch_to_section(obj_storage.object, self.ltx, "nil")
				--xr_logic.activate_by_section(obj_storage.object, self.ltx, "nil", self:name(), false)
			end
		end

		if v.begin_job ~= true then
			-- Проверяем, дошел ли персонаж до работы (то есть может ли он начать ее выполнение)
			local job_data = self.job_data[v.job_id]
			if self:distance_to_job_location(v.se_obj, job_data) < JOB_POSITION_THRESHOLD then
				-- Начинаем выполнять работу
				--printf("[smart_terrain %s] gulag: beginJob: obj=%s job= %s", self:name(), v.se_obj:name(), job_data.section)

				v.begin_job = true
				if v.stype == modules.stype_mobile then
					v.se_obj:smart_terrain_task_activate()
				end

				local obj_storage = db.storage[v.se_obj.id]
				if obj_storage ~= nil then
					self:setup_logic(obj_storage.object)
				end
			end
		end
	end
end
-- настроить логику для объекта, который в онлайне.
function se_smart_terrain:setup_logic(obj)
	--printf("setup npc logic %s", obj:name())
--	callstack()
	local npc_data = self.npc_info[obj:id()]
	if npc_data == nil then return end--clp
	local job = self.job_data[npc_data.job_id]
	if job == nil then return end--clp
	local ltx = job.ini_file or self.ltx
	local ltx_name = job.ini_path or self.ltx_name

	xr_logic.configure_schemes(obj, ltx, ltx_name, npc_data.stype, job.section, job.prefix_name or self:name())

	local sect = xr_logic.determine_section_to_activate(obj, ltx, job.section, db.actor)
	if utils.get_scheme_by_section(job.section) == "nil" then
		abort("[smart_terrain %s] section=%s, don't use section 'nil'!", self:name(), sect)
	end


	xr_logic.activate_by_section(obj, ltx, sect, job.prefix_name or self:name(), false)
end
-- получить работу, которую занимает объект
function se_smart_terrain:getJob(obj_id)
	return self.job_data[self.npc_info[obj_id].job_id]
end
-- Расстояние до работы
function se_smart_terrain:distance_to_job_location(obj, job)
	if( job == nil ) then return 999999999 end --added by clp
	local obj_gv, obj_pos

	local storage = db.storage[obj.id]

	if storage == nil then
		obj_gv, obj_pos = game_graph():vertex(obj.m_game_vertex_id), obj.position
	else
		local obj = db.storage[obj.id].object
		obj_gv, obj_pos = game_graph():vertex(obj:game_vertex_id()), obj:position()
	end

	local job_gv = game_graph():vertex(job.game_vertex_id)

	if obj_gv:level_id() == job_gv:level_id() then
		return obj_pos:distance_to(job.position)
	else
		return 10000
	end
end
-- Получение персонажа, который занимает указанную работу.
function se_smart_terrain:idNPCOnJob(job_name)
	return self.npc_by_job_section[job_name]
end
function se_smart_terrain:switch_to_desired_job(npc)
	-- Берем текущую работу НПС
	local npc_id = npc:id()
	local npc_info = self.npc_info[npc_id]

	--printf("***** %s -> %s", npc:name(), tostring(npc_info.need_job))
	local changing_npc_id = self.npc_by_job_section[npc_info.need_job]
	--printf("changing_npc_id %s", tostring(changing_npc_id))

	if changing_npc_id == nil then
		-- Мы не нашли с кем меняться, просто ресетим себя
		self.npc_info[npc_id].job_link = nil
		self.npc_info[npc_id].job_id = -1
		self.npc_info[npc_id].job_prior = -1
		self:update_jobs(self.npc_info[npc_id].se_obj)

		--print_table(self.npc_by_job_section)
		--abort("ERROR during channging NPC")
		return
	end

	if self.npc_info[changing_npc_id] == nil then
		-- Мы не нашли с кем меняться, просто ресетим себя
		self.npc_info[npc_id].job_link = nil
		self.npc_info[npc_id].job_id = -1
		self.npc_info[npc_id].job_prior = -1
		self:update_jobs(self.npc_info[npc_id].se_obj)

		--print_table(self.npc_by_job_section)
		--abort("ERROR during channging NPC")
		return
	end

	local desired_job = self.npc_info[changing_npc_id].job_id

	-- Переключаем НПС на желаемую работу
	if npc_info.job_link ~= nil then
		self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = nil
		npc_info.job_link.npc_id = nil
	end

	local selected_job_link = self.npc_info[changing_npc_id].job_link

	selected_job_link.npc_id = npc_info.se_obj.id

	self.npc_by_job_section[self.job_data[selected_job_link.job_id].section] = selected_job_link.npc_id

	npc_info.job_id = selected_job_link.job_id
	npc_info.job_prior = selected_job_link._prior
	npc_info.begin_job = true

	-- сохраняем ссылку на работу, для облегчения удаления
	npc_info.job_link = selected_job_link
	npc_info.need_job = "nil"

	local obj_storage = db.storage[npc_id]
	if obj_storage ~= nil then
		self:setup_logic(obj_storage.object)
	end

	-- Освобождаем НПС, который занимает желаемую работу и говорим ему перевыбрать работу
	self.npc_info[changing_npc_id].job_link = nil
	self.npc_info[changing_npc_id].job_id = -1
	self.npc_info[changing_npc_id].job_prior = -1
	self:update_jobs(self.npc_info[changing_npc_id].se_obj)
end


--*******************************************************
-- СЕЙВ/ЛОАД
--*******************************************************
-- сохранение
function se_smart_terrain:STATE_Write(packet)
	cse_alife_smart_zone.STATE_Write(self, packet)

	set_save_marker(packet, "save", false, "se_smart_terrain")

	if self.combat_manager == nil then
		self.combat_manager = sim_combat.CCombat_manager(self, sim_board.get_sim_board())
	end
	self.combat_manager:save(packet)

	-- Информацию о НПС в смарте
	local n = 0
	for k,v in pairs(self.npc_info) do
		n = n + 1
	end

	packet:w_u8(n)
	for k,v in pairs(self.npc_info) do
		packet:w_u16(k)
		packet:w_u8(v.job_prior)
		packet:w_u8(v.job_id)
		packet:w_bool(v.begin_job)
		packet:w_stringZ(v.need_job)
	end

	n = 0
	for k,v in pairs(self.dead_time) do
		n = n + 1
	end
	packet:w_u8(n)
	for k,v in pairs(self.dead_time) do
		packet:w_u8(k)
		utils.w_CTime(packet, v)
	end


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

-- восстановление
function se_smart_terrain:STATE_Read(packet, size)
	cse_alife_smart_zone.STATE_Read(self, packet, size)

	-- под LevelEditor не пытаться читать из пакета ничего
	if editor() then
		return
	end

	set_save_marker(packet, "load", false, "se_smart_terrain")
	self:read_params()

	if self.combat_manager == nil then
		self.combat_manager = sim_combat.CCombat_manager(self, sim_board.get_sim_board())
	end
	self.combat_manager:load(packet)

	-- Информацию о НПС в смарте
	local n = packet:r_u8()
	--printf("load %s npc", tostring(n))
	self.npc_info = {}
	for i = 1,n do
		local id = packet:r_u16()
		--printf("__ id %s", tostring(id))
		self.npc_info[id] = {}
		local npc_info = self.npc_info[id]

		npc_info.job_prior = packet:r_u8()
		--printf("__ job_prior %s", tostring(npc_info.job_prior))
		if npc_info.job_prior == 255 then
			npc_info.job_prior = -1
		end
		npc_info.job_id = packet:r_u8()
		--printf("__ job_id %s", tostring(npc_info.job_id))
		if npc_info.job_id == 255 then
			npc_info.job_id = -1
		end
		npc_info.begin_job = packet:r_bool()
		--printf("__ begin_job %s", tostring(npc_info.begin_job))
		npc_info.need_job = packet:r_stringZ()
	end

	n = packet:r_u8()
	self.dead_time = {}
	--printf("load %s dead_time", tostring(n))
	for i =1,n do
		local job_id = packet:r_u8()
		--printf("__ job_id %s", tostring(job_id))
		local dead_time = utils.r_CTime(packet)
		self.dead_time[job_id] = dead_time
	end

	self.need_init_npc = true

	set_save_marker(packet, "load", true, "se_smart_terrain")
end
-- Инициализация НПС после загрузки.
function se_smart_terrain:init_npc_after_load()
	local function find_job(jobs, npc_info)
		for k,v in pairs(jobs) do
			if v.jobs ~= nil then
				find_job(v.jobs, npc_info)
			else
				if v.job_id == npc_info.job_id then
					npc_info.job_link = v
					v.npc_id = npc_info.se_obj.id
					return
				end
			end
		end
	end


	local sim = alife()
	--printf("[%s] init_npc_after_load", self:name())
	for k,v in pairs(self.npc_info) do
		if sim:object(k) ~= nil then
			local npc_info = self:fill_npc_info(sim:object(k))

			npc_info.job_prior = v.job_prior
			npc_info.job_id = v.job_id
			npc_info.begin_job = v.begin_job
			npc_info.need_job = v.need_job

			--Теперь надо найти данную работу и выставить ссылку на нее.
			find_job(self.jobs, npc_info)

			self.npc_info[k] = npc_info
			if npc_info.job_link ~= nil then
				self.npc_by_job_section[self.job_data[npc_info.job_link.job_id].section] = k
			end
		else
			self.npc_info[k] = nil
		end
	end
end












-- Положить предмет в инвентори бокс смарта, при условии что он существует
function se_smart_terrain:call_respawn()
	if self.respawn then
		-- TODO: Вставить появление вещей в инвентори боксе
	end
end

--' Возвращает отформатированную строку свойств смарта
function se_smart_terrain:get_smart_props()
	local props = smart_names.get_smart_terrain_name(self)
	if(props==nil) or (_G.dev_debug) then
		props = self:name().."  ["..self.id.."]\\n"..
					"player = "..self.player_name.."\\n"..
					self.sim_type.." : "..tostring(self.sim_value).."\\n"..
					"squad_id = "..tostring(self.squad_id).."\\n"..
					"capacity = "..tostring(self.squad_capacity).." ("..
					sim_board.get_sim_board():get_smart_population(self)..")\\n"

			if self.respawn_sector ~= nil then
					props = props.."\\nrespawn_sector: "..tostring(self.respawn_sector).."\\n"
			end

			--' Добавляем информацию о находящихся в смарте отрядах
			for k,v in pairs(sim_board.get_sim_board().smarts[self.id].squads) do
					power = tostring(v.squad_power)
					props = props .. tostring(v.squad_id) .. " : " .. power .. "\\n"
			end
	end
	return props
end



--' Отрисовка смарта на игровом поле
function se_smart_terrain:show()
		local time = time_global()
	if(self.showtime~=nil) and (self.showtime+200>=time) then
		return
	end
	self.showtime = time

	local player = self.player_name
	local spot = "none"
	if(player~="none") then
		local relation = 0
			if(db.actor) then
					relation = db.actor:community_goodwill(player)+game_relations.get_factions_community(player, alife():actor():community())
			else
					relation = game_relations.get_factions_community(player, alife():actor():community())
			end
		if(relation==self.relation) then
			return
		else
					if(relation>=1000) then
							spot = "friend"
					elseif(relation<=-1000) then
							spot = "enemy"
					else
							spot = "neutral"
					end
		end
		self.relation = relation
	end

		if(self.smrt_showed_spot==spot) then
		level.map_change_spot_hint(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot, self:get_smart_props())
		return
		end
		if(_G.dev_debug) then
			if(self.smrt_showed_spot~=nil) then
					level.map_remove_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)
		end
				level.map_add_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..spot, self:get_smart_props())
				self.smrt_showed_spot = spot
	else
--[[
		if(self.sim_type=="base") then
					if(self.smrt_showed_spot~=nil) then
							level.map_remove_object_spot(self.id, "alife_presentation_smart_base_"..self.smrt_showed_spot)
					end
					level.map_add_object_spot(self.id, "alife_presentation_smart_base_"..spot, self:get_smart_props())
					self.smrt_showed_spot = spot
		else
]]
					if(self.smrt_showed_spot~=nil) and
				(level.map_has_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)~=0)
			then
							level.map_remove_object_spot(self.id, "alife_presentation_smart_base_"..self.smrt_showed_spot)
					end
--		end
	end
end
--' Обновление информации о смарте на игровом поле
function se_smart_terrain:refresh()
		self:show()
end
--' Убирание отрисовки смарта на игровом поле
function se_smart_terrain:hide()
	if self.smrt_showed_spot == nil then
		return
	end
		level.map_remove_object_spot(self.id, "alife_presentation_smart_"..self.sim_type.."_"..self.smrt_showed_spot)
end

-- Обновление.
-- В онлайне вызывается через binder.
-- Также может вызваться принудительно из xr_effects
function se_smart_terrain:update()
	cse_alife_smart_zone.update( self )

	self.combat_manager:update()

	local current_time = time_global()
	if self.check_time~=nil and current_time < self.check_time then
		return
	end
	if db.actor ~= nil then
		local distance = db.actor:position():distance_to_sqr(self.position)
		local idle_time = math.max(60, 0.003 * distance)
		self.check_time = current_time + idle_time
	else
		self.check_time = current_time + 10
	end

	-- Проверяем, не истек ли запрет на занимание работы, на которой убили НПС
	local current_time = game.get_game_time()
	for k,v in pairs(self.dead_time) do
		if current_time:diffSec(v) >= DEATH_IDLE_TIME then
			self.dead_time[k] = nil
		end
	end

	-- Перевыбор работ
	self:update_jobs()
end



-- установить логику и сообщить смарту, что объект перешёл в онлайн.
-- вызывается из net_spawn() объектов
function setup_gulag_and_logic_on_spawn(obj, st, sobject, stype, loaded)
	local sim = alife()
	local sobject = alife():object(obj:id())
	if sim ~= nil then
		local strn_id = sobject.m_smart_terrain_id
		printf( "setup_gulag_and_logic_on_spawn obj=%s, strn_id=%s, loaded=%s", obj:name(), tostring(strn_id), tostring(loaded))

		if strn_id ~= nil and strn_id ~= 65535 then
			local strn                   = sim:object(strn_id)
			local need_setup_logic       = (not loaded) and (strn.npc_info[obj:id()].begin_job == true)

			if need_setup_logic then
				strn:setup_logic(obj)
			else
				xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
			end
		else
			xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
		end
	else
		xr_logic.initialize_obj(obj, st, loaded, db.actor, stype)
	end
end

-- Убираем объект из смарта при смерти
function on_death(obj)
	local sim = alife()
	if sim then
		local obj = sim:object(obj.id)
		if obj == nil then return end
		local strn_id = obj:smart_terrain_id()
		if strn_id ~= 65535 then
			printf("clear dead object %s", obj:name())
			sim:object(strn_id):clear_dead(obj)
		end
	end
end

--------------------
-- прочитать секцию [smart_terrains]
-- вызывается объектами, которые могут ходить под smart terrain
function read_smart_terrain_conditions( self )
	if self.ini and self.ini:section_exist(SMART_SECTION_NAME) then
		local conds = {}
		local accepts = false
		local result, field, str
		local n = self.ini:line_count(SMART_SECTION_NAME)
		if n > 0 then
			for i = 0, n-1 do
				result, field, str = self.ini:r_line(SMART_SECTION_NAME, i, "", "" )
				conds[field] = xr_logic.parse_condlist(self, SMART_SECTION_NAME, field, str)
			end
			return conds
		end
	end
	return nil
end
