----------------------------------------------
-- Copyright (c) 2007 Dmitry 'Red75' Roschin                                              
----------------------------------------------
-- Mercenaries v0.1 beta Behavioral scheme 
----------------------------------------------

property_base=12221
property_faraway=property_base
property_farfaraway=property_base+1
property_actor_is_wounded=property_base+2
property_i_am_healer=property_base+3
property_too_far_to_heal=property_base+4
act_base=13332
act_stay_close=act_base
act_return_to_actor=act_base+1
act_run_to_wounded=act_base+2
act_heal_wounded=act_base+3

local min_faraway_dist=2
local max_faraway_dist=6
local max_farfaraway_dist=10

--tc=this.team_coordinator({})

local excluded_npc = {
bar_dolg_leader = true,
bar_dolg_petrenko = true,
bar_zastava_guard_1 = true,
bar_zastava_guard_2 = true,
bar_zastava_guard_3 = true,
bar_zastava_commander_1 = true,
bar_zastava_2_square_1_guard_4 = true,
bar_zastava_2_square_1_guard_2 = true,
bar_zastava_2_square_1_guard_6 = true,
sim_stalker_prut = true,
mil_freedom_member0012 = true,
mil_freedom_member0021 = true,
mil_freedom_member0005 = true,
mil_stalker_0003 = true,
mil_freedom_member0026 = true,
mil_freedom_member0024 = true,
mil_freedom_member0025 = true,
mil_freedom_stalker0010 = true,
mil_freedom_stalker0004 = true,
mil_blockpost_freedomstalker001 = true,
mil_stalker0012 = true,
esc_bridge_soldier5 = true,
esc_bridge_soldier4 = true,
esc_bridge_soldier3 = true,
esc_bridge_soldier2 = true,
esc_bridge_soldier1 = true,
esc_blokpost_commander = true,
aes_soldier_commander = true,
agr_nii_commander = true,
bar_freedom_attacker_commander = true,
gar_bandit_agr_6 = true,
val_rob_leader = true,
val_lager_bandits_borov = true,
pri_monolith_leader = true
}

class "evaluator_i_am_healer" (property_evaluator)

function evaluator_i_am_healer:__init(name, storage) super(nil,name)
  self.st=storage
end

function evaluator_i_am_healer:evaluate()
  return db.tc:is_healer(self.object)
end

class "evaluator_actor_is_wounded" (property_evaluator)

local actor_wounded_threshold=0.5

function evaluator_actor_is_wounded:__init(name, storage) super(nil,name)
  self.st=storage
end

function evaluator_actor_is_wounded:evaluate()
  if (not self.updtime) or self.updtime<time_global() then
    self.updtime=time_global()+1000
--    mylog("action for " .. self.object:character_name() .. " is " .. self.object:motivation_action_manager():current_action_id())
  end
  if self.st==nil or self.st.enabled~=true then
  -- модель поведения запрещена
    return false
  end
  if (not db.actor) or (not db.actor:alive()) then
    return false
  end
  -- Достал меня этот state_mgr. Попробую так
  if self.st.temp_disable and self.st.temp_disable>time_global() then
    return false
  elseif self.st.temp_disable then
    self.st.temp_disable=nil
  end
  if db.actor then
    local wounded=db.actor.health<actor_wounded_threshold
    if wounded then
    -- Назначим лекаря
      db.tc:assign_healer(db.actor)
    end
    return self.st.heal_state~=nil or (wounded and db.tc:is_healer(self.object) and db.actor:alive())
  else
    return false
  end
end

class "evaluator_too_far_to_heal" (property_evaluator)

function evaluator_too_far_to_heal:__init(name, storage) super(nil,name)
  self.st=storage
end

function evaluator_too_far_to_heal:evaluate()
  if self.st==nil or self.st.enabled~=true then
  -- модель поведения запрещена
    return false
  end
  if db.actor then
    return self.object:position():distance_to(db.actor:position())>2
  else
    return true
  end
end

class "evaluator_farfaraway" (property_evaluator)

function evaluator_farfaraway:__init(name, storage) super (nil, name)
  self.st = storage
end

function evaluator_farfaraway:evaluate()
  if self.st==nil or self.st.enabled~=true then
  -- модель поведения запрещена
    return false
  end
  -- Достал меня этот state_mgr. Попробую так
  if self.st.temp_disable and self.st.temp_disable>time_global() then
    return false
  elseif self.st.temp_disable then
    self.st.temp_disable=nil
  end
--[[
  if self.last_rep==nil or self.last_rep<time_global() then
    self.last_rep=time_global()+5000+math.random(0,1000)
    local manager=self.object:motivation_action_manager()
    mylog(self.object:character_name() .. " " .. tostring(manager:current_action_id()))
    local ge=self.object:get_enemy()
    local be=self.object:best_enemy()
    local mem_time=((be==nil) and "nil") or self.object:memory_time(be)
    if be then
      mylog("be " .. be:character_name() .. " " .. be.health .. " memtime " .. tostring(mem_time) .. " gt " .. time_global())
    end
    if ge then
      mylog("ge " .. ge:character_name() .. " " .. ge.health)
    end
  end
]]
  if self.st==nil or self.st.enabled~=true then
  -- модель поведения запрещена
    return false
  end
  local actor=db.actor
  if not actor then
  -- ГГ ещё не заспаунился
    return false
  end
  local dist=db.tc:get_center_point():distance_to(self.object:position())
  if dist>max_farfaraway_dist then
    self.farfaraway=true
  elseif dist<max_faraway_dist then
    self.farfaraway=false
  end
  return self.farfaraway==true
