--[[------------------------------------------------------------------------------------------------------------------
Smart Terrain (механизм, организовывающий всех сталкеров и некоторых монстров в сцены)
Чугай Александр
!без симуляции не работает!
--------------------------------------------------------------------------------------------------------------------]]

--function printf()
--end

local check_period

if not editor() then
	check_period = game.CTime()
	check_period:setHMSms( 0, 0, 0, 50000 )

	CTime_0 = game.CTime()
end

local monster_classes

local disagreed, agreed, agreed_exclusive = 0, 1, 2

local smart_section = "smart_terrains"

local smart_terrains = {}
local smart_terrains_by_name = {}

exclusives = {}

--[[ список всех серверных объектов смартов, которые зарегистрированы в симуляторе
Формат:
	smart_terrains = {
		level_name = { id=obj, id=obj, ... },
		level_name = { id=obj, id=obj, ... },
		...
	}
]]

----------------------------------------------------------------------------------------------------------------------
-- Разные полезные функции
----------------------------------------------------------------------------------------------------------------------
-------------- помощники для чтения ini (custom data) -------------------
-- TODO убрать их
function r_str( spawn_ini, section, line, default )
	if spawn_ini:line_exist( section, line ) then
		return spawn_ini:r_string( section, line )
	else
		return default
	end
end

function r_num( spawn_ini, section, line, default )
	if spawn_ini:line_exist( section, line ) then
		return spawn_ini:r_float( section, line )
	else
		return default
	end
end

function r_2nums( spawn_ini, section, line, def1, def2 )
	if spawn_ini:line_exist( section, line ) then
		-- если default-ов больше, чем значений в ini, то забить недостающие последним значением из ini
		local t = parse_nums( spawn_ini:r_string( section, line ) )
		local n = table.getn( t )

		if n == 0 then
			return def1, def2
		elseif n == 1 then
			return t[1], def2
		else
			return t[1], t[2]
		end
	else
		return def1, def2
	end
end

----------------------------------------------------------------------------------------------------------------------
-- Класс "se_smart_terrain". Обеспечивает поддержку smart terrain в ОФЛАЙНЕ.
-- Унаследован от скриптовой зоны.
----------------------------------------------------------------------------------------------------------------------
class "se_smart_terrain" ( cse_alife_smart_zone )

function se_smart_terrain:__init( section ) super( section )
--	log( "se_smart_terrain::se_smart_terrain called!" )

	self.initialized = false
	self.registred = false
end

function se_smart_terrain:detect_probability()
--	log( "se_smart_terrain::detect_probability called!" )
	return 0
end

function se_smart_terrain:print()
	printf( "NPCs:" )
	if self.npc_info then
		for id, v in pairs( self.npc_info ) do
			printf( "    %d %s", id, tostring(v.name) )
		end
	end
end

-- сохранение
function se_smart_terrain:STATE_Write( packet )
--	log( "se_smart_terrain::STATE_Write called!" )
	cse_alife_smart_zone.STATE_Write( self, packet )

--	self:print()

	utils.w_CTime( packet, self.duration_end )
	utils.w_CTime( packet, self.idle_end )

	if self.gulag_working then
		packet:w_bool( true )

		self.gulag:save_common( packet )

		local n = 0

		for id, v in pairs( self.npc_info ) do
			n = n + 1
		end

		packet:w_u8( n )

		for id, v in pairs( self.npc_info ) do
			packet:w_u16  ( id )
			packet:w_u8   ( v.o_group )
			packet:w_u8   ( v.o_squad )
			packet:w_bool ( v.exclusive )
			utils.w_CTime ( packet, v.stay_end )

			self.gulag:save_obj( packet, id )
		end

		packet:w_bool ( self.population_locked )
	else
		packet:w_bool( false )
	end
end

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

--	printf( "before read_params" )
--	self:print()
	
	self:read_params()

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

--	printf( "name=%s, script_version=%d", self:name(), self.script_version )

	self.duration_end = utils.r_CTime( packet )
	self.idle_end     = utils.r_CTime( packet )

	self.gulag_working = packet:r_bool()

	-- если есть работающий гулаг
	if self.gulag_working then
		self.check_time = CTime_0

		self.gulag:load_common( packet )

		local n = packet:r_u8()
		local o_id, o

--		printf( "npc_info=%s", tostring( n ) )

		for i = 1, n do
			o_id = packet:r_u16()
			self.npc_info[o_id] = {}
			o = self.npc_info[o_id]

			o.o_group   = packet:r_u8()
			o.o_squad   = packet:r_u8()
			o.exclusive = packet:r_bool()
			o.stay_end  = utils.r_CTime( packet )

			self.gulag:load_obj( packet, o_id )
		end

		self.population_locked = packet:r_bool()
	end

--	printf( "after read [%s]", self:name())
--	self:print()
end

