
--[[
ОПИСАНИЕ ФУНКЦИЙ

-- Выводит отладочное сообщение в лог, если скрипт активен (активный скрипт выбирается с помощью
-- переменной debug_info.trace_script).
function trace(script_name, fmt, ...)

-- Переводит переменную любого типа (включая nil) в строку. Используется для отладочного вывода информации.
function to_str(what)

-- Создает и возвращает копию вектора
function vector_copy_by_val(vec)

-- Настройка параметров игровых объектов:
function cfg_get_bool(char_ini, section, field, object, mandatory, default_val)
function cfg_get_string(char_ini, section, field, object, mandatory, gulag_name, default_val)
function cfg_get_number(char_ini, section, field, object, mandatory, default_val)

-- Проверяет, находится ли stalker рядом с точкой path_point пути patrol_path
function stalker_at_waypoint(stalker, patrol_path, path_point)

-- Послать stalker в заданную точку patrol_path(path_point)
function stalker_go_to_waypoint(stalker, patrol_path, path_point)

--]]

----------------------------------------------------------------------------------------------------

-- Выводит отладочное сообщение в лог, если скрипт активен (активный скрипт выбирается с помощью
-- переменной debug_info.trace_script).
--function trace(script_name, fmt, ...)
--	if debug_info.trace_script == script_name then
--		log(string.format("[TRACE] " .. script_name .. ".script: " .. fmt, unpack(arg)))
--	end
--end

-- Переводит переменную любого типа (включая nil) в строку. Используется для отладочного вывода информации.
function to_str(what)
	if what == nil then
		return "<nil>"
	else
		return tostring(what)
	end
end

--'Копирование таблицы по значению
function copy_table(dest, src)
	for k,v in pairs(src) do
		if type(v) == "table" then
			--' рекурсивный вызов себя же для подтаблиц
			dest[k] = {}
			copy_table(dest[k], v)
		else
			dest[k] = v
		end
	end
end

function print_table()
	local sub
	if subs ~= nil then
		sub = subs
	else
		sub = ""
	end
	for k,v in pairs(table) do
		if type(v) == "table" then
			printf(sub.."%s:", tostring(k))
			print_table(v, sub.."    ")
		elseif type(v) == "function" then
			printf(sub.."%s:function", tostring(k))
		elseif type(v) == "userdata" then
			printf(sub.."%s:userdata", tostring(k))
		elseif type(v) == "boolean" then
	if v == true then
		printf(sub.."%s:true", tostring(k))
	else
		printf(sub.."%s:false", tostring(k))
	end
		else
			if v ~= nil then
				printf(sub.."%s:%s", tostring(k),v)
			else
				printf(sub.."%s:nil", tostring(k),v)
			end
		end
	end
end

--' Возвращает расстояние между двумя точками графа с учетом разности уровней
function graph_distance(vid1, vid2)
	local p1 = game_graph():vertex(vid1):game_point()
	local p2 = game_graph():vertex(vid2):game_point()

	--printf("GRAPH DISTANCE [%s][%s][%s] : [%s][%s][%s]", p1.x, p1.y, p1.z, p2.x, p2.y, p2.z)

	return game_graph():vertex(vid1):game_point():distance_to(game_graph():vertex(vid2):game_point())
end

--' Сравнивает два вектора
function vector_cmp(a, b)
		return a.x == b.x and a.y == b.y and a.z == b.z
end

--' Сравнивает два вектора с заданной погрешностью
function vector_cmp_prec(a, b, d)
		return math.abs(a.x - b.x) <= d and
					 math.abs(a.y - b.y) <= d and
					 math.abs(a.z - b.z) <= d
end


-- Создает и возвращает копию вектора
function vector_copy_by_val(vec)
--[[
	local newvec = vector()
	newvec.x = vec.x
	newvec.y = vec.y
	newvec.z = vec.z
	return newvec
--]]
	return vector():set(vec)
end