end

class "evaluator_faraway" (property_evaluator)

function evaluator_faraway:__init(name, storage) super (nil, name)
  self.st = storage
end

function evaluator_faraway:evaluate()
  if self.st==nil or self.st.enabled~=true then
  -- модель поведения запрещена
    return false
  end
  -- Достал меня этот state_mgr. Попробую так
  if self.st.temp_disable and self.st.temp_disable>time_global() then
    return false
  elseif self.st.temp_disable then
    self.st.temp_disable=nil
  end
  local actor=db.actor
  if not actor then
  -- ГГ ещё не заспаунился
    return false
  end
  --  Проверим не стал ли NPC врагом ГГ, если да, то выкидываем его из команды
  if self.object:relation(actor)==game_object.enemy or xr_wounded.is_wounded(self.object) then
    actor:give_game_news(self.object:character_name() .. " won't help you anymore!", "ui\\ui_iconsTotal", Frect():set(0,235,83,47), 0, 15000)
    disable_scheme(self.object,"actor_need_help")
    return false
  end
  if self.object:best_enemy() then
    return false -- Блокирование следования за ГГ во время боя
  end
  local dist=db.tc:get_center_point():distance_to(self.object:position())
  if dist>max_faraway_dist then
    self.st.faraway=true
  elseif dist<min_faraway_dist then
    self.st.faraway=false
  end
  self.st.dist=dist
  return self.st.faraway==true
end

class "action_stay_close" (action_base)

function action_stay_close:__init(name, storage) super (nil, name)
  self.st=storage
  self.prevstp=""
end

local mov_type=0

function action_stay_close:initialize()
  action_base.initialize(self)
--  mylog("action init")
  local npc=self.object
  -- Не знаю зачем эти две функции, но они используются во всех операторах
  npc:set_desired_position()
  npc:set_desired_direction()

  -- Задаём параметры движения
  npc:set_detail_path_type(move.line)
  npc:set_path_type(game_object.level_path)

  -- Освободим сталкера от всех ограничений
  if get_movstate()~=1 then
  npc:remove_all_restrictions()
  end
  
  if amk.load_variable("blowout",0)==0 then
	  self:anomaly_evade()
	end
    
  -- Воспользуемся стэйт-менеджером для настройки анимации
  if self.st.dist and self.st.dist<max_faraway_dist then
    state_mgr.set_state(npc,"rush",nil,nil,nil,{animation=true})
  else
    state_mgr.set_state(npc,"sprint",nil,nil,nil,{animation=true})
  end

  -- Повысим зоркость
  self.object:set_sight(look.danger, nil, 0)

  self.offset=vector():set(math.random()*6-3,0,math.random()*6-3)
  self.offset:normalize()
  self.state_change_time=time_global()+1500
  self.last_v_id=npc:level_vertex_id()
end

local param=0

function action_stay_close:execute()
  action_base.execute(self)
--  mylog("action exec")
  local npc=self.object
  local actor=db.actor
  if not actor then
  -- Хм, что-то не так. Может быть ГГ перешёл на другой уровень? Запрещаем схему поведения
    self.st.enabled=false
  end
  if self.delay and self.delay>time_global() then
    return
  end
  self.delay=time_global()+1000

  -- Получаем ближайшую доступную точку в 5 метрах от ГГ
  local vertex_id=level.vertex_in_direction(db.tc:get_center_vid(),self.offset,5)

  if self.state_change_time<time_global() then
    self.state_change_time=time_global()+1500
    if self.last_v_id==npc:level_vertex_id() then
      state_mgr.set_state(npc,"idle")
      npc:set_dest_level_vertex_id(self.last_v_id)
      self.last_v_id=0
      self.st.temp_disable=time_global()+1500
      return
    elseif self.st.dist and (self.st.dist<max_faraway_dist) then
      state_mgr.set_state(npc,"rush",nil,nil,nil,{animation=true})
    else
      state_mgr.set_state(npc,"sprint",nil,nil,nil,{animation=true})
    end
    self.last_v_id=npc:level_vertex_id()
  end

--  npc:set_detail_path_type(move.line)
  if vertex_id then
    npc:set_dest_level_vertex_id(vertex_id)
  end
end

function action_stay_close:anomaly_evade()
  local npc=self.object
  local act_sec=db.storage[npc:id()].active_section or ""
  if not self.prev_pos then
    self.prev_pos=vector():set(0,-100,0)
  end
  if self.prev_pos:distance_to(npc:position())>10 then
    self.st.avg_dir=npc:position():sub(self.prev_pos):normalize()
    self.prev_pos=npc:position()
    local list=amk_mod.get_anomaly_list(npc,25)
    self.st.alist=list
    local stp=""
    local first=true
    for id,o in pairs(list) do
      if first then
        stp=o.name
        first=false
      else
        stp=stp..","..o.name
      end
    end
    if self.prevstp~=stp then
      npc:remove_restrictions("",self.prevstp)
      npc:add_restrictions("",stp)
			mylog(npc:name() .. " coop npc is avoiding anomaly. sect "..act_sec,"grb")
    end
  end
end

class "action_run_to_wounded" (action_base)

function action_run_to_wounded:__init(name, storage) super (nil, name)
  self.st=storage
  self.prevstp=""
end