-- инициализировать работу смарт террейна.
-- после этой функции смарт ПОЛНОСТЬЮ работоспособен.
-- вызывается из всех мест, которые могут вызвать другие объекты:
--   se_smart_terrain:enabled()
--   se_smart_terrain:update()
--   xr_gulag.setup_gulag_and_logic_on_spawn()
--   xr_gulag.get_npc_gulag()
--   xr_gulag.get_gulag_by_name()
--   xr_gulag.get_gulag_by_sid()
function se_smart_terrain:initialize_if_needed()
	if not self.initialized then
		sim_statistic.update()

		self:get_se_objs()

		self:preserve_exclusives()

		self.initialized = true
	end
end

-- зарезервировать места для эксклюзивных персонажей
function se_smart_terrain:preserve_exclusives()
	local e = exclusives[self:name()]

	if e then
--'	printf( "[smart_terrain %s] exclusives=%d", self:name(), e )

	self.exclusives = e
--'	printf("[smart_terrain %s] preserve_exclusives [%s]", self:name(), tostring(self.gulag.capacity_non_exclusive))
		self.gulag.capacity_non_exclusive = self.gulag.capacity_non_exclusive - e

		exclusives[self:name()] = nil
	else
		self.exclusives = 0
	end
end

-- получить серверные объекты персонажей
function se_smart_terrain:get_se_objs()
	local sim = alife()
	local obj

	for id, v in pairs( self.npc_info ) do
		obj = sim:object( id )

		if not obj then
			abort( "[smart_terrain %s] can't get server object id=%d", self:name(), id )
		end

		sim_statistic.add( obj )

		self:fill_npc_info( obj, v )
	end
end

-- заполнить информацию о персонаже
-- у монстров нету метода profile_name()
-- TODO передавать в предикаты не npc_info, а объект
function se_smart_terrain:fill_npc_info( obj, npc_info )
	if not npc_info then
		npc_info = {}
	end

	npc_info.se_obj       = obj

	npc_info.class_id     = obj:clsid()
	npc_info.section_name = obj:section_name()
	npc_info.name         = obj:name()
	npc_info.story_id     = obj.m_story_id
	npc_info.community    = self:get_obj_community( obj )
	npc_info.profile_name = obj.profile_name and obj:profile_name()
	npc_info.rank         = obj:rank()

	npc_info.group        = obj:g_group()
	npc_info.squad        = obj:g_squad()
	npc_info.team         = obj:g_team()

	if npc_info.profile_name then
		npc_info.is_sniper = string.find( npc_info.profile_name, "sniper" ) ~= nil
	end
	npc_info.is_sniper_name = string.find( npc_info.name, "sniper" ) ~= nil

	local t               = obj.smart_terrain_conditions
	if t then
		t = t[self:name()]
	end

	npc_info.cond         = t
	return npc_info
end

-- возвращает:
-- а) если сталкер: группировку сталкера и true
-- б) если монстр:  вид монстра и false
function se_smart_terrain:get_obj_community( obj )
	local cls = obj:clsid()

	if cls == clsid.script_stalker then
		return obj:community(), true
	else
		return monster_classes[cls], false
	end
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
		sect = "gulag1"

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

	if not ini:line_exist( sect, "type" ) then
		abort( "[smart_terrain %s] type is not specified", self:name() )
	end

	local gp       = {}

	self.npc_info  = {}          -- group и squad для каждого npc
	self.gparams   = gp          -- настройки гулага

	gp.type        = ini:r_string( sect, "type" )
	gp.preset_name = r_str ( ini, sect, "preset" )
--'	printf("%s read_params preset name [%s]", self:name(), tostring(gp.preset_name))
	gp.capacity    = r_num ( ini, sect, "capacity", 100 ) -- ёмкость. а настоящая ёмкость = min(эта_ёмкость, ёмкость гулага)
	gp.squad       = r_num ( ini, sect, "squad" )

	gp.idle_min,     gp.idle_max      = r_2nums( ini, sect, "idle",       0,     0 )
	gp.duration_min, gp.duration_max  = r_2nums( ini, sect, "duration",   10000, 10001 )
	gp.stay_min,     gp.stay_max      = smart_terrain_params.get_stay_time_interval( r_str( ini, sect, "stay", "default" ) )

	gp.respawn     = r_str ( ini, sect, "respawn" )

--	printf( "gp.stay_min=%d, gp.stay_max=%d", gp.stay_min * h2ms, gp.stay_max * h2ms )

	gp.cond        = r_str( ini, sect, "cond" )
	if gp.cond then
		gp.cond = xr_logic.parse_condlist( nil, sect, "cond", gp.cond)
	end

	gp.groups      = r_str( ini, sect, "groups" )
	if gp.groups then
		gp.groups = parse_nums( gp.groups )
	end

	self.accepted_communities = nil

	local t = r_str( ini, sect, "communities" )
	if t then
		t = parse_names( t )

		self.accepted_communities = {}

		for i, v in ipairs( t ) do
			self.accepted_communities[v] = true
		end
	end

	local ltx = system_ini()

	self.rank_inc_min = ltx:r_string( "smart_terrain_rank_change", "min" )
	self.rank_inc_max = ltx:r_string( "smart_terrain_rank_change", "max" )

	self.population_locked = false

	self.gulag = xr_gulag.gulag( self, self.gparams.type, self.gparams.squad, self.gparams.groups, self.npc_info, self.gparams.capacity )

	self.gulag_working = false
