convert_npc = {}
start_game = nil
is_dbgmsg = true

is_debug = nil

--local timers={}   --хранит реал-тайм таймеры
--local g_timers={} --хранит таймеры в игровом времени
local timer_trigger=nil

pda_news = sound_object([[device\pda\pda_news]])
pda_tips = sound_object([[device\pda\pda_tip]])
pda_task = sound_object([[device\pda\pda_objective]])

tips_icons = {
	default  = "ui_iconsTotal_grouping",      -- ui_iconsTotal_grouping
	trader	 = "ui_iconsNpc_trader",    -- ui_iconsNpc_trader
	dolg     = "ui_iconsNpc_stalker_do_balon_4",
	freedom  = "ui_iconsTotal_grouping",
	ecolog   = "ui_iconsNpc_ecolog",      -- ui_iconsNpc_ecolog
	army     = "ui_iconsNpc_stalker_neytral_balon_6",    -- ui_iconsNpc_stalker_neytral_balon_6
	stalker  = "ui_iconsTotal_grouping",
	csky  	 = "ui_iconsTotal_grouping",
	krot     = "ui_iconsNpc_stalker_neytral_rukzak_4",     -- ui_iconsNpc_stalker_neytral_rukzak_4
	barman   = "ui_iconsNpc_barman",    -- ui_iconsNpc_barman
	wolf	 = "ui_iconsNpc_stalker_neytral_balon_1",    -- ui_iconsNpc_stalker_neytral_balon_1
	o_soznanie = "ui_iconsO_Soznanie",
	monolith = "ui_iconsTotal_grouping",
    saharov  = "ui_iconsNpc_ucheniy_2",    -- ui_iconsNpc_ucheniy_2
    prizrak  = "ui_iconsTotal_grouping",
    killer   = "ui_iconsTotal_grouping",
    bandit   = "ui_iconsTotal_grouping",
    renegade = "ui_iconsTotal_grouping",
	uniq  =  "ui_small_icon_border"    --{ 498, 188}
}
----------------
local bufferedmessages={}

function logct(msg,tag)
	if true and (tag and (tag=="mcbt" or tag=="temp")) then --(tag=="dram")) then
	    get_console():execute("load ~~~ "..string.sub(msg,1,200))		
	end
end

function rep(npc,msg,tag)
	if string.find(npc:name(),"gar_dm") then
		logct(msg,tag)
	end
end

function mylog(msg)
  if is_debug then 
	  if msg==nil then
	    return 
	  end
	  if db and db.actor then
	    if bufferedmessages then
			xr_sound.set_sound_play(db.actor:id(), "pda_tips")
			local texture = "ui_iconsTotal_grouping"
	      for k,v in ipairs(bufferedmessages) do
	        db.actor:give_game_news("Дебаг-Инфо:", game.translate_string(v), texture,  0, 15000)
	      end
	      bufferedmessages=nil
	    end
		db.actor:give_game_news("Дебаг-Инфо:", game.translate_string(msg), texture,  0, 15000)
	  else
	    if bufferedmessages then
	      table.insert(bufferedmessages,msg)
	    end
	  end

	  if get_console() then
	    get_console():execute("load ~~~ "..string.sub(msg,1,200))
	    get_console():execute("flush")
	  end

  end
end

--показываем типс
function send_tip(news_text, header, timeout, showtime, sender, sound)
  if news_text==nil then return end
  if header==nil then header=game.translate_string("st_tip") end
  if timeout == nil then timeout = 0 end
  if showtime == nil then showtime = 5 end
  
  local player
  if sound=="news" then
    player=pda_news
  elseif sound=="task" then
    player=pda_task
  else
    player=pda_tips
  end   
  
  --' Играем дефолтный звук
  -- player:play(db.actor, timeout, sound_object.s2d)
--  gps_habar.mylog(news_text)
  
  if sender == nil then
    sender = "default"
  end
  local ui_sender = tips_icons[sender]
  local news_text = "%c[default]"..news_text
  
  if db.actor and ui_sender then
	db.actor:give_game_news(header, game.translate_string(news_text), ui_sender, timeout*1000, showtime*1000)
  end
  return true
end

--старт таймера в реальном времени
function start_timer(name,delay,action)
  if not delay then
    return false
  end
  
  if not action then
    action = ""
  end

    local time = game.time() --time in seconds since 1970
  local a=1
  while db.storage[db.actor:id()].pstor["xt"..a] do
    a=a+1
    if a>100 then 
      return false 
    end
  end
  save_variable("xt"..a, name)
  save_variable("xd"..a, time+delay*1000*system_ini():r_float("alife","time_factor"))
  save_variable("xp"..a, action)

  return true
end

--старт таймера в игровом времени
function g_start_timer(name,delay_d,delay_h,delay_m,action)
    local time = level.get_time_days()*60*24+level.get_time_hours()*60+level.get_time_minutes()  --time in game minutes
  if delay_d==nil or delay_h==nil or delay_m==nil then
    return false
  end
  
  if action==nil then
    action = ""
  end

  local a=1
  while db.storage[db.actor:id()].pstor["gt"..a] do
    a=a+1
    if a>100 then 
      return false 
    end
  end

  save_variable("gt"..a, name)
  save_variable("gd"..a, time+delay_d*60*24+delay_h*60+delay_m)
  save_variable("gp"..a, action)

  return true
end

function has_timer(name)
  for a=1,100,1 do
    tmp=load_variable("xt"..a,nil)
    if tmp and tmp==name then
      return true
    end
  end  
  return false
end

function has_g_timer(name)
  for a=1,100,1 do
    tmp=load_variable("gt"..a,nil)
    if tmp and tmp==name then
      return true
    end
  end  
  return false
end

function convert_timers()
  if load_variable("tmcv",true) then
    for a=1,100,1 do
      tmp=load_variable("x_timer_"..a,nil)
      if tmp~=nil then
        local name,delay,params=tmp,load_variable("x_timer_"..a.."_delay",0),load_variable("x_timer_"..a.."_params","")
        del_variable("x_timer_"..a)
        del_variable("x_timer_"..a.."_delay")
        del_variable("x_timer_"..a.."_params")
        save_variable("xt"..a,name)
        save_variable("xd"..a,delay)
        save_variable("xp"..a,params)
      end
    end
    for a=1,100,1 do
      tmp=load_variable("x_gtimer_"..a,nil)
      if tmp~=nil then
        local name,delay,params=tmp,load_variable("x_gtimer_"..a.."_delay",0),load_variable("x_gtimer_"..a.."_params","")
        del_variable("x_gtimer_"..a)
        del_variable("x_gtimer_"..a.."_delay")
        del_variable("x_gtimer_"..a.."_params")
        save_variable("gt"..a,name)
        save_variable("gd"..a,delay)
        save_variable("gp"..a,params)
      end
    end
    save_variable("tmcv",false)
  end
end