function action_run_to_wounded:initialize()
  action_base.initialize(self)
  local npc=self.object
  -- Не знаю зачем эти две функции, но они используются во всех операторах
  npc:set_desired_position()
  npc:set_desired_direction()


  -- Задаём параметры движения
  npc:set_detail_path_type(move.line)
  npc:set_path_type(game_object.level_path)
  -- Освободим сталкера от всех ограничений
  if get_movstate()~=1 then
  npc:remove_all_restrictions()
  end
  
  if amk.load_variable("blowout",0)==0 then
	  self:anomaly_evade()
	end
    
  -- Воспользуемся стэйт-менеджером для настройки анимации
  state_mgr.set_state(npc,"sprint",nil,nil,nil,{animation_nout=true})
end

function action_run_to_wounded:execute()
  action_base.execute(self)
  local npc=self.object
  local actor=db.actor
  if not actor then
  -- Хм, что-то не так. Может быть ГГ перешёл на другой уровень? Запрещаем схему поведения
    self.st.enabled=false
  end
  if self.delay and self.delay>time_global() then
    return
  end
  self.delay=time_global()+1000
  local act_v_id=actor:level_vertex_id()

  -- Отправляем нашего NPC в найденную точку
  local acc_id=utils.send_to_nearest_accessible_vertex( npc, act_v_id )
end

function action_run_to_wounded:anomaly_evade()
  local npc=self.object
  local act_sec=db.storage[npc:id()].active_section or ""
  if not self.prev_pos then
    self.prev_pos=vector():set(0,-100,0)
  end
  if self.prev_pos:distance_to(npc:position())>10 then
    self.st.avg_dir=npc:position():sub(self.prev_pos):normalize()
    self.prev_pos=npc:position()
    local list=amk_mod.get_anomaly_list(npc,25)
    self.st.alist=list
    local stp=""
    local first=true
    for id,o in pairs(list) do
      if first then
        stp=o.name
        first=false
      else
        stp=stp..","..o.name
      end
    end
    if self.prevstp~=stp then
      npc:remove_restrictions("",self.prevstp)
      npc:add_restrictions("",stp)
			mylog(npc:name() .. " coop npc is avoiding anomaly. sect "..act_sec,"grb")
    end
  end
end

class "action_heal_wounded" (action_base)

function action_heal_wounded:__init(name, storage) super (nil, name)
  self.st=storage
end

function action_heal_wounded:initialize()
  action_base.initialize(self)
  local npc=self.object
  -- Не знаю зачем эти две функции, но они используются во всех операторах
  npc:set_desired_position()
  npc:set_desired_direction()
--  self.st.heal_state=nil
  npc:set_mental_state(anim.free)
  state_mgr.set_state(npc,"search",nil,nil,{look_object=db.actor},{animation=true})
  xr_sound.set_sound_play(npc,"medkit",0)
  if not self.heal_end_time then
    self.heal_end_time=time_global()+1500
  end
end

function action_heal_wounded:execute()
  action_base.execute(self)
  local npc=self.object
  local actor=db.actor
  if not actor then
  -- Хм, что-то не так. Может быть ГГ перешёл на другой уровень? Запрещаем схему поведения
    self.st.enabled=false
    self.st.heal_state=nil
    return
  end
  if not db.actor:alive() then
    self.st.heal_state=nil
    return
  end
  if self.st.heal_state==nil then
    local medkit=npc:object("medkit") or 
            npc:object("medkit_army") or 
            npc:object("medkit_scientic")
    if medkit then
      npc:transfer_item(medkit,actor)
      local medkit1=actor:object("medkit") or 
            actor:object("medkit_army") or 
            actor:object("medkit_scientic")
      if medkit1 then
        actor:eat(medkit1)
        self.st.heal_state=2
      else
        self.st.heal_state=1
      end
    else
      -- аптечка куда-то делась
      self.st.heal_state=nil
      self.heal_end_time=nil
    end
  elseif self.st.heal_state==1 then
    local medkit1=actor:object("medkit") or 
          actor:object("medkit_army") or 
          actor:object("medkit_scientic")
    if medkit1 then
      actor:eat(medkit1)
      self.st.heal_state=2
    else
      -- аптечка куда-то делась
      self.st.heal_state=nil
      self.heal_end_time=nil
    end
  elseif self.st.heal_state==2 then
    if (not self.heal_end_time) or time_global()>self.heal_end_time then
      self.st.heal_state=nil
      self.heal_end_time=nil
    end
  end
end

class "action_return_to_actor" (action_base)

function action_return_to_actor:__init(name, storage) super (nil, name)
  self.st=storage
  self.prevstp=""
end

function action_return_to_actor:initialize()
  action_base.initialize(self)
  local npc=self.object
  -- Не знаю зачем эти две функции, но они используются во всех операторах
  npc:set_desired_position()
  npc:set_desired_direction()

  -- Сбрасываем текущие анимации
--  npc:clear_animations()

  -- Задаём параметры движения
  npc:set_detail_path_type(move.line)
  npc:set_path_type(game_object.level_path)

  -- Освободим сталкера от всех ограничений
  if get_movstate()~=1 then
  npc:remove_all_restrictions()
  end
  
  if amk.load_variable("blowout",0)==0 then
	  self:anomaly_evade()
	end
  
  -- Воспользуемся стэйт-менеджером для настройки анимации
  state_mgr.set_state(npc,"sprint")
  self.lsct=time_global()+1500
  self.lvid=npc:level_vertex_id()
end