-- Настройка параметра типа bool у игрового объекта.
--
-- char_ini - указатель на customdata (обычно object:spawn_ini())
-- section - имя секции в customdata
-- field - имя поля в customdata
-- object - игровой объект, от которого будет взято имя для сообщения об ошибке
-- mandatory - поле обязательно должно быть задано
--
-- Возвращает true или false
function cfg_get_bool(char_ini, section, field, object, mandatory, default_val)
	if mandatory == nil then
		abort("section '%s': wrong arguments order in call to cfg_get_bool", section)
	end

	--printf("DEBUG: conf_get_bool: section='%s', field='%s'", section, field)
	if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then
		local ret = char_ini:r_bool(section, field)
		return ret
	end
	if not mandatory then
		if default_val then
			if default_val ~= false and default_val ~= true then
				abort("object '%s': section '%s': field '%s': default value is " ..
					"not boolean", object:name(), section, field)
			end
			return default_val
		end
		return false
	end
	abort("object '%s': attempt to read a non-existant boolean field '%s' in " ..
		"section '%s'", object:name(), field, section)
end

function cfg_get_string(char_ini, section, field, object, mandatory, gulag_name, default_val)
	if mandatory == nil or gulag_name == nil then
		abort("section '%s': wrong arguments order in call to cfg_get_string", section)
	end


	--printf("DEBUG: conf_get_string: section='%s', field='%s'", section, field)
	if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then
		if gulag_name and gulag_name ~= "" then
			return gulag_name .. "_" .. char_ini:r_string(section, field)
		else
			return char_ini:r_string(section, field)
		end
	end
	if not mandatory then
		return default_val
	end
	local err = "'Attempt to read a non-existant string field '" ..
		field .. "' in section '" .. section .. "'";
	abort("%s", err)
end

function cfg_get_number(char_ini, section, field, object, mandatory, default_val)
	if mandatory == nil then
		abort("section '%s': wrong arguments order in call to cfg_get_number", section)
	end

	--printf("DEBUG: conf_get_number: section='%s', field='%s'", section, field)
	if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then
		return char_ini:r_float(section, field)
	end
	if not mandatory then
		return default_val
	end
	-- local err = "object name '" .. object:name() .. "': attempt to read a non-existant numeric field '" ..
	--	field .. "' in section '" .. section .. "'";
	-- abort("%s", err)
end

function mob_get_string(char_ini, section, field, override, object, mandatory, default_val)
	if override ~= nil then
		return override
	end

	return cfg_get_string(char_ini, section, field, object, mandatory, "", default_val)
end

function mob_get_number(char_ini, section, field, override, object, mandatory, default_val)
	if override ~= nil then
		return override
	end

	return cfg_get_number(char_ini, section, field, object, mandatory, default_val)
end

function mob_get_bool(char_ini, section, field, override, object, mandatory, default_val)
	if override ~= nil then
		return override ~= false
	end

	return cfg_get_bool(char_ini, section, field, object, mandatory, default_val)
end

-- Проверяет, находится ли stalker рядом с точкой path_point пути patrol_path
function stalker_at_waypoint(stalker, patrol_path, path_point)
--	return stalker:level_vertex_id() == patrol_path:level_vertex_id(path_point)

	local stalker_pos = stalker:position()
	local distance = stalker_pos:distance_to_sqr(patrol_path:point(path_point))
	if distance <= 0.13 then
		return true
	end
	return false

end

-- Послать stalker в заданную точку patrol_path(path_point)
--[[
function stalker_go_to_waypoint(stalker, patrol_path, path_point)
	if stalker:animation_count() > 0 then
		stalker:clear_animations()
	end
	if stalker:level_vertex_id() == patrol_path:level_vertex_id(path_point) then
		return
	end
	stalker:set_dest_level_vertex_id(patrol_path:level_vertex_id(path_point))
	stalker:set_movement_type(move.run)
	stalker:set_body_state(move.standing)
	stalker:set_sight(look.path_dir, nil, 0)
	stalker:set_path_type(game_object.level_path)
	stalker:set_mental_state(anim.danger)
	stalker:set_detail_path_type(move.line)
end
--]]

function stalker_stop(stalker)
	stalker:set_movement_type(move.stand)
end

--[[
function stalker_look_at_waypoint(stalker, patrol_path, path_point)
	local look_pt = this.vector_copy_by_val(patrol_path:point(path_point)):sub(stalker:position())
	stalker:set_sight(look.direction, look_pt, 0)
end
--]]

--[[
function stalker_look_at_stalker(stalker, whom)
	local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position())
	stalker:set_sight(look.direction, look_pt, 0)
end
--]]

--[[
function stalker_look_at_stalker_angle(stalker, whom, angle)
		--stalker - killer
		--whom - killed :)
	local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position())
	stalker:set_sight (look.direction, vector_rotate_y (look_pt, angle), 0)