--проверка таймеров, использует 3 следующие за ним функции для выбора действия
function check_timers()
  local tmp
  for a=1,100,1 do
    tmp=load_variable("xt"..a,nil)
    if tmp~=nil then
      __timer_found(a)  
    end
  end
  
  for a=1,100,1 do
    tmp=load_variable("gt"..a,nil)
    if tmp~=nil then
      __g_timer_found(a)  
    end
  end
end
function __timer_found(idx)
    local time = game.time() --time in seconds since 1970
  local name,params
  if load_variable("xd"..idx, nil)<=time then
    name=load_variable("xt"..idx, nil)
    params=load_variable("xp"..idx, nil)
    del_variable("xt"..idx)
    del_variable("xd"..idx)
    del_variable("xp"..idx)
		--oau_reason=name.." "..params
    __do_timer_action(name,params)
    return true
  end
  return false
end
function __g_timer_found(idx)
    local gtime = level.get_time_days()*60*24+level.get_time_hours()*60+level.get_time_minutes()  --time in game minutes
  local name,params
  if load_variable("gd"..idx, nil)<=gtime then
    name=load_variable("gt"..idx, nil)
    params=load_variable("gp"..idx, nil)
    del_variable("gt"..idx)
    del_variable("gd"..idx)
    del_variable("gp"..idx)
		--oau_reason=name.." "..params
    __do_timer_action(name,params)
    return true
  end
  return false
end
function __do_timer_action(select_string,params_string)
	if select_string=="punch" and watcher_act then
		watcher_act.punch_effect(tonumber(params_string))
	end		
end

--спавним объекты на карту
--для спавна неписей смотрим config\creatures\spawn_sections.ltx - там написаны имена секций для разных типов неписей
function spawn_item(spawn_item, pos, gv,lv)
  if gv==nil then gv=db.actor:game_vertex_id() end
  if lv==nil then lv=db.actor:level_vertex_id() end
  return alife():create(spawn_item, pos, lv, gv)
end

--для спавна патронов используем spawn_ammo_in_inv
function spawn_item_in_inv(spawn_item,npc)
  if npc==nil then 
    npc=db.actor 
  end
  return alife():create(spawn_item, 
      npc:position(),
      npc:level_vertex_id(),  
      npc:game_vertex_id(),
      npc:id())
end

--используем для спавна патронов
function spawn_ammo_in_inv(spawn_item,number,npc)
  if npc==nil then 
    npc=db.actor 
  end
  if number > 0 then
    return se_respawn.create_ammo(spawn_item, 
        npc:position(),
        npc:level_vertex_id(),  
        npc:game_vertex_id(),
        npc:id(),
        number)
  end
end

-- удаляем объект из игры
function remove_item(remove_item)
  if remove_item~=nil then
    alife():release(alife():object(remove_item:id()), true)
    return true
  end
  return false
end

-- выбрасываем объект из инвентаря, применимо к ГГ
function drop_item(npc,item)
  if item~=nil then
--      npc:mark_item_dropped(item)
    npc:drop_item(item)
  end
end

--убиваем непися
function make_suicide(npc)
  npc:kill(npc)
end

--узнаем отношение одного непися к другому
function get_npc_relation(obj,target)
  local rel = obj:relation(target)
  local relation
  if rel==game_object.neutral then
    relation="neutral"
  elseif rel==game_object.friend then
    relation="friend"
  elseif rel==game_object.enemy then
    relation="enemy"
  else
    return false
  end
  return relation
end

--задаем отношение одного непися к другому
function set_npc_relation(obj,target,relation)
  local rel
  if relation=="neutral" then
    rel=game_object.neutral
  elseif relation=="friend" then
    rel=game_object.friend
  elseif relation=="enemy" then
    rel=game_object.enemy
  else
    return false
  end 
  obj:set_relation(rel,target)
  return true
end

-- узнаем группировку непися, применимо к ГГ, только ОНЛАЙН
function get_npc_community(npc)
  return npc:character_community()
end

-- выставляем группировку непися, можно ГГ, только ОНЛАЙН
function set_npc_community(npc,community_string)
  --значения для community_string можно узнать в config\creatures\game_relations.ltx
  return npc:set_character_community(community_string, 0, 0)
end

--удаляем предмет из инвентаря по имени
function remove_item_from_inventory_by_name(remove_item_name,npc)
  return remove_item_from_inventory(npc:object(remove_item_name),npc)
end

--удаляем предмет из инвентаря
function remove_item_from_inventory(remove_item,npc)
  if npc==nil then npc=db.actor end
  if remove_item~=nil then
--      npc:mark_item_dropped(remove_item)
    alife():release(alife():object(remove_item:id()), true)
    return true
  end
  return false
end
--очищаем инвентарь непися, можно ГГ, использует следующую фунцию для удаления предмета
function clear_npc_inventory(npc)
    npc:iterate_inventory(__del_item, npc)
end
function __del_item(npc, item)
    local section = item:section()

    if section == "bolt" or section == "device_torch" then
        return false
    end
--    npc:mark_item_dropped(item)
    alife():release(alife():object(item:id()), true)
end
---------------------------
--проверка запущена ли игра
function check_game()
  if level.present() and (db.actor ~= nil) and db.actor:alive() then
    --start_game = true
	 return true
  end
  return false
end

--записываем переменную
function save_variable(variable_name, value)
  if value==nil then
    del_variable(variable_name)
  else
    xr_logic.pstor_store(db.actor, variable_name, value)
  end
end

--загружаем переменную
function load_variable(variable_name, value_if_not_found)
  return xr_logic.pstor_retrieve(db.actor, variable_name, value_if_not_found)
end

--удаляем переменную
function del_variable(variable_name)
  if db.storage[db.actor:id()].pstor[variable_name] then
    db.storage[db.actor:id()].pstor[variable_name] = nil
  end
end
--определяем находится ли ГГ в определенной зоне
function check_npc_in_box(npc, p1,p2,p3)
  local pos
  if npc.name then pos=npc:position() else pos=npc end
  if p3==nil then
    if is_point_inside_interval(pos.x,p1.x,p2.x) and
      is_point_inside_interval(pos.y,p1.y,p2.y) and
      is_point_inside_interval(pos.z,p1.z,p2.z) then
      return true
    else
      return false  
    end
  else
    local v1,v2,r,proj1,proj2,dv1,dv2
    v1=sub(p2,p1)
    v2=sub(p3,p2)
    v1.y=0 
    v2.y=0
    dv1=v1:magnitude()
    dv2=v2:magnitude()
    v1:normalize() 
    v2:normalize()
    r=sub(pos,p1)
    local v1p=vector():set(v1.z,0,-v1.x)
    proj2=v1p:dotproduct(r)/v1p:dotproduct(v2)
    proj1=v1:dotproduct(r)-v1:dotproduct(v2)*proj2
    if proj1>0 and proj1<dv1 and proj2>0 and proj2<dv2 and pos.y>p1.y and pos.y<p3.y then
      return true
    else
      return false
    end
  end
end

function is_point_inside_interval(x,p1,p2)
  if p1>p2 then 
    p1,p2 = p2,p1 
  end
  
  if x>p1 and x<p2 then 
    return true
  else 
    return false
  end