function action_return_to_actor:execute()
  action_base.execute(self)
  local npc=self.object
  local be=npc:best_enemy()
  if be then
  -- Будем игнорировать всех врагов
    npc:enable_memory_object(be,false)
  end
  if self.delay and self.delay>time_global() then
    return
  end
  self.delay=time_global()+1000

  --Проверим не завис ли NPC
  if self.lsct<time_global() then
    self.lsct=time_global()+1500
    if self.lvid==npc:level_vertex_id() then
      state_mgr.set_state(npc,"idle")
      npc:set_dest_level_vertex_id(self.lvid)
      self.lvid=0
      self.st.temp_disable=time_global()+1500
      return
    end
    self.lvid=npc:level_vertex_id()
    state_mgr.set_state(npc,"sprint")
  end
  
  local acc_id=utils.send_to_nearest_accessible_vertex( npc, db.tc:get_center_vid() )
end

function action_return_to_actor:anomaly_evade()
  local npc=self.object
  local act_sec=db.storage[npc:id()].active_section or ""
  if not self.prev_pos then
    self.prev_pos=vector():set(0,-100,0)
  end
  if self.prev_pos:distance_to(npc:position())>10 then
    self.st.avg_dir=npc:position():sub(self.prev_pos):normalize()
    self.prev_pos=npc:position()
    local list=amk_mod.get_anomaly_list(npc,25)
    self.st.alist=list
    local stp=""
    local first=true
    for id,o in pairs(list) do
      if first then
        stp=o.name
        first=false
      else
        stp=stp..","..o.name
      end
    end
    if self.prevstp~=stp then
      npc:remove_restrictions("",self.prevstp)
      npc:add_restrictions("",stp)
			mylog(npc:name() .. " coop npc is avoiding anomaly. sect "..act_sec,"grb")
    end
  end
end


function add_to_binder(object, char_ini, scheme, section, st)
--  mylog("add to binder")

  local operators={}
  operators.smti_alife  = xr_actions_id.state_mgr + 2

  local manager = object:motivation_action_manager()
  local property_wounded = xr_evaluators_id.sidor_wounded_base

  -- Evaluators
--  mylog("ch1")
  manager:remove_evaluator(property_faraway)
  manager:add_evaluator(property_faraway, evaluator_faraway("evaluator_faraway",st))
  manager:add_evaluator(property_farfaraway, evaluator_farfaraway("evaluator_farfaraway",st))
--  manager:add_evaluator(property_i_am_healer, evaluator_i_am_healer("evaluator_i_am_healer",st))
-- Я объединил свойства i_am_healer и actor_is_wounded в одно чтобы упростить отключение боевой схемы
  manager:add_evaluator(property_actor_is_wounded, evaluator_actor_is_wounded("evaluator_actor_is_wounded",st))
  manager:add_evaluator(property_too_far_to_heal, evaluator_too_far_to_heal("evaluator_too_far_to_heal",st))
  

  -- Держимся в 20м от ГГ
  local action = action_stay_close("action_stay_close",st)
  action:add_precondition(world_property(stalker_ids.property_alive, true))
  action:add_precondition(world_property(stalker_ids.property_enemy, false))
  action:add_precondition(world_property(property_wounded, false))
  action:add_precondition(world_property(blowout_scheme.evid_anomaly,false))
	action:add_precondition(world_property(blowout_scheme.evid_blowout,false))
  if olp_mod.looting and watcher_act then
    action:add_precondition(world_property(watcher_act.evid_see_stuff, false))
  end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_medic, false)) end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_self_medic, false)) end
  action:add_precondition(world_property(property_farfaraway, false))
  action:add_precondition(world_property(property_faraway, true))
  action:add_precondition(world_property(property_actor_is_wounded, false))
  action:add_effect (world_property(property_faraway, false))
  manager:add_action (act_stay_close, action)

-- Во время боя отошли слишком далеко, возвращаемся
  local action = action_return_to_actor("action_return_to_actor",st)
  action:add_precondition(world_property(stalker_ids.property_alive, true))
  action:add_precondition(world_property(property_wounded, false))
  action:add_precondition(world_property(blowout_scheme.evid_anomaly,false))
	action:add_precondition(world_property(blowout_scheme.evid_blowout,false))
  if olp_mod.looting and watcher_act then
    action:add_precondition(world_property(watcher_act.evid_see_stuff, false))
  end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_medic, false)) end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_self_medic, false)) end
  action:add_precondition(world_property(property_farfaraway, true))
  action:add_precondition(world_property(property_actor_is_wounded, false))
  action:add_effect (world_property(property_farfaraway, false))
  manager:add_action (act_return_to_actor, action)

-- Бежим к раненному ГГ.
  local action=action_run_to_wounded("action_run_to_wounded",st)
  action:add_precondition(world_property(stalker_ids.property_alive, true))
  action:add_precondition(world_property(property_wounded, false))
  action:add_precondition(world_property(property_too_far_to_heal, true))
  action:add_precondition(world_property(property_actor_is_wounded, true))
  action:add_precondition(world_property(blowout_scheme.evid_anomaly,false))
	action:add_precondition(world_property(blowout_scheme.evid_blowout,false))
  if olp_mod.looting and watcher_act then
    action:add_precondition(world_property(watcher_act.evid_see_stuff, false))
  end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_medic, false)) end
  if xrs_medic then action:add_precondition(world_property(xrs_medic.evid_self_medic, false)) end
  action:add_effect(world_property(property_too_far_to_heal, false))
  manager:add_action(act_run_to_wounded,action)

-- Лечим ГГ.
  local action=action_heal_wounded("action_heal_wounded",st)
  action:add_precondition(world_property(stalker_ids.property_alive, true))
  --action:add_precondition(world_property(property_wounded, false))
  action:add_precondition(world_property(property_too_far_to_heal, false))
  action:add_precondition(world_property(property_actor_is_wounded, true))
  action:add_effect(world_property(property_actor_is_wounded, false))
  manager:add_action(act_heal_wounded,action)