end

-- подходит ли персонаж по предустановкам.
-- если такой предустановки нету, то он всегда подходит.
function se_smart_terrain:check_preset( npc_community, npc_rank, preset_name )
	local preset = smart_terrain_params.get_preset( preset_name )

	if preset == false then
		return true
	else
		local t = preset[npc_community]

--		printf( "preset_name=%s ranks=[%d,%d] npc_rank=%d", preset_name, t[1], t[2], npc_rank )

		if t and ( npc_rank >= t[1] and npc_rank <= t[2] ) then
			return true
		else
			return false
		end
	end
end

--[[ подходит ли объект гулагу?
1) если объект не согласен сюда идти, то smart terrain его не возьмёт
2) если объект согласен идти куда угодно, то проверить, есть ли ещё места, подходит ли он гулагу,
   подходит ли он хоть на одну работу
3) если объект согласен идти только в этот смарт, то принимаем даже если нету места (он вытеснит кого-то), проверить,
   подходит ли он гулагу, подходит ли он хоть на одну работу
]]
local level_groups = {
    l01_escape = "group1",
    l02_garbage = "group1",
    l03_agroprom = "group1",
    l03u_agr_underground = "group1",
    l04_darkvalley = "group1",
    l04u_labx18 = "group1",
    l05_bar = "group1",
    l06_rostok = "group1",
    l07_military = "group1",
    l08_yantar = "group1",
    l08u_brainlab = "group1",
    l10_radar = "group1",
    l10u_bunker = "group1",
    l11_pripyat = "group1",
    l12_stancia = "group3",
    l12u_sarcofag = "group3",
    l12u_control_monolith = "group3",
    l12_stancia_2 = "group3"
}

function se_smart_terrain:smart_terrain_accepts_obj( obj, obj_agreement )
	if obj_agreement == disagreed then
--		printf("[%s] disagreed", obj:name())
		return false
	end

--'	if obj:name() == "esc_wolf" then
--'		printf("before checking: pop = %s, cap = %s", tostring(self.gulag:get_non_exclusive_population()), tostring(self.gulag.capacity_non_exclusive))
--'	end

	if obj_agreement == agreed_exclusive or
	   self.gulag:get_non_exclusive_population() < self.gulag.capacity_non_exclusive
	then
		local community, is_stalker = self:get_obj_community( obj )

        --' Проверка что смарттеррейн и НПС находятся в одном кластере уровней (чтобы не ходили через БАР)
        local smart_level_group = self:get_level_name()
        local npc_level_group = alife():level_name(game_graph():vertex(obj.m_game_vertex_id):level_id())

--'		if obj:name() == "esc_wolf" then
--'			printf("smart=%s stalker=%s", tostring(smart_level_group), tostring(npc_level_group))
--'		end

        if level_groups[smart_level_group] ~= level_groups[npc_level_group] then
            return false
        end
    

--'	if obj:name() == "esc_wolf" then
--'		printf( "comm=%s is_stalker=%s", community, tostring( is_stalker ) )
--'	end

		if self.accepted_communities and not self.accepted_communities[community] then
			return false
		end

--'		if obj:name() == "esc_wolf" then
--'			printf( "community=%s rank=%d reputation=%d", community, obj:rank(), obj:reputation() )
--'		end
	
		if obj_agreement ~= agreed_exclusive and not self:check_preset( community, obj:rank(), self.gparams.preset_name ) then
			return false
		end

--'		if obj:name() == "esc_wolf" then
--'			printf( "check_preset ok" )
--'		end

		if not xr_gulag.checkNpc( community, is_stalker, self.gparams.type, obj:rank(), obj ) then
			return false
		end

--'	if obj:name() == "esc_wolf" then
--'		printf( "checkNpc ok" )
--'	end

		return self.gulag:is_there_any_suitable_job( self:fill_npc_info( obj ), obj_agreement == agreed_exclusive )
	else
		return false
	end
end

--[[ подходит ли гулаг объекту?
у объекта в custom data прописаны условия для некоторых smart_terrains:
	<smart_terrain_name1> = <condlist1>
	<smart_terrain_name2> = <condlist2>
	...
Если для какого-то smart_terrain условие выполнилось, он называется эксклюзивным.
Если у объекта появился хоть один эксклюзивный smart terrain, то он будет согласен идти только в него.
Если не появилось ни одного эксклюзивного, то он согласен идти в любой.
]]
-- TODO а если в начале эксклюзивный, а потом в любой?
-- вариант: any=condlist
function se_smart_terrain:obj_accepts_smart_terrain( obj )
	if obj.smart_terrain_conditions then
		local any_exclusive = false
		local s

		for name, condlist in pairs(obj.smart_terrain_conditions) do
			s = xr_logic.pick_section_from_condlist( db.actor_proxy, obj, condlist )

			if s ~= nil then
				if name == "none" then				
					return disagreed
			 	elseif name == self:name() then