end
--]]

--[[
function stalker_look_firepoint_angle(stalker, whom, angle)
		--stalker - killer
		--whom - killed :)
	local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position())
	stalker:set_sight (look.fire_point, vector_rotate_y (look_pt, angle), 0)
end
--]]

local function door_default_callback(door, actor)
	local ph_shell = door:get_physics_shell()
	local joint = ph_shell:get_joint_by_bone_name("door")

	local low_limit = 0
	local hi_limit = 0
	low_limit, hi_limit = joint:get_limits(low_limit, hi_limit, 0)

	local angle = joint:get_axis_angle(0)
	if angle-low_limit > hi_limit - angle
	then
		joint:set_max_force_and_velocity(100, -1.5, 0)
	else
		joint:set_max_force_and_velocity(100, 1.5, 0)
	end
end

function door_init(door)
	door:set_use_callback(door_default_callback)
end

-- Дверь door импульсом захлопнется и залочится, при юзе будет вызываться callback_fn
function door_close_then_lock(door, callback_fn)
	local ph_shell = door:get_physics_shell()
	local joint = ph_shell:get_joint_by_bone_name("door")
	local low_limit = 0
	local hi_limit = 0
	low_limit, hi_limit = joint:get_limits(low_limit, hi_limit, 0)
	local angle = joint:get_axis_angle(0)
	if angle-low_limit > hi_limit - angle
	then
		joint:set_max_force_and_velocity(1000000, 0, 0)
	else
		joint:set_max_force_and_velocity(1000000, 0, 0)
	end
end

--unlock_then_open(door) -- дверь разлочится и импульсом откроется
--locked(door) -- true, если дверь закрыта и залочена

--При попытке поюзать незапертую дверь она просто силой открывается до максимально допустимого состояния.