-- Блокируем движковые схемы поведения
  action=manager:action(stalker_ids.action_alife_planner)
  action:add_precondition(world_property(property_faraway, false))
  action:add_precondition(world_property(property_actor_is_wounded, false))
  action=manager:action(stalker_ids.action_combat_planner)
  action:add_precondition(world_property(property_farfaraway, false))
  action:add_precondition(world_property(property_actor_is_wounded, false))
  action=manager:action(stalker_ids.action_danger_planner)
  action:add_precondition(world_property(property_actor_is_wounded, false))
  action:add_precondition(world_property(property_faraway, false))
--  action=manager:action(operators.smti_alife)
--  action:add_precondition(world_property(property_faraway, false))

-- Работает только для встроенных схем. Придётся вносить изменения в xr_motivator
--  xr_logic.subscribe_action_for_events(object, st, db.tc)
end

function death_callback(victim,who)
  if db.tc then
    db.tc:death_callback(victim,who)
  end
end

function death_offline(obj_id)
--  db.actor:give_game_news(object:character_name() .. " погиб!", "ui\\ui_iconsTotal", Frect():set(0,658,83,47), 0, 15000)
  remove_from_actor_pstor(obj_id)
end


-------------------- Координатор командной работы ------------------
class "team_coordinator"

function team_coordinator:__init(storage)
  self.st=storage
  self.npcs={}
  self.leader=db.actor
  if self.leader==nil then
    mylog("db.actor is nil")
    -- Потом вылетит
  end
  self.count=0
end

function team_coordinator:add_npc(npc)
  self.npcs[npc:id()]=npc
  self.count=self.count+1
end

function team_coordinator:remove_npc(npc)
  self.npcs[npc:id()]=nil
  self.count=self.count-1
end

function team_coordinator:online_count()
  return self.count
end

function team_coordinator:get_center_point()
  return (self.center_point or db.actor:position())
end

function team_coordinator:get_center_vid()
  return (self.center_vid or db.actor:level_vertex_id())
end

function team_coordinator:set_center_point(pos,v_id)
  self.center_vid=v_id
  self.center_point=pos and vector():set(pos.x,pos.y,pos.z)
end


function team_coordinator:death_callback(object,cause)
  if self.npcs[object:id()] then
    db.actor:give_game_news(object:character_name() .. " has died!", "ui\\ui_iconsTotal", Frect():set(0,658,83,47), 0, 15000)
    disable_scheme(object, "actor_need_help")
  end
end

function team_coordinator:assign_healer()
  local need_to_change=false
  if self.healer_id then
  -- Проверим в порядке ли текущий лекарь
    local healer=self.npcs[self.healer_id]
    if healer then
      need_to_change=xr_wounded.is_wounded(healer) or (not npc_have_medkit(healer))
    else
      need_to_change=true
    end
  else
    need_to_change=true
  end
  need_to_change=true
  if need_to_change then
    local new_healer=self:nearest_npc_to(db.actor)
    if new_healer then
      self.healer_id=new_healer:id()
    else
      self.healer_id=nil
    end
  end
end

function team_coordinator:is_healer(npc)
  return npc:id()==self.healer_id
end

function team_coordinator:get_first_npc()
  for k,v in pairs(self.npcs) do
    return v
  end
  return nil
end


function team_coordinator:nearest_npc_to(npc)
  local lpos=npc:position()
  local nnpc=nil
  local mindist=100000
  for k,v in pairs(self.npcs) do
    if (not xr_wounded.is_wounded(v)) and npc_have_medkit(v) then
      local dist=lpos:distance_to(v:position())
      if dist<mindist then
        mindist=dist
        nnpc=v
      end
    end
  end
  return nnpc,mindist
end

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

function set_help(npc, ini)
  -- Создадим тим-координатор, если его ещё нет и восстановим настойки схемы
  if not db.tc then
    db.tc=team_coordinator({})
    restore_scheme_state()
  end
--  mylog("set help")
  local st = xr_logic.assign_storage_and_bind(npc, ini, "actor_need_help")
  st.enabled=true
  db.tc:add_npc(npc)
  add_to_actor_pstor(npc:id())
-- Освободим перса от гулага
  xr_gulag.resetJob(npc)
end

function update_help(npc, ini)
  -- Создадим тим-координатор, если его ещё нет и восстановим настойки схемы
  if not db.tc then
    db.tc=team_coordinator({})
    looking_for_actor()
  end
--  mylog("set help")
  local st = xr_logic.assign_storage_and_bind(npc, ini, "actor_need_help")
  st.enabled=true
  db.tc:add_npc(npc)
  add_to_actor_pstor(npc:id())
-- Освободим перса от гулага
  xr_gulag.resetJob(npc)
end

function disable_scheme(npc, scheme)
  local st = db.storage[npc:id()][scheme]
  if st then
    st.enabled = false
    db.tc:remove_npc(npc)
    remove_from_actor_pstor(npc:id())
  end
end

local actor_team_ids=nil