--'					if obj:name() == "esc_wolf" then
--'						printf("[%s] obj_accepts_smart_terrain[%s] = agreed_exclusive", self:name(), obj:name())
--'					end
					return agreed_exclusive
				end
			else
				--' Если текущий смарттеррейн эксклюзивный и недоступен
				if name == self:name() then
					return disagreed
				end
			end
		end

		--' Если объекту запрещено переходить в online и эксклюзивные
		--' смарты недоступны, то не пускаем его никуда. Это исключает
		--' переброску отключенных до (или после) определённого события
		--' людей и мутантов в другой неэксклюзивный смарт.
		if obj:can_switch_online() == false then
			return disagreed
		end

		--' Надо убедиться что эксклюзивный смарттеррейн доступен, прежде чем запрещать все остальные
--'		if any_exclusive then
--'			if obj:name() == "gar_bandit_agr_10" then
--'				printf("[%s] obj_accepts_smart_terrain[%s] = disagreed. excl=%s", self:name(), obj:name(), tostring(any_exclusive))
--'			end
--'			return disagreed
--'		end
	end

--'	if obj:name() == "esc_wolf" then
--'		printf("[%s] obj_accepts_smart_terrain[%s] = agreed", self:name(), obj:name())
--'	end

	return agreed
end

-- может ли объект быть добавлен в smart terrain?
-- вызывается симулятором при поиске задания для объекта/группы.
-- если объект - группа, то вернёт true только если все объекты в группе вернут true
function se_smart_terrain:enabled(obj)
	-- DEBUG
	if self.disabled then
--		printf("[smart_terrain %s] DISABLED obj=%s (self.disabled)", self:name(), obj:name())
		return false
	end
	--------
	self:register_if_needed()
	self:initialize_if_needed()

	if self:is_gulag_available() and not self.population_locked then
		local b = self:obj_accepts_smart_terrain( obj )
		local a = self:smart_terrain_accepts_obj( obj, b )

--'		if obj:name() == "esc_wolf" then
--'			if a == true then
--'				printf("[smart_terrain %s] ENABLED obj=%s exclusive=%s", self:name(), obj:name(), tostring(b))
--'			else
--'				printf("[smart_terrain %s] DISABLED obj=%s  accept_obj = %s", self:name(), obj:name(), tostring(b))
--'			end
--'		end

		return a
	else

--'		if obj:name() == "esc_wolf" then
--'			printf("[smart_terrain %s] DISABLED not_enabled", self:name())
--'		end
		return false
	end
end

-- возвращает меру подходящести персонажа смарттерейну.
-- вызывается для всех смарттерейнов, которые вернули true из enabled
-- для того, чтобы выбрать тот единственный, в который персонаж всё-таки пойдёт.
-- критерии:
--   1) если объект уже бывал в других смартах, то чем больше свободных работ на уровне этого смарта, тем лучше
--   2) иначе ближе - лучше
-- TODO Оптимизировать
function se_smart_terrain:suitable( obj )
--'	if obj:name() == "esc_wolf" then
--'		printf("[%s] suitable [%s]", self:name(), obj:name())
--'	end

	local v = 0
	
	--' Повысить подходящесть эксклюзивного смарттеррейна
	if self:obj_accepts_smart_terrain(obj) == agreed_exclusive then
		v = 100000 --'Так чтобы все эксклюзивные были заведомо лучше
	end

	for id, strn in pairs( smart_terrains[self:get_level_name()] ) do

		if strn:is_gulag_available() then
			v = v + strn.gulag.capacity - strn.gulag:get_population()
		end
	end

--'	if obj:name() == "esc_wolf" then
--'		printf( "[smart_terrain %s] obj=%s suitable=%d was_in_smart_terrain=%s",
--'		        self:name(),
--'		        obj:name(),
--'				v,
--'		        tostring(obj.was_in_smart_terrain) )
--'	end

	return v
end

-- добавить npc в smart terrain.
-- если места нету или нету свободных подходящих работ, то
-- вытолкнуть из этого smart terrain с подходящей работы объект, который попал в него не эксклюзивно.
function se_smart_terrain:register_npc( obj )

	printf( "[smart_terrain %s] register called obj=%s gulag_type=%s", self:name(), obj:name(), self.gparams.type )

	local npc_info = {
		-- старые squad и group
		o_group        = obj:g_group(),
		o_squad        = obj:g_squad(),

		exclusive      = self:obj_accepts_smart_terrain( obj ) == agreed_exclusive,

--		move_offline   = o:move_offline(),
		stay_end       = game.CTime()
	}

	self:fill_npc_info( obj, npc_info )

	if self.gulag_working and
