module ("stalker")
module ("globals")
module ("xr_actions_id")
module ("xr_evaluators_id")
module ("xr_sounds_id")
module ("xr_evaluators")

module ("gramofon")

module ("xr_motivator")
module ("xr_sounds")


module ("xr_reactions")
module ("xr_state")
module ("xr_position")

module ("xr_gulag")
module ("xr_meet")
module ("xr_kamp")
module ("xr_guard")
module ("xr_robbers")
module ("xr_talker")
module ("xr_camper2")
module ("xr_sleeper")
module ("xr_commander")
module ("xr_swat")
module ("xr_patrol")
module ("xr_follower")
module ("xr_ambush")
module ("xr_waiter")
module ("xr_wounded")
module ("xr_scout")
module ("xr_trivial")
module ("xr_report")

module ("escape_factory_bandits")

function printf(fmt,...)
    log(string.format(fmt,unpack(arg)))
end

function wait_game(time_to_wait)
    verify_if_thread_is_running()
    if (time_to_wait == nil) then
        coroutine.yield()
    else
        local time_to_stop = game.time() + time_to_wait
        while game.time() <= time_to_stop do
            coroutine.yield()
        end
    end
end

function wait(time_to_wait)
    verify_if_thread_is_running()
    if (time_to_wait == nil) then
        coroutine.yield()
    else
        local time_to_stop = device():time_global() + time_to_wait
        while device():time_global() <= time_to_stop do
            coroutine.yield()
        end
    end
end

function random(low,up)
    rnd     = math.random(10000)
    rnd     = rnd/10000
    rnd     = rnd * (up - low)
    rnd     = rnd + low
    return  rnd
end