function add_to_actor_pstor(npc_id)
  local actor=db.actor
  mylog("add to pstor")
  if actor then
    local nid=npc_id
    if actor_team_ids then
      actor_team_ids[nid]=true
    end
    local cnt=xr_logic.pstor_retrieve(actor,"team_cnt",0)
    local firstzero=0
    for i=1,cnt,1 do
      if xr_logic.pstor_retrieve(actor,"team_id_"..i,0)==nid then
      -- уже в команде
        return
      elseif xr_logic.pstor_retrieve(actor,"team_id_"..i,0)==0 and firstzero==0 then
        firstzero=i
      end
    end
    if firstzero==0 then
      cnt=cnt+1
      xr_logic.pstor_store(actor,"team_cnt",cnt)
    else
      cnt=firstzero
    end
    mylog("adding spot for member "..cnt)
    local npc=level.object_by_id(nid)
    level.map_add_object_spot_ser(nid, "anh_merc", (npc or "Наёмник") and npc:character_name())
    xr_logic.pstor_store(actor,"team_id_" .. cnt,nid)
  else
    mylog("db.actor is nil in add_to_actor_pstor!")
  end
end

function remove_from_actor_pstor(npc_id)
  local actor=db.actor
  mylog("remove from pstor")
  if actor then
    local cnt=xr_logic.pstor_retrieve(actor,"team_cnt",0)
    local nid=npc_id
    level.map_remove_object_spot(nid, "anh_merc")
    if actor_team_ids then
      actor_team_ids[nid]=nil
    end
    for i=1,cnt,1 do
      if xr_logic.pstor_retrieve(actor,"team_id_"..i,0)==nid then
        xr_logic.pstor_store(actor,"team_id_"..i,nil)
      end
    end
-- Пересчитаем количество
    local new_cnt=0
    for i=1,cnt,1 do
      if xr_logic.pstor_retrieve(actor,"team_id_"..i,false) then
        new_cnt=i
      end
    end
    xr_logic.pstor_store(actor,"team_cnt",new_cnt)
  else
    mylog("db.actor is nil in remove_from_actor_pstor!")
  end  
end

function is_in_actor_team(obj_id)
  local actor=db.actor
  if (not actor_team_ids) and (not db.actor) then
    return false
  elseif actor_team_ids then
    return actor_team_ids[obj_id]
  else
    actor_team_ids={}
    local cnt=xr_logic.pstor_retrieve(actor,"team_cnt",0)
    for i=1,cnt,1 do
      id=xr_logic.pstor_retrieve(actor,"team_id_"..i,0)
      if id~=0 then
        actor_team_ids[id]=true
        mylog(id .. " is in actor team")
      end
    end
    return actor_team_ids[obj_id]
  end
end

function obj_id_in_team(obj_id)
  return is_in_actor_team(obj_id)
end

local offline_dest_table={
    l01_escape={
      none="esc2_bandit_les_kamp_1",
      l02_garbage="esc2_most_kamp_1"
    },
    l02_garbage={
      none="gar_dolg_blokpost_kamp_point_task"
    },
    l03_agroprom = {
      none="agr_factory_hold_fake_walk2"
    }, 
    l03u_agr_underground = {
      none="agr_factory_hold_fake_walk2"
    },
    l04_darkvalley = {
      none="val_escort_nap1_walk"
    }, 
    l04u_labx18 = {
      none="val_escort_nap1_walk"
    }, 
    l05_bar = {
      none="bar_visitors_kamp_1_task"
    },
    l06_rostok = {
      none="ros_zombies_walk"
    },
    l07_military = {
      none="mil_dolg_ambush_walk1"
    }, 
    l08_yantar = {
      none="yan_st_stalker2_kamp_1"
    }, 
    l08u_brainlab = {
      none="yan_st_stalker2_kamp_1" -- не нашёл
    }, 
    l10_radar = {
      none="rad_entrance_kamp_task"
    },
    l10u_bunker = {
      none="rad_entrance_kamp_task" -- не нашёл
    }, 
    l11_pripyat = {
      none="pri_followers_wait_stalker1_walk"
    }, 
    l12_stancia = {
      none="aes_heli_attack_desant_walk1_2" -- взял из spawn.ini надо проверять
    }, 
    l12u_sarcofag = {
      none="aes_heli_attack_desant_walk1_2" -- не нашёл
    }, 
    l12u_control_monolith = {
      none="aes_heli_attack_desant_walk1_2" -- не нашёл
    }, 
    l12_stancia_2 = {
      none="aes_heli_attack_desant_walk1_2" -- не нашёл
    }
  }

local offline_dest_point

function get_offline_dest()
  return offline_dest_point
end

function update_offline_dest(cur_level,prev_level)
  if offline_dest_table[cur_level] then
    if offline_dest_table[cur_level][prev_level] then
      offline_dest_point=offline_dest_table[cur_level][prev_level]
    else
      offline_dest_point=offline_dest_table[cur_level]["none"]
    end
  end
  if not offline_dest_point then
    mylog("odp is nil for " .. tostring(cur_level) .. " " .. tostring(prev_level))
  end
  offline_dest_point=offline_dest_point or "esc2_bandit_les_kamp_1"
end

function update_level_names()
--  mylog("updlevnames")
  local actor=db.actor
  if not actor then
    offline_dest_point="esc2_bandit_les_kamp_1"
    mylog("not db.actor")
    return
  end
  local cur_level_name=xr_logic.pstor_retrieve(actor,"current_level","none")
  xr_logic.pstor_store(actor,"prev_level",cur_level_name)
  xr_logic.pstor_store(actor,"current_level",level.name())
--    mylog("cur level "..level.name() .. " prev level " .. cur_level_name)
  -- Проверим, нужно ли ждать на другом уровне
  local w_level=xr_logic.pstor_retrieve(actor,"wait_level",nil)
  if w_level then
    -- ждём на другом уровне
    update_offline_dest(w_level,level.name())
  else
    update_offline_dest(level.name(),cur_level_name)
  end