--	   ( self.gulag:get_population() == self.gulag.capacity or
	   not self.gulag:is_there_any_suitable_job( npc_info, false ) --)
	then
		printf( "get_population=%d capacity=%d", self.gulag:get_population(), self.gulag.capacity )
		printf( "is_there_any_suitable_job=%s", tostring(self.gulag:is_there_any_suitable_job(npc_info, false)) )
		print_table( self.npc_info )
		self:kick_someone_non_exclusive( npc_info )
	end

	self:update_obj_rank( obj )

	obj.was_in_smart_terrain = true

	self.npc_info[obj.id] = npc_info

	t = self.npc_info[obj.id].stay_end
--	t:setHMSms( 0, 0, 0, math.random( self.gparams.stay_min * h2ms, self.gparams.stay_max * h2ms ) )
	t:setHMSms( math.random( self.gparams.stay_min, self.gparams.stay_max ), 0, 0, 0 )
	t:add( game.get_game_time() )

--	o:move_offline( false )

	if not self.gulag_working then
		self:create_gulag()
	end

	self.gulag:addObject( obj.id )

	sim_statistic.add( obj )

	self.gulag:update()


--	printf( "[smart_terrain %s] added %s to gulag type=%s", self:name(), obj:name(), self.gparams.type )
end

-- отпустить npc
-- TODO а если такого объекта уже нету?
function se_smart_terrain:unregister_npc( obj )
	local n = self.npc_info[obj.id]

	--' Если анрегистрим мертвого эксклюзивного НПС, нужно уменьшить счетчик эксклюзивности
	if not obj:alive() and obj.smart_terrain_conditions then
		local any_exclusive = false
		local s

		for name, condlist in pairs(obj.smart_terrain_conditions) do
			s = xr_logic.pick_section_from_condlist( db.actor_proxy, obj, condlist )

			if s ~= nil then
			 	if name == self:name() then
					--' уменьшаем счетчик
					self.exclusives = self.exclusives - 1
					self.gulag.capacity_non_exclusive = self.gulag.capacity_non_exclusive + 1
				end
			end
		end
	end

	if not n then
		printf( "self.npc_info[obj.id] = nil !!! obj.id=%d", obj.id )
		self:print()
		self.gulag:print()
	end

	self.gulag:removeObject( obj.id )

	obj.squad = n.o_squad
	obj.group = n.o_group

	self.npc_info[obj.id] = nil

	obj:clear_smart_terrain()

	sim_statistic.remove( obj )

--'	if sim_statistic.show_stalker_spot == true and obj:alive() then
--'		local community, rank = sim_statistic.getNpcType(obj)
--'		local spot = "alife_presentation_monstr"
--'		if IsStalker(obj) then
--'			spot = "alife_presentation_"..community
--'		end
--'		local str = obj:name().." "..self:name().."\\n".."<<removed>>"
--'		level.map_change_spot_hint(obj.id, spot, str)		
--'	end

	printf( "[smart_terrain %s] removed %s from gulag type=%s, strn_id=%s", self:name(), obj:name() or "<<<nil>>>", self.gparams.type, obj:smart_terrain_id())
end

-- изменть ранг сталкера при регистрации в смарт
function se_smart_terrain:update_obj_rank( obj )
	local community, is_stalker = self:get_obj_community( obj )

	if is_stalker then
		local old_rank = obj:rank()

		obj:set_rank( old_rank + math.random( self.rank_inc_min, self.rank_inc_max ) )

--		printf( "[smart_terrain %s] obj=%s rank %d -> %d", self:name(), obj:name(), old_rank, obj:rank() )
	end
end

-- выгнать из-под смарттеррейна первого попавшегося неэксклюзивного персонажа,
-- который занимает работу, подходящую новому персонажу.
-- TODO а если я выгоню кого-то, а его работа не подойдёт по idle ?
function se_smart_terrain:kick_someone_non_exclusive( new_npc_info )
	for obj_id, npc_info in pairs( self.npc_info ) do
		if not npc_info.exclusive and self.gulag:obj1_job_suits_obj2( obj_id, new_npc_info ) then
			printf("[smart_terrain %s] kicking %s", self:name(), npc_info.name)
			self:unregister_npc( npc_info.se_obj )
			return
		end
	end

	abort( "[smart_terrain %s] can't add npc, all are exclusive or there are no suitable jobs", self:name() )
end

-- выдать объекту задание.
function se_smart_terrain:task( obj )
	local path_name = self.gulag:get_obj_job_path_name( obj.id )
	if not path_name then
		printf("[smart_terrain %s] task: obj=%s job_path=nil, CLEARING", self:name(), obj:name() )
		self:unregister_npc(obj) --'Затычка против вылета.
		return CALifeSmartTerrainTask("simulation_default_path")
	end

--'	printf("SEETING TASK PATH %s = %s", obj:name(), path_name)
	--' Пробуем проапдейтить типс чувака на карте