function action(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 action_first(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,true)
    end
    return  entity_action(act)
end

function round (value)
    local min = math.floor (value)
    local max = min + 1
    if value - min > max - value then return max end
    return min
end

class "pp_effector" (effector)

function pp_effector:__init(effector_type,start_time,dest_power,life_time) super(effector_type,start_time)
    self.start_time = start_time
    self.stop_time  = start_time + life_time
    self.max_power  = dest_power
end

function pp_effector:process(pp)
    effector.process(self,pp)

    local curr_time = device():time_global()
    local d         = 0.0

    if curr_time < self.start_time then
        d           = 0.0
    else
        if curr_time < self.stop_time then
            d           = (curr_time - self.start_time) / (self.stop_time - self.start_time)
        else
            self.info   = self.max_power
            return      true
        end
    end

    local dual      = duality()
    local noise     = noise()
    local base      = color()
    local gray      = color()
    local add       = color()

    dual.h          = self.max_power.dual.h         * d
    dual.v          = self.max_power.dual.v         * d

    noise.grain     = self.max_power.noise.grain        * d
    noise.intensity     = self.max_power.noise.intensity    * d
    noise.fps       = self.max_power.noise.fps      * d

    base.r          = self.max_power.color_base.r       * d
    base.g          = self.max_power.color_base.g       * d
    base.b          = self.max_power.color_base.b       * d

    gray.r          = self.max_power.color_gray.r       * d
    gray.g          = self.max_power.color_gray.g       * d
    gray.b          = self.max_power.color_gray.b       * d

    add.r           = self.max_power.color_add.r        * d
    add.g           = self.max_power.color_add.g        * d
    add.b           = self.max_power.color_add.b        * d

    pp.gray         = self.max_power.gray           * d
    pp.blur         = self.max_power.blur           * d

    pp.dual         = dual
    pp.noise        = noise
    pp.color_base       = base
    pp.color_gray       = gray
    pp.color_add        = add

    self.info       = pp

    return              true
end

function pp_effector:finished()
    return          self.stop_time < device():time_global()
end

--
-- postprocess for rainbow
--
class "pp_linear_lerp" (effector)

function pp_linear_lerp:__init(effector_type,start_time,life_time,start_power,dest_power) super(effector_type,start_time)
    self.start_time = start_time
    self.stop_time  = start_time + life_time
    self.min_power  = start_power
    self.max_power  = dest_power
end

function pp_linear_lerp:process(pp)
    effector.process(self,pp)

    local curr_time = device():time_global()
    local d         = 0.0

    if curr_time < self.start_time then
        d           = 0.0
    else
        if curr_time < self.stop_time then
            d           = (curr_time - self.start_time) / (self.stop_time - self.start_time)
        else
            self.info   = self.max_power
            return      true
        end
    end

    local dual      = duality()
    local noise     = noise()
    local base      = color()
    local gray      = color()
    local add       = color()

    dual.h          = self.min_power.dual.h         + (self.max_power.dual.h        - self.min_power.dual.h     )   * d
    dual.v          = self.min_power.dual.v         + (self.max_power.dual.v        - self.min_power.dual.v     )   * d

    noise.grain     = self.min_power.noise.grain        + (self.max_power.noise.grain       - self.min_power.noise.grain    )   * d
    noise.intensity     = self.min_power.noise.intensity    + (self.max_power.noise.intensity   - self.min_power.noise.intensity)   * d
    noise.fps       = self.min_power.noise.fps      + (self.max_power.noise.fps         - self.min_power.noise.fps  )   * d

    base.r          = self.min_power.color_base.r       + (self.max_power.color_base.r      - self.min_power.color_base.r   )   * d
    base.g          = self.min_power.color_base.g       + (self.max_power.color_base.g      - self.min_power.color_base.g   )   * d
    base.b          = self.min_power.color_base.b       + (self.max_power.color_base.b      - self.min_power.color_base.b   )   * d

    gray.r          = self.min_power.color_gray.r       + (self.max_power.color_gray.r      - self.min_power.color_gray.r   )   * d
    gray.g          = self.min_power.color_gray.g       + (self.max_power.color_gray.g      - self.min_power.color_gray.g   )   * d
    gray.b          = self.min_power.color_gray.b       + (self.max_power.color_gray.b      - self.min_power.color_gray.b   )   * d

    add.r           = self.min_power.color_add.r        + (self.max_power.color_add.r       - self.min_power.color_add.r    )   * d
    add.g           = self.min_power.color_add.g        + (self.max_power.color_add.g       - self.min_power.color_add.g    )   * d
    add.b           = self.min_power.color_add.b        + (self.max_power.color_add.b       - self.min_power.color_add.b    )   * d

    pp.gray         = self.min_power.gray           + (self.max_power.gray          - self.min_power.gray       )   * d
    pp.blur         = self.min_power.blur           + (self.max_power.blur          - self.min_power.blur       )   * d

    pp.dual         = dual
    pp.noise        = noise
    pp.color_base       = base
    pp.color_gray       = gray
    pp.color_add        = add

    self.info       = pp

    return          true
end

function pp_linear_lerp:finished()
    return          self.stop_time < device():time_global()
end
--
-- end of postprocess for rainbow
--

function get_level_object(obj_name)
    local res       = level.object(obj_name)
    while res == nil do
        printf("get_level_object() is waiting for object \"%s\"", obj_name)
        res         = level.object(obj_name)
        wait        ()
    end
    return          res
end

function get_actor()
    local res       = level.actor()
    while res == nil do
        res         = level.actor()
        wait        ()
    end
    return          res
end

function distance_between(obj1, obj2)
    return obj1:position():distance_to(obj2:position())
end

--------------------------------------------------
--  LUA     Finite State Machine
--   
--------------------------------------------------
class 'FSM'

--      Non-Player Character
function FSM:__init(script_name, npc_obj)
    --   v NPC
    self.npc = npc_obj
    --  -  , v  
    self.script_name = script_name
    -- ,       
    --    
    self.transition_matrix = {[0] = {}}
    --      
    self.current_state = 0

    --  ,   
    self.machine_running = true

    printf("inialization FSM for NPC %s", self.npc:name())
end

function FSM:is_running()
    return self.machine_running
end

function FSM:start()
    printf("starting FSM for %s", self.npc:name())
    self.machine_running = true
end

function FSM:stop()
    printf("stoping FSM for %s", self.npc:name())
    self.machine_running = false
    self:reset_script_control()
end


--     
--    
function FSM:set_transition(from_state_num, to_state_num, cond_func)
    printf("in setting transition %d, %d ", from_state_num, to_state_num)

    if self.transition_matrix[from_state_num] == nil then
       self.transition_matrix[from_state_num] = {[to_state_num] = cond_func}
    else
       self.transition_matrix[from_state_num][to_state_num] = cond_func
    end
end

--   FSM
function FSM:run()
    local transition_vector = self.transition_matrix[self.current_state]
    table.foreach(transition_vector,
                  function(to_state_num, cond_func)
                        if cond_func(self.npc, self) == true then
                            printf("transition from %d to %d", self.current_state, to_state_num)

                            if to_state_num == 0 then
                                self:reset_script_control()
                            end

                            --if  self.current_state == 0 then
                            --  self:set_script_control()
                            --end

                            self.current_state = to_state_num
                            return true
                        end
                        return nil
                  end)
end


--    FSM
function FSM:run_loop()
    printf("running FSM loop for NPC %s", self.npc:name())

    while self:is_running() do
        local transition_vector = self.transition_matrix[self.current_state]
        table.foreach(transition_vector,
                      function(to_state_num, cond_func)
                            if cond_func(self.npc, self) == true then
                                printf("transition from %d to %d", self.current_state, to_state_num)

                                if to_state_num == 0 then
                                    self:reset_script_control()
                                end

                                --if  self.current_state == 0 then
                                --  self:set_script_control()
                                --end

                                self.current_state = to_state_num
                                return true
                            end
                            return nil
                      end)
        if self.npc:alive () == false then
           self.npc:script (false, self.script_name)
           return
           end
        wait()
    end
end

--     NPC (   )
function FSM:set_script_control()
    printf("set script control for FSM of NPC %s", self.npc:name())
    self.npc:script(true, self.script_name)
end

--      NPC (   )
function FSM:reset_script_control()
    printf("reset script control for FSM of NPC %s", self.npc:name())
    self.npc:script(false, self.script_name)
end

--------------------------------------------------
-- end of  class 'FSM'
--------------------------------------------------

function reset_action (npc, script_name)
    if npc:get_script () then
       npc:script (false, script_name)
    end
    npc:script (true, script_name)
end

class "script_object"

function script_object:__init(name,script,...)
    self.action         = action(nil,unpack(arg))
    self.object_name    = name
    self.script_name    = script
    self.object         = nil
end

function script_object:update()
    local                   obj = self.object
    self.object             = level.object(self.object_name)
    if ((obj == nil) and (self.object ~= nil)) then
        self.object:script  (true,self.script_name)
        self.object:command (self.action,false)
    end
end

--
--   
--
function GiveInfo(obj, info_number)

    local result = obj:give_info_portion(info_number)
    while not result do
        wait()
        result = obj:give_info_portion(info_number)
    end
end

function GiveInfoViaPda(obj_receiver, obj_sender, info_number)
    obj_receiver:give_info_portion_via_pda(info_number, obj_sender)
end


--------------------------------------------------
-- Functions and variables added by Zmey
--------------------------------------------------

-- ,    ,      
time_infinite = 100000000

-- = , v  v    
-- (    v vv  debug_log)
debug_script_name = ""

-- Tv   ,  script_name == debug_script_name
-- L   
function debug_log(script_name, fmt, ...)
  if debug_script_name == script_name then
    log(string.format(fmt, unpack(arg)))
--    flush()
  end
end

-- +  v  v - , v    v 
function interrupt_action(who, script_name)
  if who:get_script() then
    who:script(false, script_name)
  end
end

function random_choice(...)
  local r = math.random(1, arg.n)
  return arg[r]
end

function new_action(...)
  local act = entity_action()
  for i = 1, arg.n do
    act:set_action(arg[i])
  end
  return act;
end

obj_last_actions = {};

function perform_action(obj, action_name, action)
  if (obj ~= nil) then
    obj_last_actions[obj] = action_name
    obj:command(act, false)
  end
end

function last_action(obj)
  return obj_last_actions[obj]
end

function if_then_else(cond, if_true, if_false)
    if cond then
        return if_true
    end
    return if_false
end

function update_action (npc, script, ...)
    if npc == nil then return end
    local act = npc:action ()
    if arg.n == 0 then return end

    if act == nil then act = entity_action () end

    for a = 1, arg.n, 1 do
        if arg[a] ~= nil then act:set_action (arg[a]) end
    end
    reset_action (npc, script)
    npc:command (act, false)

end


function set_current_time (hour, min, sec)
    local current_time_factor = level.get_time_factor ()

    printf ("Need time : %d:%d:%d", hour, min, sec)

    local current_time = game.time ()
    local c_day = math.floor (current_time / 86400000)
    local c_time = current_time - c_day * 86400000
    local n_time = (sec + min * 60 + hour * 3600) * 1000

    if c_time > n_time then c_day = c_day + 1 end
    n_time = n_time + c_day * 86400000

    level.set_time_factor (10000)
    while game.time () < n_time do wait () end

    level.set_time_factor (current_time_factor)
end




----------------------------------------------------------------------------------------------------------------------
-- Evaluators
----------------------------------------------------------------------------------------------------------------------
-- v id    0  256   v .

--Constant evaluator
class "const_evaluator" (property_evaluator)

function const_evaluator:__init (value) super ()
    self.value = value
end

function const_evaluator:evaluate()
    return          self.value
end

--Wait evaluator
class "wait_evaluator" (property_evaluator)

function wait_evaluator:__init (wait_time) super ()
    self.wait_time = wait_time
    self.first_call = true
    self.current_time = 0
end

function wait_evaluator:evaluate ()
    if self.first_call == true then
       self.first_call = false
       self.current_time = device ():time_global ()
       return false
    end

    local t = device():time_global () - self.current_time;
    if t > self.wait_time then return true end
    return false
end



function str_split (str)

    local strlen = string.len (str)

    local parts = {{}, {}, {}, {}}
    local cpart = 1

    for a = 1, strlen, 1 do
        local char = string.byte (str, a)
        if char ~= 95 then
           table.insert (parts[cpart], char)
        else
           cpart = cpart + 1
           if cpart > 4 then break end
        end
    end

    if cpart ~= 4 then return "unknown", "stalker", 0, 0 end

    local str1 = string.char ()
    local str2 = string.char ()
    local str3 = string.char ()
    local str4 = string.char ()

    local ref = parts[1]
    for a = 1, table.getn (ref), 1 do
        str1 = string.format ("%s%c", str1, ref[a])
    end

    ref = parts[2]
    for a = 1, table.getn (ref), 1 do
        str2 = string.format ("%s%c", str2, ref[a])
    end

    ref = parts[3]
    for a = 1, table.getn (ref), 1 do
        str3 = string.format ("%s%c", str3, ref[a])
    end

    ref = parts[4]
    for a = 1, table.getn (ref), 1 do
        str4 = string.format ("%s%c", str4, ref[a])
    end

    printf ("%s %s %d %d", str1, str2, str3, str4)
    return str1, str2, (str3 + 1) - 1, (str4 + 1) - 1
end

function random_number (min_value, max_value)
    math.randomseed (device ():time_global ())
    if min_value == nil and max_value == nil then
      return math.random ()
    else
      return math.random (min_value, max_value)
    end
end

-- ६ ⮪  
-- 㣠
local ms_per_day = 24 * 60 * 60 * 1000
function day_time()
    return math.mod( game.time(), ms_per_day )
end

--Time in hours
function local_hours()
    return math.floor( math.mod( game.time()/1000, 86400 )/ 3600 )
end

--  ப  ", , ..."  ⠡ { "", "", "", ... }
-- 㣠
function parse_names( s )
    local t = {}

    for name in string.gfind( s, "([%w_]+)%p*" ) do
        table.insert( t, name )
    end

    return t
end

-- 襭 砩 롮ઠ  ⠡  { obj1 = weight1, obj2 = weight2, ... }
-- 頥   obj.  >= 0
-- 㣠
function random_choice_weighted( a )
    local sum = 0

    for k, w in pairs( a ) do
        sum = sum + w
    end

    local r = math.random( sum )
    sum = 0

    for k, w in pairs( a ) do
        sum = sum + w

        if r <= sum then
            return k
        end
    end
end

-- ஢,   ꥪ  
function is_object_online (object_id)
    local sim = alife ()
    if sim == nil then return true end
    local obj = sim:object (object_id)
    if obj == nil then return false end
    return obj.online
end

-- ஢ 㦨   (। clsid)
function isWeapon(id)
    if id == clsid.wpn_vintorez then return true
    elseif id == clsid.wpn_fn2000 then return true
    elseif id == clsid.wpn_ak74 then return true
    elseif id == clsid.wpn_lr300 then return true
    elseif id == clsid.wpn_hpsa then return true
    elseif id == clsid.wpn_pm then return true
    elseif id == clsid.wpn_fort then return true
    elseif id == clsid.wpn_binocular then return true
    elseif id == clsid.wpn_shotgun then return true
    elseif id == clsid.wpn_svd then return true
    elseif id == clsid.wpn_svu then return true
    elseif id == clsid.wpn_rpg7 then return true
    elseif id == clsid.wpn_val then return true
    elseif id == clsid.wpn_vintorez then return true
    elseif id == clsid.wpn_walther then return true
    elseif id == clsid.wpn_usp45 then return true
    elseif id == clsid.wpn_groza then return true
    elseif id == clsid.wpn_knife then return true
    elseif id == clsid.wpn_grenade_launcher then return true
    elseif id == clsid.obj_explosive then return true
    elseif id == clsid.wpn_grenade_f1 then return true
    elseif id == clsid.wpn_grenade_rpg7 then return true
    elseif id == clsid.wpn_grenade_rgd5 then return true
    elseif id == clsid.wpn_grenade_fake then return true
        else return false end
end

--Tv yaw  
function yaw( v1, v2 )
    return  math.acos( ( (v1.x*v2.x) + (v1.z*v2.z ) ) / ( math.sqrt(v1.x*v1.x + v1.z*v1.z ) * math.sqrt(v2.x*v2.x + v2.z*v2.z ) ) )
end


--  
function clear_table (t)
    while table.getn (t) do
          table.remove (t, 1)
    end
end          