--    mylog("odp is " .. offline_dest_point)
end

-- Вызывается при переключении на новую секцию. Производит вычитывание настроек из текущей секции.
function reset_help(npc, scheme, st, section)
end

function i_am_friend(talker,target)
  if xr_wounded.is_wounded(target) then
    -- Не будем приставать с просьбой о помощи к раненным
    return false
  end
  return true
--  return target:relation(talker)==game_object.friend
end

function check_faction(talker,target)
  
  if amk.load_variable("blowout",0)>0 or
  target:relation(talker)~=game_object.friend or
  excluded_npc[target:name()] then
    return false
  end
  return true

end

-- За основу этой функции взята функция dialogs.actor_have_medkit
function npc_have_medkit(talker, target)
  return talker:object("medkit") ~= nil or
       talker:object("medkit_army") ~= nil or
       talker:object("medkit_scientic") ~= nil
end
function npc_havent_medkit(talker, target)
  return not npc_have_medkit(talker,target)
end

function scheme_is_active(talker,target) -- talker - ГГ
  local st = db.storage[target:id()]["actor_need_help"]
  return (st~=nil) and st.enabled==true
end
function scheme_is_not_active(talker,target)
  return not scheme_is_active(talker,target)
end
function scheme_is_not_active_npc(talker,target) -- talker - NPC
  return not scheme_is_active(target,talker)
end
function scheme_is_active_npc(talker,target) -- talker - NPC
  return scheme_is_active(target,talker)
end
function activate_scheme(talker,target)
--  mylog("activate scheme")
  set_help(talker,talker:spawn_ini())
end
function deactivate_scheme(talker,target)
  disable_scheme(talker,"actor_need_help")
end

local abuse_count={}
local abuse_punch_limit={}
function abuse_limit(talker,target)
  return abuse_count[target:id()] and abuse_count[target:id()]==3
end
function abuse_limit2(talker,target)
  return abuse_count[target:id()] and abuse_count[target:id()]>3
end
function no_abuse_limit(talker,target)
  return abuse_count[target:id()]==nil or abuse_count[target:id()]<3
end
function init_punch(talker)
  if abuse_punch_limit[talker:id()]==nil then
    abuse_punch_limit[talker:id()]=math.random(6,15)
  end
end
function abuse_punch(talker,target) -- talker - ГГ
  init_punch(target)
  return abuse_count[target:id()] and abuse_count[target:id()]>abuse_punch_limit[target:id()]
end
function abuse_no_punch(talker,target)
  return not abuse_punch(talker,target)
end

function reset_abuse(talker,target)
  abuse_count[talker:id()]=nil
  abuse_punch_limit[talker:id()]=nil
end
function inc_abuse(talker,target)
  abuse_count[talker:id()]=(abuse_count[talker:id()] or 0)+1
end
function do_punch(talker,target) -- talker - NPC
  xr_abuse.enable_abuse(talker)
  xr_abuse.add_abuse(talker, 100000) -- для верности
-- взято из xr_effects.actor_punch
  local active_slot = db.actor:active_slot()
  if active_slot == 1 or
     active_slot == 2
  then
    local active_item = db.actor:active_item()
    if active_item then
      db.actor:drop_item(active_item)
    end
  end  
  set_inactivate_input_time(30)
  local snd_obj = xr_sound.get_safe_sound_object([[affects\hit_fist]])
  snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)
  level.add_cam_effector("camera_effects\\fusker.anm", 999, false, "")    
end
-- Получить напрямую степень опьянения ГГ по-видимому невозможно. Можно отслеживать использование бутылок водки, но это оставлю на потом
function actor_drunk(talker,target)
  return true
end
-- надет ли на target костюм с противогазом или замкнутой системой дыхания
function actor_antigas(talker,target)
  local outfit=target:get_current_outfit()
  if outfit==nil or outfit.section==nil then
    return false
  else
    local sect=outfit:section()
    if sect=="novice_outfit" or sect=="bandit_outfit" or sect=="killer_outfit"
      or sect=="monolit_outfit" or sect=="specops_outfit" or sect=="military_outfit"
      or sect=="stalker_outfit" or sect=="svoboda_light_outfit" then
      return false
    end
  end
  return true
end
function actor_no_antigas(talker,target)
  return not actor_antigas(talker,target)
end

local decision_switch={} -- Будем отслеживать как часто ГГ меняет своё решение о помощи

function status_changed(talker,targer) -- talker - NPC
--  mylog("status changed")
  if decision_switch[talker:id()] then
    local ds=decision_switch[talker:id()]
    if time_global()<ds.time then
    -- если ГГ меняет решение слишком часто, то увеличим счётчик
      ds.count=ds.count+1
    else
      ds.count=0
    end
    ds.time=time_global()+20000
  else
    decision_switch[talker:id()]={time=time_global()+20000,count=0}
  end  
end

-- ГГ не слишком часто менял своё решение
function actor_can_decide(talker,target) -- talker - ГГ
  if decision_switch[target:id()] and decision_switch[target:id()].time<time_global() then
  -- прошло установленное время и NPC готов нас выслушать
    decision_switch[target:id()]=nil
  end
  return decision_switch[target:id()]==nil or decision_switch[target:id()].count<3
end

function actor_cannot_decide(talker,target) -- talker - ГГ
  return not actor_can_decide(talker,target)
end

-- подсчёт и трансфер аптечек
function actor_have_ge1_medkit(talker,target) -- talker - ГГ
  return npc_medkit_count(talker)>=1