--'	if sim_statistic.show_stalker_spot == true and obj:alive() then
--'		local community, rank = sim_statistic.getNpcType(obj)
--'		local spot = "alife_presentation_monstr"
--'		if IsStalker(obj) then
--'			spot = "alife_presentation_"..community
--'		end
--'		local str = obj:name().." "..self:name().."\\n"..path_name

--'		level.map_change_spot_hint(obj.id, spot, str)		
--'	end

	return CALifeSmartTerrainTask( path_name )
end

-- может ли данный гулаг создаться в данный момент?
function se_smart_terrain:is_gulag_available()
	if self.gparams.cond then
		local bb = xr_logic.pick_section_from_condlist( db.actor_proxy, self, self.gparams.cond ) ~= nil
--'		self:show_smart_debug_spot(bb)
--'		if self:name() == "gar_bandit_agr" then
--'			printf("[smartterrain %s] is_gulag_available = %s", self:name(), tostring(bb))
--'		end
		return bb
	else
--'		self:show_smart_debug_spot(true)
--'		if self:name() == "gar_bandit_agr" then
--'			printf("[smartterrain %s] is_gulag_available = %s extra", self:name(), tostring(true))
--'		end
		return true
	end
end
function se_smart_terrain:show_smart_debug_spot(avail)
	if sim_statistic.show_smart_spot == true then
		--' Показать отметку
		local str = self:name().." ("..tostring(self.gulag.state)..")\\n".."pop = "..tostring(self.gulag:get_population()).."("..tostring(self.gulag.capacity_non_exclusive)..":"..tostring(self.gulag.capacity)..") comed = "..tostring(self.gulag:get_population_comed())

		if self.debug_avail ~= avail then
			if self.debug_avail == true then
				level.map_remove_object_spot(self.id, "alife_presentation_smart_on")
			elseif self.debug_avail == false then
				level.map_remove_object_spot(self.id, "alife_presentation_smart_off")
			end
			if avail == true then
				level.map_add_object_spot(self.id, "alife_presentation_smart_on", str)
			else
				level.map_add_object_spot(self.id, "alife_presentation_smart_off", str)
			end
			self.debug_avail = avail
		else
			if avail == true then
				level.map_change_spot_hint(self.id, "alife_presentation_smart_on", str)
			else
				level.map_change_spot_hint(self.id, "alife_presentation_smart_off", str)
			end
		end
		 self.spot_showed = true
	else
		--' Убрать отметку
		if self.spot_showed == nil then
			return
		end
		self.spot_showed = nil
		if self.debug_avail == true then
			level.map_remove_object_spot(self.id, "alife_presentation_smart_on")
		else
			level.map_remove_object_spot(self.id, "alife_presentation_smart_off")
		end		
	end
end
-- установка/снятие блокировки приёма персонажей
function se_smart_terrain:lock_population( need_lock )
	self.population_locked = need_lock
end

-- в гулаг поступил первый персонаж и он начинает работать
function se_smart_terrain:create_gulag()
	self.duration_end = game.CTime()
--	self.duration_end:setHMSms( 0, 0, 0, math.random( self.gparams.duration_min * h2ms, self.gparams.duration_max * h2ms ) )
	self.duration_end:setHMSms( math.random( self.gparams.duration_min, self.gparams.duration_max ), 0, 0, 0 )
	self.duration_end:add( game.get_game_time() )

	self.check_time = CTime_0

	self.gulag:initialize()

	self.gulag_working = true
end

-- всех освободить, убить гулаг,
-- создать новый нерабочий гулаг, зарезервировать в нём места для эксклюзивных
function se_smart_terrain:remove_gulag( disable )
	-- DEBUG
	self.disabled = disable
	--------

	if not self.gulag_working then
		return
	end

	printf( "[smart_terrain %s] removed gulag type=%s", self:name(), self.gparams.type )

	-- освободить всех персонажей
	for id, npc_info in pairs( self.npc_info ) do
		self:unregister_npc( npc_info.se_obj )
	end

	if self.gparams.idle_max > 0 then
		self.idle_end = game.CTime()
--		self.idle_end:setHMSms( 0, 0, 0, math.random( self.gparams.idle_min * h2ms, self.gparams.idle_max * h2ms ) )
		self.idle_end:setHMSms( math.random( self.gparams.idle_min, self.gparams.idle_max ), 0, 0, 0 )
		self.idle_end:add( game.get_game_time() )
	else
		self.idle_end = nil
	end

	self.gulag = xr_gulag.gulag( self, self.gparams.type, self.gparams.squad, self.gparams.groups, self.npc_info, self.gparams.capacity )
	self.gulag:load_jobs()

	self.gulag_working = false

	self.gulag.capacity_non_exclusive = self.gulag.capacity_non_exclusive - self.exclusives
--'	self:show_smart_debug_spot(false)
end