end

function sub(v1,v2)
  local newvec = vector()
  newvec.x = v1.x-v2.x
  newvec.y = v1.y-v2.y
  newvec.z = v1.z-v2.z
  return newvec
end
----------------------------

--инвентарное название объекта
function get_inv_name(section)
  return system_ini():r_string(section,"inv_name")
end

-- Внимание! Строки в структуре не должны содержать символов с кодами 0-31.
function pack_array_to_string(array)
  if not array then
    return "nil"
  end
  return string.char(1)..pack_new(array)
end

function unpack_array_from_string(str)
  if str==nil or str=="" or str=="nil" then return {} end
  if string.sub(str,1,1)~=string.char(1) then
    -- Старый формат упаковки
    return _parse(str)
  else
    -- новый формат упаковки тэгирован символом c кодом 1.
    return parse_new(string.sub(str,2,-1))
  end
end

function _assign(tbl,key,val)
  local key0=string.match(key,'"(.*)"')
  if key0 then
    tbl[key0]=val
  else
    tbl[key+0]=val
  end
end

local pack_type_num=1
local pack_type_string=2
local pack_type_bool=3
local pack_type_table=4
local pack_val_endtable=5

--[[
 Новый формат упаковки: 
 table ::= subtable
 subtable ::= keytype key valuetype ( value | subtable 0x5 )
 keytype ::= ( 0x1 | 0x2 | 0x3 | 0x4 )
 valuetype ::= ( 0x1 | 0x2 | 0x3 | 0x4 )
]]
function pack_new(tbl)
  local ret=""
  for k,v in pairs(tbl) do
    if type(k)=="number" then
      ret=ret..string.char(pack_type_num)..k
    elseif type(k)=="string" then
      ret=ret..string.char(pack_type_string)..k
    else
      abort("unsupported key type "..type(k))
    end
    if type(v)=="number" then
      ret=ret..string.char(pack_type_num)..v
    elseif type(v)=="string" then
      ret=ret..string.char(pack_type_string)..v
    elseif type(v)=="boolean" then
      ret=ret..string.char(pack_type_bool)..v
    elseif type(v)=="table" then
      ret=ret..string.char(pack_type_table)..pack_new(v)..string.char(pack_val_endtable)
    end
  end
  return ret
end

function parse_new(str,idx)
  local ret={}
  idx=idx or 1
  while true do
    local key,value
    if idx>string.len(str) then
      return ret,idx
    end
    vtype,idx=get_byte(str,idx)
    if vtype==pack_type_num then
      key,idx=get_num(str,idx)
    elseif vtype==pack_type_string then
      key,idx=get_string(str,idx)
    elseif vtype==pack_val_endtable then
      return ret,idx
    else
      abort("unsupported key type "..tostring(vtype))
    end
    vtype,idx=get_byte(str,idx)
    if vtype==pack_type_num then
      value,idx=get_num(str,idx)
    elseif vtype==pack_type_string then
      value,idx=get_string(str,idx)
    elseif vtype==pack_type_bool then
      value,idx=get_bool(str,idx)
    elseif vtype==pack_type_table then
      value,idx=parse_new(str,idx)
    else
      abort("unsupported key type "..tostring(vtype))
    end
    ret[key]=value
  end
end

function get_byte(str,idx)
  return string.byte(string.sub(str,idx,idx)),idx+1
end

function get_string(str,idx)
  local idx1=string.len(str)+1
  for i=idx,string.len(str),1 do
    if string.byte(string.sub(str,i,i))<32 then
      idx1=i
      break
    end
  end
  return string.sub(str,idx,idx1-1),idx1
end

function get_num(str,idx)
  local st,idx1=get_string(str,idx)
  return st+0,idx1
end

function get_bool(str,idx)
  local st,idx1=get_string(str,idx)
  return st=="1",idx1
end


function _parse(str)
  local ret={}
  while str and str~="" do
    local i1,i2,key=string.find(str,'(.-)=>')
    str=string.sub(str,i2+1)
    i1,i2,val=string.find(str,'"(.-)"|')
    if val and i1==1 then
      -- строка
      _assign(ret,key,val)
    else
      i1,i2,val=string.find(str,'(%b{})|')
      if val and i1==1 then
        -- таблица
        _assign(ret,key,_parse(string.sub(val,2,-2)))
      else
        i1,i2,val=string.find(str,'(.-)|')
        -- число или булево значение
        if val=="true" then
          _assign(ret,key,true)
        elseif val=="false" then
          _assign(ret,key,false)
        else
          _assign(ret,key,val+0)
        end
      end
    end
    str=string.sub(str,i2+1)
  end
  return ret
end

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

--колбэк на получение инфопоршена
function on_info(npc, info_id)
end

--колбэк на взятие предмета в инвентарь ГГ
function on_item_take(obj)
drop_outfits.dead_change(obj)
end

--колбэк на взятие предмета в инвентарь ГГ из ящика
function on_item_take_from_box(box, item)
end

--колбэк на потерю предмета из инвентаря ГГ
function on_item_drop(obj)
end

--колбек на переход непися в оффлайн
function on_npc_go_offline(npc)
end

--колбэк на апдейт ГГ (удобно для проверки условий, так как вызывается постоянно, нельзя перегружать, а то будут лаги)
function on_actor_update(self)
------------------------------------------------------
 --не удалять! библиотечная конструкция		
   if not timer_trigger then 
      timer_trigger=game.time() 
   end
   if timer_trigger<=game.time() then
      timer_trigger=game.time()+1000
      check_timers()
   end
 --не удалять! библиотечная конструкция		
------------------------------------------------------
	if start_game == nil and check_game() then
		start_game = true
	end
	if xrs_armor then 
		xrs_armor.run_scripts_actor(self) 
	end 
	if dynamic_hud then dynamic_hud.update() end
end

--колбэк на апдейт неписей
function on_npc_update(self)
	if xrs_armor then 
		xrs_armor.run_scripts_npc(self) 
	end 
end

--колбэк на смерть непися
function on_death(victim, who)
	drop_outfits.spawn_outfit(victim:id(),nil)
end

--хит-колбэк непися
function on_hit(obj, amount, local_direction, who, bone_index)
	drop_outfits.hit_check(obj, amount, local_direction, who, bone_index)
end		
		
--колбэк на юзание непися
function on_use(victim, who)
	if lootmoney and not victim:alive() then
		lootmoney.lootmoney(victim)
	end
	if victim:character_community()=="zombied" or string.find(victim:name(),"zombi") then
		zombie_checkup()
	end
end 

-- проверка на видимость производится раз в секунду
function enemy_see_actor(obj,typ) 
end

function actor_see_enemy(obj,typ) 
end

-- непись стрелял в гг
function npc_shot_actor(obj) 
end

--загружаем все переменные, которые нужно, вызывается загрузке игры, автоматически; вручную не вызывать
function on_game_load()
	if (offline_trade) then
		offline_trade.update_trade()
	end
end