function parse_waypoint_data(pathname, wpflags, wpname)
	local rslt = {}

	rslt.flags = wpflags

	local at
	if string.find(wpname, "|", at, true) == nil then
		return rslt
	end

	local par_num
	local fld
	local val

	par_num = 1
	for param in string.gfind(wpname, "([%w%+~_\\%=%{%}%s%!%-%,%*]+)|*") do
		if par_num == 1 then
			-- continue
		else
			if param == "" then
				abort("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname)
			end

			local t_pos = string.find(param, "=", 1, true)

			if t_pos == nil then
				printf("Path_param: %s", param)
				abort("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname)
			end

			fld = string.sub(param, 1,t_pos - 1)
			val = string.sub(param, t_pos + 1)

			if not fld or fld == "" then
				abort("path '%s': waypoint '%s': syntax error while parsing the param '%s': no field specified",
					pathname, wpname, param)
			end

			if not val or val == "" then
				val = "true"
			end

			if fld == "a" then
				rslt[fld] = xr_logic.parse_condlist(db.actor, "waypoint_data", "anim_state", val)
			else
				rslt[fld] = val
			end
		end
		par_num = par_num + 1
	end
	return rslt
end

function path_parse_waypoints(pathname)
	if not pathname then
		return nil
	end

	--printf("_bp: path_parse_waypoints: pathname='%s'", pathname)
	local ptr = patrol(pathname)
	local cnt = ptr:count()
	local rslt = {}

	for pt = 0, cnt - 1 do
		--printf("_bp: %s", ptr:name(pt))
		rslt[pt] = parse_waypoint_data(pathname, ptr:flags(pt), ptr:name(pt))
		if not rslt[pt] then
			abort("error while parsing point %d of path '%s'", pt, pathname)
		end

	end

	return rslt
end

function path_parse_waypoints_from_arglist(pathname, num_points, ...)
	local arg = {...}

	if not pathname then
		return nil
	end

	local ptr = patrol(pathname)
	local cnt = ptr:count()

	if cnt ~= num_points then
		abort("path '%s' has %d points, but %d points were expected", pathname, cnt, num_points)
	end

	local rslt = {}

	local cur_arg
	local fl
	for pt = 0, cnt-1 do
		cur_arg = arg[pt + 1]
		if not cur_arg then
			abort("script error [1] while processing point %d of path '%s'", pt, pathname)
		end
		fl = flags32()
		fl:assign(cur_arg[1])
		rslt[pt] = parse_waypoint_data(pathname, fl, cur_arg[2])
		if not rslt[pt] then
			abort("script error [2] while processing point %d of path '%s'", pt, pathname)
		end
	end

	return rslt
end

function action2(obj,...)
	local act = entity_action()
	local i = 1
	while true do
		if (arg[i] ~= nil) then
			act:set_action(arg[i])
		else
			break
		end
		i = i + 1
	end
	if (obj ~= nil) then
		obj:command(act,false)
	end
	return  entity_action(act)
end

function wpn_info_get(npc)
	local rslt = {}

	local active_item = npc:active_item()
	local has_weapon = active_item and isWeapon(active_item)
	if has_weapon then
		rslt["id"] = active_item:id()
		rslt["ammo"] = active_item:get_ammo_in_magazine()
	end

	return rslt
end

function wpn_info_equal(wpn_info1, wpn_info2)
	return wpn_info1["id"] == wpn_info2["id"] and wpn_info1["ammo"] == wpn_info2["ammo"]
end

function get_scheme_by_section(section)
	local scheme = string.gsub(section, "%d", "")
	local at, to = string.find(scheme, "@", 1, true)
	if at and to then
		scheme = string.sub(scheme, 1, at - 1)
	end
	return scheme
end

-- a | b | c  ==>  { 1 = "a", 2 = "b", 3 = "c" }
function parse_params(params)
	--printf("_bp: parse_params: params=%s", params)
	local rslt = {}
	local n = 1
	for fld in string.gfind(params, "%s*([^|]+)%s*") do
		--printf("_bp: parse_params iter=%d, fld=%s", n, fld)
		rslt[n] = fld
		n = n + 1
	end
	return rslt
end

function is_day()
	return level.get_time_hours() >= 5 and level.get_time_hours() < 22
end

function electro_art_enabled ()
		return level.get_time_hours() >= 0 and level.get_time_hours() < 5
end

function no_need_to_rotate(npc, target_pos)
	local y = yaw(npc:direction(), utils.vector_copy_by_val(target_pos):sub(npc:position()))

	return y < 0.3
end

function no_need_to_rotate_xz(npc, target_pos)
	local dir1 = npc:direction()
	dir1.y = 0
	local dir2 = utils.vector_copy_by_val(target_pos):sub(npc:position())
	dir2.y = 0
	local y = yaw(dir1, dir2)

	return y < 0.3
end

-- Перевод угла из радианов в градусы
function rad2deg(r)
	return r * 180.0 / math.pi
end

-- Перевод угла из градусов в радианы
function deg2rad(d)
	return d * math.pi / 180.0
end

-- угол между двумя векторами в градусах.
function angle_diff(a1, a2)
	local b1 = a1:normalize()
	local b2 = a2:normalize()
	local dotp = b1:dotproduct(b2)

	return rad2deg(math.acos(math.abs(dotp)))
end

-- true, если нужно поворачивать влево
function angle_left(dir1, dir2)
	local dir_res = vector()
	dir_res:crossproduct(dir1, dir2)
	return dir_res.y <= 0
end

function angle_left_xz(dir1, dir2)
	local dir_res = vector()
	dir1.y = 0
	dir2.y = 0
	dir_res:crossproduct(dir1, dir2)
	return dir_res.y <= 0
end

function get_nearest_waypoint(obj, pathname, ptr, cnt)
	local pt_chosen = nil
	local min_dist = nil
	local dist
	for i = 0, cnt - 1 do
		dist = obj:position():distance_to(ptr:point(i))
		if not min_dist or dist < min_dist then
			min_dist = dist
			pt_chosen = i
		end
	end
	if not pt_chosen then
		abort("object '%s': path '%s': get_nearest_waypoint: unable to choose a nearest waypoint (path has no waypoints?)",
			obj:name(), pathname)
	end
	return pt_chosen
end

function npc_in_zone(npc, zone)
	return npc ~= nil and zone ~= nil and zone:inside(npc:position())
end

------------------------- запись/загрузка CTime ---------------------------
local CTime_0

if not editor() then
	CTime_0 = game.CTime()
end

-- запись CTime в пакет. если t=nil, то запишет один нулевой байт
function w_CTime( p, t )
	--set_save_marker(p, "save", false, "CTIME")
	if t == nil then
		p:w_u8(-1)
		--set_save_marker(p, "save", true, "CTIME")
		return
	end

	if (CTime == nil) or (t ~= CTime_0) then
		local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
		Y, M, D, h, m, s, ms = t:get( Y, M, D, h, m, s, ms )

		p:w_u8 ( Y - 2000 )
		p:w_u8 ( M )
		p:w_u8 ( D )
		p:w_u8 ( h )
		p:w_u8 ( m )
		p:w_u8 ( s )
		p:w_u16( ms )
	else
		p:w_u8 ( 0 )
	end
	--set_save_marker(p, "save", true, "CTIME")
end

-- чтение CTime из пакета
function r_CTime( p )
	--set_save_marker(p, "load", false, "CTIME")
	local Y = p:r_u8()
	if Y == 255 then
		--set_save_marker(p, "load", true, "CTIME")
		return nil
	end

	if Y ~= 0 then
		local t = game.CTime()
		local M, D, h, m, s, ms = p:r_u8(), p:r_u8(), p:r_u8(), p:r_u8(), p:r_u8(), p:r_u16()
		t:set( Y + 2000,  M, D, h, m, s, ms)
		--set_save_marker(p, "load", true, "CTIME")
		return t
	else
		--set_save_marker(p, "load", true, "CTIME")
		return 0
	end
end
---------------------------------------------------------------------------

-- отослать в ближайшую разрешённую ноду по направлению к заданной.
-- возвращает vertex_id, в которое отправил персонажа
function send_to_nearest_accessible_vertex( npc, v_id )
	if not npc:accessible( v_id ) then
		local vtemp = vector()
		printf("vertex_position")
		v_id, vtemp = npc:accessible_nearest( level.vertex_position( v_id ), vtemp )
	end

	npc:set_dest_level_vertex_id( v_id )

	return v_id
end

-- происходит ли в данный момент смена уровня?
-- нужно для того, чтобы объекты знали, какую информацию записывать при сохранении, а какую нет
function level_changing()
	local sim = alife()

	if not sim then
		return false
	end

	local actor_gv = game_graph():vertex( sim:actor().m_game_vertex_id )

	return actor_gv:level_id() ~= sim:level_id()
end


function parse_data_1v(npc,s)
	local t = {}

		if s then
		for name in string.gfind( s, "(%|*%d+%|[^%|]+)%p*" ) do
--			printf("[%s]", utils.to_str(name))
			local dat = {	dist = nil,
							state = nil}

			local t_pos = string.find( name, "|", 1, true )

			local dist = string.sub( name, 1, t_pos - 1 )
			local state	= string.sub( name, t_pos + 1)

--			printf("	[%s]=[%s][%s]", utils.to_str(dist), utils.to_str(state), utils.to_str(sound))
			dat.dist = tonumber(dist)
			if state then
				dat.state = xr_logic.parse_condlist(npc, dist, state, state)
			end
			t[tonumber(dist)] = dat
		end
	end
		return t
end



--' Вычитка свойств для спауна объектов.
function parse_names(s)
		local t = {}
		for name in string.gfind( s, "([%w_%-.\\]+)%p*" ) do
				table.insert( t, name )
		end
		return t
end

function parse_spawns(str)
	--' если default-ов больше, чем значений в ini, то забить недостающие последним значением из ini
	local t = utils.parse_names(str)
	local n = #t

	local ret_table = {}
	local k = 1
	while k <= n do
		local spawn = {}
		spawn.section = t[k]
		--' Проверяем что это не последняя запись
		if t[k+1] ~= nil then
			local p = tonumber(t[k+1])
			--' проверяем что вторым числом задана вероятность, а не другая секция спавну
			if p then
				--' забиваем число
				spawn.prob = p
				k = k + 2
			else
				--' забиваем дефолт 1
				spawn.prob = 1
				k = k + 1
			end
		else
			spawn.prob = 1
			k = k + 1
		end
		table.insert(ret_table, spawn)

	end
	return ret_table
end

function r_2nums( spawn_ini, section, line, def1, def2 )
	if spawn_ini:line_exist( section, line ) then
		-- если default-ов больше, чем значений в ini, то забить недостающие последним значением из ini
		local t = utils.parse_names( spawn_ini:r_string( section, line ) )
		local n = #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


function parse_target(target)
		local pos = string.find(target, ",")
		if pos then
				return string.sub(target, 1, pos - 1), string.sub(target, pos + 1)
		else
				return target, nil
		end
end