-- заполняет таблицу monster_classes
function se_smart_terrain:fill_monster_classes()
	if not monster_classes then
		monster_classes = {
			[clsid.bloodsucker_s ] = "bloodsucker",
			[clsid.boar_s        ] = "boar",
			[clsid.burer_s       ] = "burer",
			[clsid.cat_s         ] = "cat",
			[clsid.chimera_s     ] = "chimera",
			[clsid.controller_s  ] = "controller",
			[clsid.pseudodog_s   ] = "pseudodog",
			[clsid.psy_dog_s     ] = "psy_dog",
			[clsid.dog_s         ] = "dog",
			[clsid.flesh_s       ] = "flesh",
			[clsid.fracture_s    ] = "fracture",
			[clsid.poltergeist_s ] = "poltergeist",
			[clsid.gigant_s      ] = "pseudo_gigant",
			[clsid.snork_s       ] = "snork",
			[clsid.tushkano_s    ] = "tushkano",
			[clsid.zombie_s      ] = "zombie"
		}
	end
end

-- возвращает название уровня, на котором находится smart terrain
function se_smart_terrain:get_level_name()
--	return "chugai_test"

--	printf( "[smart_terrain %s] is on level %s", self:name(), alife():level_name( game_graph():vertex( self.m_game_vertex_id ):level_id() ) )

	return alife():level_name( game_graph():vertex( self.m_game_vertex_id ):level_id() )

--[[	local gvid = self.m_game_vertex_id
	printf( "self.m_game_vertex_id=%s", tostring( gvid ) )

	local gg = game_graph()
	if gg == nil then
		printf( "game_graph() == nil" )
	else
		printf( "game_graph() ok" )
	end

	local v = gg:vertex( gvid )
	if v == nil then
		printf( "game_graph():vertex( gvid ) == nil" )
	else
		printf( "game_graph():vertex( gvid ) ok" )
	end

	local lid = v:level_id()
	printf( "v:level_id()=%s", tostring( lid ) )

	local ln = alife():level_name( lid )
	printf( "alife():level_name( ln )=%s", tostring( ln ) )

	return ln]]
end

-- регистрация объекта в симуляторе.
-- вызывается симулятором.
function se_smart_terrain:on_register()
	cse_alife_smart_zone.on_register( self )

	self:register_if_needed()
	self:initialize_if_needed()
end

function se_smart_terrain:register_if_needed()
	--' Если уже зарегистрены то ничего не делать.
	if self.registred == true then
		return
	end
	self.registred = true
	
	printf( "%s register called", self:name() )
	-- DEBUG
	if self.disabled then
		return
	end

	self:fill_monster_classes()

	local level_name = self:get_level_name()

	if not self.gparams.preset_name then
		self.gparams.preset_name = level_name
	end

	if not smart_terrains[level_name] then
		smart_terrains[level_name] = {}
	end

	smart_terrains[level_name][self.id] = self
	smart_terrains_by_name[self:name()] = self

	if self.gparams.respawn then
		db.strn_by_respawn[self.gparams.respawn] = self
	end

	self.gulag:load_jobs()

	if self.gulag_working then
		self.gulag:initialize()
	end

	sim_statistic.reinit()

	-- Регистрация в таскменеджере
	task_manager.get_random_task():register_target(self)
end



-- дерегистрация объекта в симуляторе.
-- вызывается симулятором.
function se_smart_terrain:on_unregister()
	cse_alife_smart_zone.on_unregister( self )

--	printf( "[smart_terrain %s] on_unregister called", self:name() )

	local lvl_smarts = smart_terrains[self:get_level_name()]

	if lvl_smarts then
		lvl_smarts[self.id] = nil
	end

	smart_terrains_by_name[self:name()] = nil

	if self.gparams and self.gparams.respawn then
		db.strn_by_respawn[self.gparams.respawn] = nil
	end

	db.actor_proxy:deinit()
end

-- вызвать спавнер, если настроен.
function se_smart_terrain:call_respawn()
	if self.gparams.respawn then
		se_respawn.spawn( self.gparams.respawn )
	end
end

-- возвращает строку с принимаемыми группировками и рангами
function se_smart_terrain:stat_get_accepted_communities()
	local comms = {}

	-- from preset
	local ltx         = ini_file( "misc\\" .. self.gparams.type .. ".ltx" )
	local preset_name = self.gparams.preset_name

	if ltx and ltx:section_exist( preset_name ) then
		local result, field, str
		local n = ltx:line_count( preset_name )

		for i = 0, n-1 do
			result, field, str = ltx:r_line( preset_name, i, "", "" )

			if not self.accepted_communities or self.accepted_communities[field] then
				comms[field] = str
			end
		end
	end

	-- from custom data
	if self.accepted_communities then
		for comm, v in pairs( self.accepted_communities ) do
			if not comms[comm] then
				comms[comm] = "all"
			end
		end
	end

	-- prepare return
	local s = ""

	for comm, ranks in pairs( comms ) do
		s = s .. comm .. "(" .. ranks .. "),"
	end

	return s
end