end

function actor_have_ge2_medkit(talker,target) -- talker - ГГ
  return npc_medkit_count(talker)>=2
end
function actor_have_ge4_medkit(talker,target) -- talker - ГГ
  return npc_medkit_count(talker)>=4  
end
function actor_have_ge8_medkit(talker,target) -- talker - ГГ
  return npc_medkit_count(talker)>=8  
end

function npc_medkit_count(npc)
  local count=0
  local iter_func=function (npc,item)
      local sect=item:section()
      if sect=="medkit" or sect=="medkit_army" or sect=="medkit_scientic" then
        count=count+1
      end
    end
  npc:iterate_inventory(iter_func,npc)
  return count
end

function transfer_1_medkit(talker,target) -- talker - ГГ
--  mylog("transfer 1")
  transfer_n_medkit(talker,target,1)
end
function transfer_2_medkit(talker,target) -- talker - ГГ
--  mylog("transfer 2")
  transfer_n_medkit(talker,target,2)
end
function transfer_4_medkit(talker,target) -- talker - ГГ
--  mylog("transfer 4")
  transfer_n_medkit(talker,target,4)
end
function transfer_8_medkit(talker,target) -- talker - ГГ
--  mylog("transfer 8")
  transfer_n_medkit(talker,target,8)
end

function transfer_n_medkit(talker,target,n)
--  mylog("transfer n")
  local count=n
  local iter_func=function (npc,item)
      local sect=item:section()
      if (sect=="medkit" or sect=="medkit_army" or sect=="medkit_scientic") and count>0 then
--        mylog("transfer item " .. count)
        npc:transfer_item(item,target)
        news_manager.relocate_item(talker, "out", sect)
        count=count-1
      end
    end
  talker:iterate_inventory(iter_func,talker)
end

function dlg_AddPhrase(dlg, str, p1, p2, p3)
	local ver = _g.game_version()
	if ver >= 7 then
		p1 = tostring(p1)
		if p2 == -1 then
			p2 = ""
		else
			p2 = tostring(p2)
		end
	end
	return dlg:AddPhrase(str, p1, p2, p3)
end


function init_commands_dialog(dlg)
  mylog("init cmd dlg")
  local phr, scr
  phr=dlg_AddPhrase(dlg,"Attention, everyone in my group...",0,-1,0)
  phr=dlg_AddPhrase(dlg,"",1,0,0)
  phr=dlg_AddPhrase(dlg,"(Movement Command) - I want everyone to...",10,1,0)
  phr=dlg_AddPhrase(dlg,"Ah, nevermind...",20,1,0)
  phr=dlg_AddPhrase(dlg,"",11,10,0)
  phr=dlg_AddPhrase(dlg,"",21,20,0)

  phr=dlg_AddPhrase(dlg,"Follow me now. Free formation.",100,11,0)
  phr=dlg_AddPhrase(dlg,"Ok, but I won't go to underground vaults, sorry.",1010,100,0)
  scr=phr:GetPhraseScript(phr)
  scr:AddAction("actor_need_help.anh_set_state_normal")
  
  phr=dlg_AddPhrase(dlg,"Wait for me here.",101,11,0)
  phr=dlg_AddPhrase(dlg,"Ok, waiting...",1000,101,0)
  scr=phr:GetPhraseScript(phr)
  scr:AddAction("actor_need_help.anh_set_state_wait")

  phr=dlg_AddPhrase(dlg,"Well, OK, I understand. Let's go.",10101,1010,0)
  phr=dlg_AddPhrase(dlg,"Ok.",10106,10101,0)

  phr=dlg_AddPhrase(dlg,"Good luck.",200,21,0)

--  scr=phr:GetPhraseScript(phr)
--  scr:AddAction("actor_need_help.show_dialog")
end


function show_dialog()
--  local spwn=ui_cheat.cheat(get_hud())
--  spwn:SetClGame(level.main_input_receiver())
--  level.start_stop_menu(spwn,true)
end

local cur_movstate=0

function anh_set_state_wait()
  if db.tc then
    db.tc:set_center_point(db.actor:position(),db.actor:level_vertex_id())
  end
  cur_movstate=1
  xr_logic.pstor_store(db.actor,"wait_level",level.name())
  store_movstate()
  --db.actor:give_info_portion("actor_set_wait")
end

function anh_set_state_normal()
  if db.tc then
    db.tc:set_center_point(nil,nil)
  end
  cur_movstate=2
  xr_logic.pstor_store(db.actor,"wait_level",nil)
  store_movstate()
end

function anh_movstate_is_wait()
  return cur_movstate~=nil and cur_movstate==1
end

function anh_movstate_is_normal()
  return cur_movstate~=nil and cur_movstate==2
end

function anh_movstate_is_unknown()
  return not (anh_movstate_is_wait() or anh_movstate_is_normal())
end

function store_movstate()
  xr_logic.pstor_store(db.actor,"movstate",cur_movstate)  
end

function get_movstate()
  if db.actor then
    return xr_logic.pstor_retrieve(db.actor,"movstate",0)
  end
  return 0 -- ХЗ
end

function restore_scheme_state()
  local movstate=xr_logic.pstor_retrieve(db.actor,"movstate",0)
  if movstate==1 then
    anh_set_state_wait()
  elseif movstate==2 then
    anh_set_state_normal()
  else
    anh_set_state_normal()
  end
end

function looking_for_actor()
  local movstate=xr_logic.pstor_retrieve(db.actor,"movstate",0)
  if movstate>0 then
    anh_set_state_normal()
  end
end