--записываем все переменные, которые нужно, вызывается присохранении игры, автоматически; вручную не вызывать
function on_game_save() 
end

-- Эта функция вызывается самой первой. Онлайновые объекты недоступны! db.actor недоступен!
function on_game_start()
end

--------------------- user function section---------------

function load_table(name)
  local var=load_variable(name)
  return (var==nil and {}) or unpack_array_from_string(var)
end

function save_table(name,tbl)
  save_variable(name,pack_array_to_string(tbl))
end

function update_table(name,id,val)
  local tbl=load_table(name)
  tbl[id]=val
  save_table(name,tbl)
  return tbl
end

	
  local stpk=net_packet()
  local uppk=net_packet()

    -- id на входе
function read_stalker_params_money(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		sobj:STATE_Write(stpk) 
		stpk:r_seek(2)

		-- cse_alife_trader_abstract properties
		return stpk:r_s32()
	end
	return nil
end

    -- id на входе
function read_stalker_params_character_name(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		sobj:STATE_Write(stpk) 
		stpk:r_seek(2)

		-- cse_alife_trader_abstract properties
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_s32()
		stpk:r_s32()
		stpk:r_s32()

		return stpk:r_stringZ()
	end
	return nil
end

    -- id на входе
function read_stalker_params_visual(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		sobj:STATE_Write(stpk) 
		stpk:r_seek(2)

		-- cse_alife_trader_abstract properties
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_s32()
		stpk:r_s32()
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_u8()
		stpk:r_u8()

		-- cse_alife_object properties
		stpk:r_u16()
		stpk:r_float()
		stpk:r_s32()
		stpk:r_s32()
		stpk:r_s32()
		stpk:r_stringZ()
		stpk:r_s32()
		stpk:r_s32()

		-- cse_visual
		return stpk:r_stringZ()
	end
	return nil
end
  
    -- id на входе
function read_stalker_params(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		sobj:STATE_Write(stpk)
		sobj:UPDATE_Write(uppk)
		local size=stpk:w_tell()
		local size1=uppk:w_tell()

		stpk:r_seek(2)
		uppk:r_seek(2)
		
		local ret = {}

		-- cse_alife_trader_abstract properties
		ret.money					= stpk:r_s32()
		ret.specific_character	= stpk:r_stringZ()
		ret.trader_flags			= stpk:r_s32()
		ret.character_profile	= stpk:r_stringZ()
		ret.community_index		= stpk:r_s32()
		ret.rank						= stpk:r_s32()
		ret.reputation				= stpk:r_s32()
		ret.character_name		= stpk:r_stringZ()

		-- cse_alife_object properties
		ret.game_vertex_id		= stpk:r_u16()
		ret.distance				= stpk:r_float()
		ret.direct_control		= stpk:r_s32()
		ret.level_vertex_id		= stpk:r_s32()
		ret.object_flags			= stpk:r_s32()
		ret.custom_data			= stpk:r_stringZ()
		ret.story_id				= stpk:r_s32()
		ret.spawn_story_id		= stpk:r_s32()

		-- cse_visual
		ret.visual_name			= stpk:r_stringZ()
		
		ret.upd_extra_data=read_extra(uppk,size)
		ret.extra_data=read_extra(stpk,size)
		return ret
	end
	return nil
end
  
-- таблица параметров и id на входе
function write_stalker_params(ret,id,convert)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		
		-- cse_alife_trader_abstract properties
		stpk:w_s32(ret.money)
		stpk:w_stringZ(ret.specific_character)
		stpk:w_s32(ret.trader_flags)
		stpk:w_stringZ(ret.character_profile)
		stpk:w_s32(ret.community_index)
		stpk:w_s32(ret.rank)
		stpk:w_s32(ret.reputation)
		stpk:w_stringZ(ret.character_name)
  
		-- cse_alife_object properties
		stpk:w_u16(ret.game_vertex_id)
		stpk:w_float(ret.distance)
		stpk:w_s32(ret.direct_control)
		stpk:w_s32(ret.level_vertex_id)
		stpk:w_s32(ret.object_flags)
		stpk:w_stringZ(ret.custom_data)
		stpk:w_s32(ret.story_id)
		stpk:w_s32(ret.spawn_story_id)
  
		-- cse_visual
		stpk:w_stringZ(ret.visual_name)

		write_extra(uppk,ret.upd_extra_data) 
		write_extra(stpk,ret.extra_data)

		local size=stpk:w_tell()
		stpk:r_seek(2)
		uppk:r_seek(2)
		sobj:STATE_Read(stpk,size)
		sobj:UPDATE_Read(uppk)
		
		local obj=level.object_by_id(id)
		if obj and convert then
			if convert == 1 then
				convert_npc[id]="turn_off"	
			end
			if convert == 2 then
				convert_npc[id]="turn_on"	
			end
		end
	end
end

  -- id на входе
function read_stalker_params_old(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		sobj:STATE_Write(stpk)
		sobj:UPDATE_Write(uppk)
		local size=stpk:w_tell()
		local size1=uppk:w_tell()

		stpk:r_seek(2)
		uppk:r_seek(2)
		local tbl=parse_stalker_packet({},stpk,uppk,size,size1,sobj)
		return tbl
	end
	return nil
end

-- таблица параметров и id на входе
function write_stalker_params_old(tbl,id,convert)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		fill_stalker_packet(tbl,stpk,uppk,sobj)
		local size=stpk:w_tell()
		stpk:r_seek(2)
		uppk:r_seek(2)
		sobj:STATE_Read(stpk,size)
		sobj:UPDATE_Read(uppk)
		local obj=level.object_by_id(id)
		if obj and convert then
			if convert == 1 then
				convert_npc[id]="turn_off"	
			end
			if convert == 2 then
				convert_npc[id]="turn_on"	
			end
		end
	end
end

function read_outfit_params(id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		sobj:STATE_Write(stpk)
		sobj:UPDATE_Write(uppk)
		local size=stpk:w_tell()
		local size1=uppk:w_tell()
		stpk:r_seek(2)
		uppk:r_seek(2)
		local tbl=parse_outfit_packet({},stpk,uppk,size,size1)
		return tbl
	end
	return nil
end

function write_outfit_params(tbl,id)
	local sobj
	if type(id) == "number" then
		sobj = alife():object(id)
	else
		sobj = id
		id = sobj.id
	end
	if sobj then
		stpk:w_begin(0)
		uppk:w_begin(0)
		fill_outfit_packet(tbl,stpk,uppk)
		local size=stpk:w_tell()
		stpk:r_seek(2)
		uppk:r_seek(2)
		sobj:STATE_Read(stpk,size)
		sobj:UPDATE_Read(uppk)
	end
end

function parse_outfit_packet(ret,stpk,updpk,size,size1)
  parse_dynamic_object_visual(ret,stpk,updpk)
  parse_item_packet(ret,stpk,updpk)
  ret.upd_condition=updpk:r_u8() 
  ret.upd_extra_data=read_extra(updpk,size1)
  
  ret.imm=stpk:r_u8()
  ret.pid=stpk:r_s32()
  ret.con=stpk:r_float()
  --ret.none=stpk:r_stringZ()
  
  ret.extra_data=read_extra(stpk,size)
  return ret
end

function fill_outfit_packet(ret,stpk,updpk)
  fill_dynamic_object_visual(ret,stpk,updpk)
  fill_item_packet(ret,stpk,updpk)
  updpk:w_u8(ret.upd_condition)
  write_extra(updpk,ret.upd_extra_data)
    
  stpk:w_u8(ret.imm)
  stpk:w_s32(ret.pid)
  stpk:w_float(ret.con)
  --stpk:w_stringZ(ret.none)

  write_extra(stpk,ret.extra_data)
end

function parse_item_packet(ret,stpk,updpk)
  ret.condition=stpk:r_float()
  ret.upgrades=readvu32stringZ(stpk)
  ret.upd_num_items=updpk:r_u8()
  
  ret.float_data_item_packet=read_float(updpk,19)
  --[[ret.upd_ph_force={}
  ret.upd_ph_force.x=updpk:r_float()
  ret.upd_ph_force.y=updpk:r_float()
  ret.upd_ph_force.z=updpk:r_float()
  ret.upd_ph_torque={}
  ret.upd_ph_torque.x=updpk:r_float()
  ret.upd_ph_torque.y=updpk:r_float()
  ret.upd_ph_torque.z=updpk:r_float()
  ret.upd_ph_position={}
  ret.upd_ph_position.x=updpk:r_float()
  ret.upd_ph_position.y=updpk:r_float()
  ret.upd_ph_position.z=updpk:r_float()
  ret.upd_ph_rotation={}
  ret.upd_ph_rotation.a=updpk:r_float()
  ret.upd_ph_rotation.b=updpk:r_float()
  ret.upd_ph_rotation.c=updpk:r_float()
  ret.upd_ph_rotation.d=updpk:r_float()
  ret.upd_ph_angular_vel={}
  ret.upd_ph_angular_vel.x=updpk:r_float()
  ret.upd_ph_angular_vel.y=updpk:r_float()
  ret.upd_ph_angular_vel.z=updpk:r_float()
  ret.upd_ph_linear_vel={}
  ret.upd_ph_linear_vel.x=updpk:r_float()
  ret.upd_ph_linear_vel.y=updpk:r_float()
  ret.upd_ph_linear_vel.z=updpk:r_float()]]--
  
  ret.upd_ph_unknown=updpk:r_u8()
  return ret
end

function fill_item_packet(ret,stpk,updpk)
  stpk:w_float(ret.condition)
  writevu32stringZ(stpk,ret.upgrades)
  updpk:w_u8(ret.upd_num_items)
  
  write_float(updpk,ret.float_data_item_packet)
  --[[updpk:w_float(ret.upd_ph_force.x)
  updpk:w_float(ret.upd_ph_force.y)
  updpk:w_float(ret.upd_ph_force.z)
  updpk:w_float(ret.upd_ph_torque.x)
  updpk:w_float(ret.upd_ph_torque.y)
  updpk:w_float(ret.upd_ph_torque.z)
  updpk:w_float(ret.upd_ph_position.x)
  updpk:w_float(ret.upd_ph_position.y)
  updpk:w_float(ret.upd_ph_position.z)
  updpk:w_float(ret.upd_ph_rotation.a)
  updpk:w_float(ret.upd_ph_rotation.b)
  updpk:w_float(ret.upd_ph_rotation.c)
  updpk:w_float(ret.upd_ph_rotation.d)
  updpk:w_float(ret.upd_ph_angular_vel.x)
  updpk:w_float(ret.upd_ph_angular_vel.y)
  updpk:w_float(ret.upd_ph_angular_vel.z)
  updpk:w_float(ret.upd_ph_linear_vel.x)
  updpk:w_float(ret.upd_ph_linear_vel.y)
  updpk:w_float(ret.upd_ph_linear_vel.z)]]--
  
  updpk:w_u8(ret.upd_ph_unknown)
end

function parse_stalker_packet(ret,stpk,updpk,size,size1,sobj)
  parse_human_packet(ret,stpk,updpk)
  parse_skeleton_packet(ret,stpk,updpk)
  ret.upd_start_dialog=updpk:r_stringZ()
  ret.upd_extra_data=read_extra(updpk,size1)

  local t
  local newver=false
  t = stpk:r_u8()
  newver=(t>99)
  if newver then
       t=t-100
  end
  if t==0 or t==1 or t==2 then
      ret.job_online = t
  else
      ret.job_online = 3
	  ret.job_online_condlist=stpk:r_stringZ()
  end

  if newver then
	  ret.upgo=readvu32stringZ(stpk)
	  ret.upgh=readvu32stringZ(stpk)
	  ret.cono=stpk:r_float()
	  ret.conh=stpk:r_float()
	  ret.offmoney=stpk:r_s32()
	  ret.bon=read_bon(stpk)
  end
 
  ret.extra_data=read_extra(stpk,size)
  return ret
end

function fill_stalker_packet(ret,stpk,updpk,sobj)
  fill_human_packet(ret,stpk,updpk)
  fill_skeleton_packet(ret,stpk,updpk)
  updpk:w_stringZ(ret.upd_start_dialog)
  write_extra(updpk,ret.upd_extra_data)
  
  local t = ret.job_online
  if t==0 or t==1 or t==2 then
    stpk:w_u8(t+100)
  else
    stpk:w_u8(103)
    stpk:w_stringZ(ret.job_online_condlist)
  end
  
  writevu32stringZ(stpk,ret.upgo)
  writevu32stringZ(stpk,ret.upgh)
  stpk:w_float(ret.cono)
  stpk:w_float(ret.conh)
  stpk:w_s32(ret.offmoney)
  write_bon(stpk,ret.bon)
  
  write_extra(stpk,ret.extra_data)
end

function parse_human_packet(ret,stpk,updpk)
  parse_trader_packet(ret,stpk,updpk)
  parse_monster_packet(ret,stpk,updpk)
  ret.predicate5=readvu32u8(stpk)
  ret.predicate4=readvu32u8(stpk)  
  return ret
end

function fill_human_packet(ret,stpk,updpk)
  fill_trader_packet(ret,stpk,updpk)
  fill_monster_packet(ret,stpk,updpk)
  writevu32u8(stpk,ret.predicate5)
  writevu32u8(stpk,ret.predicate4)
end

function parse_skeleton_packet(ret,stpk,updpk)
  ret.skeleton_name=stpk:r_stringZ()
  ret.skeleton_flags=stpk:r_u8()
  ret.source_id=stpk:r_u16()
  if bit_and(ret.skeleton_flags, 4) then
	 ret.unk_n=readvu8u8(stpk)
	 ret.unk_n1 = stpk:r_u16()
	 
	 ret.float_data_skeleton_packet=read_float(stpk,6)
	 --[[ret.unk_n2={}
	 ret.unk_n2.x = stpk:r_float()
	 ret.unk_n2.y = stpk:r_float()
	 ret.unk_n2.z = stpk:r_float()
	 ret.unk_n3={}
	 ret.unk_n3.x = stpk:r_float()
	 ret.unk_n3.y = stpk:r_float()
	 ret.unk_n3.z = stpk:r_float()]]--
	 
	 ret.unk_n4 = readvu16u64(stpk)
  end  
  return ret
end

function fill_skeleton_packet(ret,stpk,updpk)
  stpk:w_stringZ(ret.skeleton_name)
  stpk:w_u8(ret.skeleton_flags)
  stpk:w_u16(ret.source_id)
  if bit_and(ret.skeleton_flags, 4) then
	 writevu8u8(stpk,ret.unk_n)
	 stpk:w_u16(ret.unk_n1)
	 
	 write_float(stpk,ret.float_data_skeleton_packet)
	 --[[stpk:w_float(ret.unk_n2.x)
	 stpk:w_float(ret.unk_n2.y)
	 stpk:w_float(ret.unk_n2.z)
	 stpk:w_float(ret.unk_n3.x)
	 stpk:w_float(ret.unk_n3.y)
	 stpk:w_float(ret.unk_n3.z)]]--
	 
    writevu16u64(stpk,ret.unk_n4)
  end
end

function parse_trader_packet(ret,stpk,updpk)
  ret.money=stpk:r_s32()
  ret.specific_character=stpk:r_stringZ()
  ret.trader_flags=stpk:r_s32()
  ret.character_profile=stpk:r_stringZ()
  ret.community_index=stpk:r_s32()
  ret.rank=stpk:r_s32()
  ret.reputation=stpk:r_s32()
  ret.character_name=stpk:r_stringZ()  
  return ret
end

function fill_trader_packet(ret,stpk,updpk)
  stpk:w_s32(ret.money)
  stpk:w_stringZ(ret.specific_character)
  stpk:w_s32(ret.trader_flags)
  stpk:w_stringZ(ret.character_profile)
  stpk:w_s32(ret.community_index)
  stpk:w_s32(ret.rank)
  stpk:w_s32(ret.reputation)
  stpk:w_stringZ(ret.character_name)
end

function parse_monster_packet(ret,stpk,updpk)
  parse_creature_packet(ret,stpk,updpk)
  ret.base_out_restrictors=stpk:r_stringZ()
  ret.base_in_restrictors=stpk:r_stringZ()
  ret.smart_terrain_id=stpk:r_u16()
  ret.smart_terrain_task_active=stpk:r_u8()
  
  ret.upd_next_game_vertex_id=updpk:r_u16()
  ret.upd_prev_game_vertex_id=updpk:r_u16()
  ret.upd_distance_from_point=updpk:r_float()
  ret.upd_distance_to_point=updpk:r_float()
  return ret
end

function fill_monster_packet(ret,stpk,updpk)
  fill_creature_packet(ret,stpk,updpk)
  stpk:w_stringZ(ret.base_out_restrictors)
  stpk:w_stringZ(ret.base_in_restrictors)
  stpk:w_u16(ret.smart_terrain_id)
  stpk:w_u8(ret.smart_terrain_task_active)
  
  updpk:w_u16(ret.upd_next_game_vertex_id)
  updpk:w_u16(ret.upd_prev_game_vertex_id)
  updpk:w_float(ret.upd_distance_from_point)
  updpk:w_float(ret.upd_distance_to_point)
end

function parse_creature_packet(ret,stpk,updpk)
  parse_dynamic_object_visual(ret,stpk,updpk)
  ret.g_team=stpk:r_u8()
  ret.g_squad=stpk:r_u8()
  ret.g_group=stpk:r_u8()
  ret.health=stpk:r_float()
  ret.dynamic_out_restrictions=readvu32u16(stpk)
  ret.dynamic_in_restrictions=readvu32u16(stpk)  
  ret.killer_id=stpk:r_u16()
  ret.game_death_time=readvu8u8(stpk)

  ret.upd_health=updpk:r_float()
  ret.upd_timestamp=updpk:r_s32()
  ret.upd_creature_flags=updpk:r_u8()
  
  --ret.float_data_creature_packet=read_float(updpk,7)
  ret.upd_position={}
  ret.upd_position.x=updpk:r_float()
  ret.upd_position.y=updpk:r_float()
  ret.upd_position.z=updpk:r_float()
  ret.upd_o_model=updpk:r_float()
  ret.upd_o_torso={}
  ret.upd_o_torso.x=updpk:r_float()
  ret.upd_o_torso.y=updpk:r_float()
  ret.upd_o_torso.z=updpk:r_float()
  
  ret.upd_g_team=updpk:r_u8()
  ret.upd_g_squad=updpk:r_u8()
  ret.upd_g_group=updpk:r_u8()  
  return ret
end

function fill_creature_packet(ret,stpk,updpk)
  fill_dynamic_object_visual(ret,stpk,updpk)
  stpk:w_u8(ret.g_team)
  stpk:w_u8(ret.g_squad)
  stpk:w_u8(ret.g_group)
  stpk:w_float(ret.health)
  writevu32u16(stpk,ret.dynamic_out_restrictions)
  writevu32u16(stpk,ret.dynamic_in_restrictions)  
  stpk:w_u16(ret.killer_id)
  writevu8u8(stpk,ret.game_death_time)

  updpk:w_float(ret.upd_health)
  updpk:w_s32(ret.upd_timestamp)
  updpk:w_u8(ret.upd_creature_flags)
  
  --write_float(updpk,ret.float_data_creature_packet)
  updpk:w_float(ret.upd_position.x)
  updpk:w_float(ret.upd_position.y)
  updpk:w_float(ret.upd_position.z)
  updpk:w_float(ret.upd_o_model)
  updpk:w_float(ret.upd_o_torso.x)
  updpk:w_float(ret.upd_o_torso.y)
  updpk:w_float(ret.upd_o_torso.z)
  
  updpk:w_u8(ret.upd_g_team)
  updpk:w_u8(ret.upd_g_squad)
  updpk:w_u8(ret.upd_g_group)
end

function parse_object_packet(ret,stpk,updpk)
  ret.game_vertex_id=stpk:r_u16()
  ret.distance=stpk:r_float()
  ret.direct_control=stpk:r_s32()
  ret.level_vertex_id=stpk:r_s32()
  ret.object_flags=stpk:r_s32()
  ret.custom_data=stpk:r_stringZ()
  ret.story_id=stpk:r_s32()
  ret.spawn_story_id=stpk:r_s32()
  return ret
end

function fill_object_packet(ret,stpk,updpk)
  stpk:w_u16(ret.game_vertex_id)
  stpk:w_float(ret.distance)
  stpk:w_s32(ret.direct_control)
  stpk:w_s32(ret.level_vertex_id)
  stpk:w_s32(ret.object_flags)
  stpk:w_stringZ(ret.custom_data)
  stpk:w_s32(ret.story_id)
  stpk:w_s32(ret.spawn_story_id)
end

function parse_visual_packet(ret,stpk,updpk)
  ret.visual_name=stpk:r_stringZ()
  ret.visual_flags=stpk:r_u8()
  return ret
end

function fill_visual_packet(ret,stpk,updpk)
  stpk:w_stringZ(ret.visual_name)
  stpk:w_u8(ret.visual_flags)
end

function parse_dynamic_object_visual(ret,stpk,updpk)
  parse_object_packet(ret,stpk,updpk)
  parse_visual_packet(ret,stpk,updpk)
  return ret
end

function fill_dynamic_object_visual(ret,stpk,updpk)
  fill_object_packet(ret,stpk,updpk)
  fill_visual_packet(ret,stpk,updpk)
end

function read_extra(packet,size)
  local v={}
  for i=packet:r_tell(),size-1,1 do
	  table.insert(v,packet:r_u8())
  end
  return v
end

function read_float(packet,size)
  local v={}
  for i=1,size,1 do
	  table.insert(v,packet:r_float())
  end
  return v
end

function readvu16u64(packet)
  local v={}
  local len=packet:r_u16()
  for i=1,len*8,1 do
    table.insert(v,packet:r_u8())
  end
  return v
end

function readvu32u8(packet)
  local v={}
  local len=packet:r_s32()
  for i=1,len,1 do
    table.insert(v,packet:r_u8())
  end
  return v
end

function readvu8u8(packet)
  local v={}
  for i=1,8,1 do
    table.insert(v,packet:r_u8())
  end
  return v
end

function read_bon(packet)
  local v={}
  for i=1,5,1 do
    table.insert(v,packet:r_u8())
  end
  return v
end

function readvu32u16(packet)
  local v={}
  local len=packet:r_s32()
  for i=1,len,1 do
  local readvu32u16_par = packet:r_u16()
    table.insert(v,readvu32u16_par)
  end
  return v
end

function readvu32stringZ(packet)
  local v={}
  local len=packet:r_s32()
  for i=1,len,1 do
    table.insert(v,packet:r_stringZ())
  end
  return v
end

function write_extra(pk,v)
	for i,k in ipairs(v) do
		pk:w_u8(k)
	end 
end

function write_float(pk,v)
	for i,k in ipairs(v) do
		pk:w_float(k)
	end 
end

function writevu16u64(pk,v)
  local len=#v
  pk:w_u16(len/8)
  for i=1,len,1 do
    pk:w_u8(v[i])
  end
end

function writevu32u8(pk,v)
  local len=#v
  pk:w_s32(len)
  for i=1,len,1 do
    pk:w_u8(v[i])
  end
end

function writevu8u8(pk,v)
  for i=1,8,1 do
    pk:w_u8(v[i])
  end
end

function write_bon(pk,v)
  if v==nil then v={} end
  for i=1,5,1 do
    pk:w_u8(v[i])
  end
end

function writevu32u16(pk,v)
  local len=#v
  pk:w_s32(len)
  for i=1,len,1 do
    pk:w_u16(v[i])
  end
end

function writevu32stringZ(pk,v)
  if v==nil then v={} end
  local len=#v
  pk:w_s32(len)
  for i=1,len,1 do
    pk:w_stringZ(v[i])
  end
end

function convert_npcs()
	for k,v in pairs(convert_npc) do
		local obj=level.object_by_id(k)
		local sobj=alife():object(k)
		if sobj then
			if obj==nil and v=="turn_on" then
				--sobj.job_online = true
				sobj.sim_forced_online = true
				if sobj.online ~= true then
					sobj:switch_online()
				end
				convert_npc[k]=false
					
			elseif obj and v=="turn_off" then
				if IsStalker(sobj) then
					obj:clear_animations()
					if sobj:alive() then
						obj:stop_talk()
					end
				end
				--switch_offline(obj)
				sobj:switch_offline()
				convert_npc[k]="turn_on"
				break
					
			elseif obj~=nil and v=="transfer_outfit" then
				local npc = level.object_by_id(sobj.pid)
				if npc then
					obj:transfer_item(obj,npc)
				else
					alife():release(sobj, true)
				end
				convert_npc[k]=false
					
			elseif obj~=nil and v=="transfer_helmet" then
				local npc = level.object_by_id(sobj.pid)
				if npc then
					obj:transfer_item(obj,npc)
				else
					alife():release(sobj, true)
				end
				convert_npc[k]=false
					
			elseif obj and v==false then
				if IsStalker(sobj) and not sobj:alive() then
					local h = hit()
					h.draftsman = obj
					h.type = hit.strike
					h.direction = obj:direction()
					h:bone("bip01_spine")
					h.power = 100
					h.impulse = 200
					obj:hit(h)
				end
				convert_npc[k]=nil
			end
		else
			convert_npc[k]=nil
		end
	end
end

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

--для правильного парсинга запрещены комментарии!!!
function parse_custom_data(str)
	local t={}
	if str then
		for section, section_data in string.gfind(str,"%s*%[([^%]]*)%]%s*([^%[%z]*)%s*") do
			section = trim(section)
			t[section]={}
			for line in string.gfind(trim(section_data), "([^\n]*)\n*") do
				if string.find(line,"=")~=nil then
					for k, v in string.gfind(line, "([^=]-)%s*=%s*(.*)") do
						k = trim(k)
						if k~=nil and k~='' and v~=nil then 
							t[section][k]=trim(v) 
						end
					end
				else
					for k, v in string.gfind(line, "(.*)") do
						k = trim(k)
						if k~=nil and k~='' then
							t[section][k]="<<no_value>>" 
						end
					end
				end
			end
		end
	end
	return t
end

function parse_ini_section_to_array(ini,section)
	local tmp = {}
	if ini:section_exist(section) then
		local result, id, value = nil, nil, nil
		for a=0,ini:line_count(section)-1 do
			result, id, value = ini:r_line(section,a,"","")
			if id ~= nil and trim(id) ~= "" and trim(id) ~= nil then
				tmp[trim(id)] = trim(value)
			end
		end
	else
		printf("parse_ini_section_to_array:Error! <no such section>")
	end
	return tmp
end

function str_explode(div,str,clear)
	local t={}
	local cpt = string.find (str, div, 1, true)
	if cpt then
		repeat
			if clear then
				table.insert( t, trim(string.sub(str, 1, cpt-1)) )
			else
				table.insert( t, string.sub(str, 1, cpt-1) )
			end
			str = string.sub( str, cpt+string.len(div) )
			cpt = string.find (str, div, 1, true)
		until cpt==nil
	end
	if clear then
		table.insert(t, trim(str))
	else
		table.insert(t, str)
	end
	return t
end

function trim (s)
	return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end

function gen_custom_data(tbl)
	local str=''
	for key, value in pairs(tbl) do
		str = str.."\n["..key.."]\n"
		for k, v in pairs(value) do
			if v~="<<no_value>>" then
				str=str..k.." = "..v.."\n"
			else
				str=str..k.."\n"
			end
		end
	end
	return str
end 

function dump_table(tbl)
  for k,v in pairs(tbl) do
    if type(v)=="table" then
      get_console():execute("load ~~~ "..tostring(k).." => ")
      dump_table(v)
    else
      str="load ~~~ "..tostring(k).." => "..tostring(v)
      if string.len(str)>200 then
        str=string.sub(str,1,200)
      end
      get_console():execute(str)
    end
  end
  get_console():execute("flush")
end

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

function read_if_exist_num(ltx, section, value, default)
	if ltx:line_exist(section,value) then
		return ltx:r_u32(section,value)
	else
		return default
	end
end

function read_if_exist_str(ltx, section, value, default)
	if ltx:line_exist(section,value) then
		return ltx:r_string(section,value)
	else
		return default
	end
end

function add_spot_on_map(obj_id,type,text)
	-- возможные типы type см. в ui\map_spots.xml
	if obj_id then
		if text == nil then text = " " end
		-- Ставим метку на серверный объект чтобы её не пришлось обновлять
		level.map_add_object_spot_ser(obj_id, type, text)
	end
end
function remove_spot_from_map(obj_id,type)
	if obj_id and level.map_has_object_spot(obj_id, type) ~= 0 then
		level.map_remove_object_spot(obj_id, type)
	end
end

function quotemeta(str)
	return (string.gsub(s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1"))
end

function add(v1,v2)
	local nv=vector()
	nv.x=v1.x+v2.x
	nv.y=v1.y+v2.y
	nv.z=v1.z+v2.z
	return nv
end

function set_len(v,num)
	local cl = math.sqrt(v.x*v.x+v.y*v.y+v.z*v.z)
	cl = num/cl
	v.x= v.x*cl
	v.y= v.y*cl
	v.z= v.z*cl
	return v
end

function angle_diff(a1, a2)
	local b1 = a1:normalize()
	local b2 = a2:normalize()
	local dotp = b1:dotproduct(b2)
	return utils.rad2deg(math.acos(dotp))
end

local sound_object_by_path = {}
--' Обертка вокруг функции, возвращающий звуковой объект.
function get_safe_sound_object(path)
	if sound_object_by_path[path] == nil then
		sound_object_by_path[path] = sound_object(path)
	end
	return sound_object_by_path[path]
end

function IsTrader(npc)
	local npc_id
	local com
	-- HACK
	if npc.m_story_id ~= nil then
		npc_id = npc.id
		com = npc:community()
	else
		npc_id = npc:id()
		com = npc:character_community()
	end
	if com == "trader" then return true end

	--[[if npc.smart_terrain_id and npc:smart_terrain_id() and npc:smart_terrain_id()~=65535 then
		local sm = alife():object(npc:smart_terrain_id())
		if sm and sm.gulag and sm.gulag.ltx and sm.gulag.ObjectJob and sm.gulag.Job then
			local jobid = sm.gulag.ObjectJob[npc_id]
			if jobid then
				local job = sm.gulag.Job[jobid]
				if job and job.section and sm.gulag.ltx:section_exist(job.section) and sm.gulag.ltx:line_exist(job.section, "trade") then
					return true
				end
			end
		end
	end]]
	
	local char_ini = xr_logic.get_customdata_or_ini_file(npc, "<customdata>")
	if char_ini and char_ini:section_exist("logic") and char_ini:line_exist("logic", "trade") then
		return true
	end
	
	return false
end

function get_addon_status(wpn,addon)
	if addon == "sc" then
		return wpn:weapon_scope_status()
	elseif addon == "sl" then
		return wpn:weapon_silencer_status()
	elseif addon == "gl" then
		return wpn:weapon_grenadelauncher_status()
	end
	return 0
end

local rep_mem = {}

function get_addon_flag(wpn,with_int)
	local id,tg = wpn:id(),time_global()
	if tg == rep_mem.tg and rep_mem.id == id and rep_mem.wi == with_int then
		return rep_mem.fl
	else
		local flag = 0
		if wpn:weapon_is_scope() then
			if with_int or get_addon_status(wpn,"sc") == 2 then
				flag = 1
			end
		end
		if wpn:weapon_is_silencer() then
			if with_int or get_addon_status(wpn,"sl") == 2 then
				flag = flag+4
			end
		end
		if wpn:weapon_is_grenadelauncher() then
			if with_int or get_addon_status(wpn,"gl") == 2 then
				flag = flag+2
			end
		end
		rep_mem = {tg = tg,id = id,wi = with_int,fl = flag}
		return flag
	end
end

function addon_attached(wpn,addon,flag)
	local ft = {sc = {[1] = true,[3] = true,[5] = true,[7] = true},
				sl = {[4] = true,[5] = true,[6] = true,[7] = true},
				gl = {[2] = true,[3] = true,[6] = true,[7] = true}}
	if ft[addon] then
		return ft[addon][flag or get_addon_flag(wpn,true)] == true
	end
	return false
end

function get_visual(object)
	local obj
	if type(object) == "number" then
		obj = level.object_by_id(object)
	else
		obj = object
	end
	local vis = obj:get_visual_name()
	if vis and vis ~= "" then
		vis = imm.str_explode("\\",vis)
		vis = imm.str_explode(".",vis[3])
		vis = vis[1]
		return vis
	end
end

function get_visual__(object)
	local sobj
	if type(object) == "number" then
		sobj = alife():object(object)
	else
		sobj = object
	end
	local t
	if IsStalker(sobj) then
		t = imm.read_stalker_params_visual(sobj)
	else 
		t = imm.read_outfit_params(sobj)
	end
	if t then
		local vis = t.visual_name
		if vis and vis ~= "" then
			vis = imm.str_explode("\\",vis)
			vis = imm.str_explode(".",vis[3])
			vis = vis[1]
			return vis
		end
	end
end
function zombie_checkup()
	local actor = db.actor
	if math.random() > 0.7 then
		local a=math.random()
		local h = hit()
		h.draftsman = actor
		h.type = hit.telepatic
		h.power = 0.3+a/30
        h.impulse = 0.0
	    h.direction = vector():set(0,0,1)
		actor:hit(h)
		h.type = hit.radiation
		h.power = 0.3+a/30
		actor:hit(h)
		level.add_pp_effector("alcohol.ppe", 100, false)
		local rnd=math.random(1,6)
		local snd_obj
		
		if rnd==1 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_1]])
		elseif rnd==2 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_2]])
		elseif rnd==3 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_3]])
		elseif rnd==4 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_4]])
		elseif rnd==5 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_5]])
		elseif rnd==6 then
			snd_obj = 	get_safe_sound_object([[characters_voice\human_01\stalker\fight\hit\hit_6]])
		end
		snd_obj:play_no_feedback(actor, sound_object.s2d, 0, vector(), 1.0)
	end
end


function dbglog(fmt,...)    
local msg = string.format(fmt, ...)    
local msg_no_ws = string.gsub(msg, "%s", "_" )  
get_console():execute("IMM:"..msg_no_ws)  
--get_console():execute("IMM_ERROR:" .. msg_no_ws)
--get_console():execute("flush")
end