-- вывод статистики смарта
function se_smart_terrain:stat()
	local s = self:get_level_name() .. " " .. self:name()

	local pop   = self.gulag:get_population()
	local cap   = self.gulag.capacity

	local avail = self:is_gulag_available()

	if avail then
		s = s .. " enabled "
	else
		s = s .. " disabled "
	end

	s = s .. pop .. " " .. cap .. " " .. self:stat_get_accepted_communities()

	printf( s )

--	for id, npc_info in pairs( self.npc_info ) do
--		printf( "    %s", npc_info.name )
--	end

	return avail, pop, cap
end

-- Обновление.
-- В онлайне вызывается через binder.
-- Также может вызваться принудительно из xr_effects
-- Если мужиков в гулаге больше не осталось или условия существования гулага не выполняются, то убрать гулаг.
function se_smart_terrain:update()
--	log( "se_smart_terrain::update called!" )
	cse_alife_smart_zone.update( self )

	if not self.gulag_working then
		return
	end

--'	self:initialize_if_needed()

	if self:is_gulag_available() then
		if self.check_time < game.get_game_time() then
			self.check_time = game.get_game_time() + check_period

			--' проверять, не собрался ли кто-то к этому времени уже уходить
			for id, npc_info in pairs( self.npc_info ) do
				if npc_info.cond and xr_logic.pick_section_from_condlist( db.actor_proxy, npc_info.se_obj, npc_info.cond ) == nil then
					printf("[smart_terrain %s] leaving %s", self:name(), npc_info.name)
					self:unregister_npc( npc_info.se_obj )
				end
			end

			if self.gulag:get_population() == 0 then
				self:remove_gulag()
			else
				self.gulag:update()
			end
		end
	else
		self:remove_gulag()
	end
end

--------------------
function on_death( obj_id )
--	printf( "on_death obj_id=%d", obj_id )

	local sim = alife()

	if sim then
		local obj     = sim:object( obj_id )
		local strn_id = obj:smart_terrain_id()
-- alterei
		-- logf("smart_terrain on_death = " .. obj:name())
		-- local victim_pos = obj:position()
		-- local npc_pos = vector():set(victim_pos.x, victim_pos.y + 1, victim_pos.z)	
		-- logf("monster pos = " .. victim_pos.x .. ", " .. victim_pos.y .. ", " .. victim_pos.z)

		if strn_id ~= 65535 then
			sim:object( strn_id ).gulag:clear_dead(obj_id)
		end
	end
end

-----------------------
--' Удаляет объект из смарттеррейнов.
function unregister_npc(npc)
	local sim = alife()

	if sim then
		local obj     = sim:object(npc.id)
		local strn_id = obj:smart_terrain_id()

		if strn_id ~= 65535 then
			if sim:object(strn_id) then
				sim:object(strn_id):unregister_npc(obj)
			end
		end
	end
end



--------------------
-- прочитать секцию [smart_terrains]
-- вызывается объектами, которые могут ходить под smart terrain
function read_smart_terrain_conditions( self )
	if self.ini and self.ini:section_exist( smart_section ) then
		local conds = {}
		local accepts = false
		local result, field, str
		local n = self.ini:line_count( smart_section )

		if n > 0 then
			for i = 0, n-1 do
				result, field, str = self.ini:r_line( smart_section, i, "", "" )

				conds[field] = xr_logic.parse_condlist(self, smart_section, field, str)
			end

			return conds
		end
	end

	return nil
end

--------------------
-- Вывод статистики по всем смартам в симуляции
function stat()
	printf( "--------------- Smart terrains statistics START ----------------------" )

	local jobs, jobs_in_avail, occupied
	local level_stats = {}

	for level_name, t in pairs( smart_terrains ) do
		jobs, jobs_in_avail, occupied = 0, 0, 0

		for id, strn in pairs( t ) do
			local avail, pop, cap = strn:stat()

			jobs       = jobs     + cap
			occupied   = occupied + pop

			if avail then
				jobs_in_avail = jobs_in_avail + cap
			end
		end

		local l = {}
		level_stats[level_name] = l

		l.jobs          = jobs
		l.occupied      = occupied
		l.jobs_in_avail = jobs_in_avail
	end

	jobs, jobs_in_avail, occupied = 0, 0, 0

	printf("")

	for level_name, l in pairs( level_stats ) do
		printf( "Level %s :", level_name )
		printf( "    Jobs in all smart terrains : %d",       l.jobs )
		printf( "    Jobs in available smart terrains : %d", l.jobs_in_avail )
		printf( "    Occupied jobs : %d",                    l.occupied )

		jobs          = jobs          + l.jobs
		jobs_in_avail = jobs_in_avail + l.jobs_in_avail
		occupied      = occupied      + l.occupied
	end

	printf( "All levels:" )
	printf( "    Jobs in all smart terrains : %d",       jobs )
	printf( "    Jobs in available smart terrains : %d", jobs_in_avail )
	printf( "    Occupied jobs : %d",                    occupied )

	printf( "--------------- Smart terrains statistics END ------------------------" )
end
