--[[ General Scripting TIPS 1. dealing with objects and userdata: NEVER keep userdata (ie. engine classes) in global scope, instead keep track of game object id, and grab object in the scope you need it in by using level.object_by_id or db.storage table. Otherwise object can't deconstruct and you will end up with instances were script binder exists but object doesn't or level object exists but server object doesn't. This is because variables in lua are all references. Ex. _G.npc = level.object_by_id(2342) alife():release(alife_object(2342)) Above, 'npc' will not be nil but se_obj will be! scripts will blow up. Pure Virtual Function calls may occur! Or quite possibly silent errors like for example when player goes to talk to npc game crashes because self.object is nil Such thing is 'okay' in db.storage, but that is because on net_destroy db.storage[id] is set nil. So if you are keeping track of objects directly in a table, instead of ID then you absolutely must rid of every single reference to the userdata so that it can be destroy and garbage collected. Otherwise you end up with undetectable issues. 2. naming variables: There are global namespace occupied for engine calls: task, level, weather Never use them as variables and such, you might break the game in no time by mistake You can check a full list of namespace in lua_help.script at the end Be aware with variables names inside scripts as well, don't use names found inside _G script if possible. And don't use names identical to scripts names _G is a core script where everything inside can be called directly from other scripts, without specifying the source Ex: calling IsStalker in some script will call the function found here directly, even if you didn't specify the script like this: _G.IsStalker --]] GAME_VERSION = "1.5.3" if string.find(command_line(), "-dbg") then DEV_DEBUG = true end if string.find(command_line(), "-dbgdev") then -- because users all using dbg, need to hide OP features DEV_DEBUG_DEV = true end USE_INI_MEMOIZE = true -- Improves performance if true, by disabling update methods for squads and smart terrains on levels not linked in configs\ai_tweaks\simulation_objects.ltx -- But this means simulation will only be active on actor level and linked levels everything else will remain in an idle state DEACTIVATE_SIM_ON_NON_LINKED_LEVELS = false ------------------------------------------------------------------------------------------------- -- Use marshal library for saving persistent data (like xr_logic pstor) -- marshal library can encode tables, functions, strings and numbers to easily allow persistent data storage to file -- This is used for db.storage[id].pstor, surge_manager, mines, and treasure_manager.script if enabled -- See alife_storage_manager.script for implementation require("lua_extensions") marshal = require "marshal" USE_MARSHAL = marshal ~= nil ---------------------------------------------------------------------- -- Global use ini_sys = system_ini() -- global use for cached system_ini() AC_ID = 0 -- Used for getting actor id -- AC_LVL_ID = nil -- Used for getting actor's level id, set by simulation_objects ------- replaced with function because crashes mus_vol = 0 -- Used for temp sound muting amb_vol = 0 -- Used for temp sound muting timer_transparent = 0 --маскхалат KEYS_UNLOCK = true -- Used for scripted GUI to disable action keybinds while they are active ---------------------------------------------------------------------- -- Utilities local string_find = string.find local string_match = string.match local string_gmatch = string.gmatch local string_sub = string.sub local string_gsub = string.gsub local string_format = string.format local math_cos = math.cos local math_sin = math.sin local math_acos = math.acos local math_sqrt = math.sqrt local math_floor = math.floor local math_ceil = math.ceil ---------------------------------------------------------------------- function start_game_callback() math.randomseed(device():time_global()) printf("S.T.A.L.K.E.R. Anomaly version %s",GAME_VERSION) --printf("OpenXRay x64, Call of Chernobyl build %s",GAME_VERSION) if (USE_MARSHAL) then printf("using marshal library") end -- Alundaio if (axr_main) then axr_main.on_game_start() end -- End Alundaio --SIMBOARD = nil sim_board.get_sim_board() --smart_names.init_smart_names_table() task_manager.clear_task_manager() --xr_sound.start_game_callback() dialog_manager.fill_phrase_table() --sim_objects.clear() sr_light.clean_up () pda.add_quick_slot_items_on_game_start() end ------------------------------------------------------------------------------------------------------- -- SCRIPTED CALLBACKS ------------------------------------------------------------------------------------------------------- function RegisterScriptCallback(name,func_or_userdata) axr_main.callback_set(name,func_or_userdata) end function UnregisterScriptCallback(name,func_or_userdata) axr_main.callback_unset(name,func_or_userdata) end function SendScriptCallback(name,...) -- Call this from a script to create a new callback to functions that register for it with RegisterScriptCallback -- Every time this function is executed it will callback to all registered members -- If axr_main.script has a function by this name, it will automatically trigger it! --utils_data.debug_write(strformat("BEFORE SendScriptCallback %s",name)) -- callback to all registered functions axr_main.make_callback(name,...) --utils_data.debug_write(strformat("AFTER SendScriptCallback %s",name)) -- check if axr_main has it's own function to execute if (axr_main[name]) then axr_main[name](...) end end function AddScriptCallback(name) axr_main.callback_add(name) end ------------------------------------------------------------------------------------------------------- -- GUI ------------------------------------------------------------------------------------------------------- _HUD_STATE = true _GUIs = {} _GUIsInstances = {} _GUIs_keyfree = {} -- specify GUIs that allow global keybinds function Register_UI(name, path, instance) if (not name) then return end printdbg("* Register UI: %s", name) _GUIs[name] = { path=path , instance=instance } _GUIsInstances[name] = { path=path , instance=instance } if (not _GUIs_keyfree[name]) then KEYS_UNLOCK = false end SendScriptCallback("GUI_on_show", name, path) end function Unregister_UI(name) if not (name and _GUIs[name]) then return end local path = _GUIs[name].path printdbg("* Unregister UI: %s", name) _GUIs[name] = nil if is_empty(_GUIs) then KEYS_UNLOCK = true else KEYS_UNLOCK = true for n,p in pairs(_GUIs) do if (not _GUIs_keyfree[n]) then KEYS_UNLOCK = false end end end SendScriptCallback("GUI_on_hide", name, path) end function DestroyAll_UI() hide_hud_all() for n,p in pairs(_GUIsInstances) do local path = p.path local ui = p.instance if path and _G[path] then if ui and _G[path][ui] then _G[path][ui] = nil elseif _G[path].GUI then _G[path].GUI = nil end end _GUIsInstances[n] = nil end end function Check_UI(name) if name then return _GUIs[name] and true or false end return is_not_empty(_GUIs) end function Overlapped_UI(name) if _GUIs["Dialog"] then -- it's ok for dialog to be opened below the inventory return (size_table(_GUIs) > 2) end return (size_table(_GUIs) > 1) end function CloseAll_UI() for name,t in pairs(_GUIs) do local path = t.path local ui = t.instance if path and _G[path] then if ui and _G[path][ui] and _G[path][ui].Close then _G[path][ui]:Close() elseif _G[path].GUI and _G[path].GUI.Close then _G[path].GUI:Close() end end end end ----- function GetActorMenu() return ActorMenu.get_actor_menu() end function hide_hud_all() for name,t in pairs(_GUIs) do local path = t.path local ui = t.instance if path and _G[path] then if ui and _G[path][ui] and _G[path][ui].Close then _G[path][ui]:Close() elseif _G[path].GUI and _G[path].GUI.Close then _G[path].GUI:Close() end end end hide_hud_inventory() hide_hud_pda() end function hide_hud_inventory() local hud = get_hud() if (hud) then hud:HideActorMenu() return true end return false end function hide_hud_pda() if db.actor and db.actor:active_slot() == 8 then db.actor:activate_slot(0) return true end return false --[[ local hud = get_hud() if (hud) then hud:HidePdaMenu() return true end return false --]] end function show_all_ui(show) local hud = get_hud() if not (hud) then return end if(show) then show_indicators(true) -- db.actor:restore_weapon() db.actor:disable_hit_marks(false) hud:show_messages() else if db.actor:is_talking() then db.actor:stop_talk() end level.hide_indicators_safe() hide_hud_all() hud:hide_messages() -- db.actor:hide_weapon() db.actor:disable_hit_marks(true) end end function show_indicators(state) if state then level.show_indicators() else level.hide_indicators_safe() end _HUD_STATE = state end function main_hud_shown() if _HUD_STATE -- indicators enabled and (actor_menu.last_mode == 0) -- inventory is off and (pda.dialog_closed == true) -- dialog is off and (not Check_UI()) -- no active GUI and get_console_cmd(1,"hud_draw") -- command hud is on and (not axr_main.binoc_is_zoomed) -- no zoomed weapon and (not axr_main.scoped_weapon_is_zoomed) -- no zoomed weapon then return true end return false end -------------------------------------------------------------------------------------------------- -- DELAYED EVENT QUEUE ------------------------------------------------------------------------------------------------------- --[[ -- Events must have a unique id. Such as object id or another identifier unique to the occasion. -- Action id must be unique to the specific Event. This allows a single event to have many queued -- actions waiting to happen. -- -- Returning true will remove the queued action. Returning false will execute the action continuously. -- This allows for events to wait for a specific occurrence, such as triggering after a certain amount of -- time only when object is offline -- -- param 1 - Event ID as type -- param 2 - Action ID as type -- param 3 - Timer in seconds as type -- param 4 - Function to execute as type -- extra params are passed to executing function as table as param 1 -- see on_game_load or state_mgr_animation.script for example uses -- This does not persists through saves! So only use for non-important things. -- For example, do not try to destroy npcs unless you do not care that it can fail before player saved then loaded. --]] local ev_queue = {} function CreateTimeEvent(ev_id,act_id,timer,f,...) if not (ev_queue[ev_id]) then ev_queue[ev_id] = {} ev_queue[ev_id].__size = 0 end if not (ev_queue[ev_id][act_id]) then ev_queue[ev_id][act_id] = {} ev_queue[ev_id][act_id].timer = time_global() + timer*1000 ev_queue[ev_id][act_id].f = f ev_queue[ev_id][act_id].p = {...} ev_queue[ev_id].__size = ev_queue[ev_id].__size + 1 end end function RemoveTimeEvent(ev_id,act_id) if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then ev_queue[ev_id][act_id] = nil ev_queue[ev_id].__size = ev_queue[ev_id].__size - 1 end end function ResetTimeEvent(ev_id,act_id,timer) if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then ev_queue[ev_id][act_id].timer = time_global() + timer*1000 end end function ProcessEventQueue(force) if has_alife_info("sleep_active") then return false end for event_id,actions in pairs(ev_queue) do for action_id,act in pairs(actions) do if (action_id ~= "__size") then if (force) or (time_global() >= act.timer) then -- utils_data.debug_write(strformat("event_queue: event_id=%s action_id=%s",event_id,action_id)) if (act.f(unpack(act.p)) == true) then ev_queue[event_id][action_id] = nil ev_queue[event_id].__size = ev_queue[event_id].__size - 1 end end end end if (ev_queue[event_id].__size == 0) then ev_queue[event_id] = nil end end return false end function ProcessEventQueueState(m_data,save) if (save) then m_data.event_queue = ev_queue else ev_queue = m_data.event_queue or ev_queue end end ------------------------------------------------------------------------------------------------------- -- LEVEL HANDLERS ------------------------------------------------------------------------------------------------------- local function timer_travel(pos,lvid,gvid,angle) change_level_now(pos,lvid,gvid,angle) return true end function ChangeLevel(pos,lvid,gvid,angle,anim) -- IMPORTANT: You must realize that when you send this event it will happen immediately -- if done in lua code it will not execute the rest of the block, level changes immediately happen! --[[ NET_Packet p; p.w_begin (M_CHANGE_LEVEL); -- M_CHANGE_LEVEL == 13 p.w (&m_game_vertex_id,sizeof(m_game_vertex_id)); p.w (&m_level_vertex_id,sizeof(m_level_vertex_id)); p.w_vec3 (m_position); p.w_vec3 (m_angles); Level().Send(p,net_flags(TRUE)); --]] -- Hide PDA to avoid issue of loading in another map with PDA on, it might block interaction with travel UI hide_hud_all() -- Stop talking if db.actor:is_talking() then db.actor:stop_talk() end -- requires OpenXRay if anim then SendScriptCallback("on_before_level_changing") level.add_pp_effector("sleep_fade.ppe", 1313, false) CreateTimeEvent(0,"delay_travel",3,timer_travel,pos,lvid,gvid,angle) -- Tronex else change_level_now(pos,lvid,gvid,angle) end end function change_level_now(pos,lvid,gvid,angle) local p = net_packet() p:w_begin(13) p:w_u16(gvid) p:w_u32(lvid) p:w_vec3(pos) p:w_vec3(angle) level.send(p,true) end -- Wrapper for level.add_call -- For some reason in 1.6 engine level.remove_call does not work! If you know why, please contact me @alundaio local level_add_call_unique = {} function AddUniqueCall(functor_a) if (level_add_call_unique[functor_a]) then return end local function wrapper() if not (level_add_call_unique[functor_a]) then return true end if (functor_a()) then level_add_call_unique[functor_a] = nil return true end return false end level_add_call_unique[functor_a] = true level.add_call(wrapper,function() end) end function RemoveUniqueCall(functor_a) level_add_call_unique[functor_a] = nil end function JumpToLevel(new_level) -- requires OpenXray local level_name = level.name() if (level_name == new_level) then return false end local cvertex local sim,gg = alife(),game_graph() -- first try to find a smart_terrain on specified level for id,smart in pairs(db.smart_terrain_by_id) do cvertex = smart and gg:vertex(smart.m_game_vertex_id) if (cvertex and sim:level_name(cvertex:level_id()) == new_level) then ChangeLevel(cvertex:level_point(),cvertex:level_vertex_id(),smart.m_game_vertex_id,VEC_ZERO,true) return true end end -- in case level has no smarts then just teleport to first found gvid for level for gvid=0, 4860 do if gg:valid_vertex_id(gvid) then cvertex = gg:vertex(gvid) lvl = sim:level_name(cvertex:level_id()) if (lvl == new_level) then ChangeLevel(cvertex:level_point(),cvertex:level_vertex_id(),gvid,VEC_ZERO,true) return true end else break end end return false end function TeleportObject(id,pos,lvid,gvid) -- Requires OpenXray if (db.offline_objects[id]) then db.offline_objects[id].level_vertex_id = nil end db.spawned_vertex_by_id[id] = nil alife():teleport_object(id,gvid,lvid,pos) end function TeleportSquad(squad,pos,lvid,gvid) -- Requires OpenXray local sim = alife() sim:teleport_object(squad.id,gvid,lvid,pos) for k in squad:squad_members() do if (db.offline_objects[k.id]) then db.offline_objects[k.id].level_vertex_id = nil end db.spawned_vertex_by_id[k.id] = nil sim:teleport_object(k.id,gvid,lvid,pos) end end function in_time_interval(val1, val2) --Проверка по временному интервалу. local game_hours = level.get_time_hours() if val1 >= val2 then return game_hours < val2 or game_hours >= val1 else return game_hours < val2 and game_hours >= val1 end end function level_changing() -- происходит ли в данный момент смена уровня? -- нужно для того, чтобы объекты знали, какую информацию записывать при сохранении, а какую нет local sim = alife() if (not sim) then return false end local actor_gv = game_graph():vertex( sim:actor().m_game_vertex_id ) return actor_gv:level_id() ~= sim:level_id() end -------------------------------------------------------------------- -- Serialization of userdata for Marshal Library -------------------------------------------------------------------- if (marshal) then function game_CTime___persist(self) local Y, M, D, h, m, s, ms = 0,0,0,0,0,0,0 if (self and self.get) then Y, M, D, h, m, s, ms = self:get(Y, M, D, h, m, s, ms) end return function () local t = game.CTime() t:set(Y, M, D, h, m, s, ms) return t end end getmetatable(game.CTime()).__persist = game_CTime___persist end -- debug to find objects that shouldn't be calling game_object:alive() --[[ game_object.alive = function(self) callstack() printf("alive %s",self:name()) local se_obj = alife_object(self:id()) return se_obj:alive() end --]] -------------------------------------------------------------------- -- Used by modules.script for generic module management schemes = {} schemes_by_stype = {} function LoadScheme(filename, scheme, ...) if not (_G[filename]) then printe("!ERROR: Trying to load scheme that does not exist! %s",filename) return end schemes[scheme] = filename local p = {...} for i=1,#p do if not (schemes_by_stype[p[i]]) then schemes_by_stype[p[i]] = {} end schemes_by_stype[p[i]][scheme] = true end end ------------------------------------------------------------------------------------------------------- -- CONSOLE LOGGERS ------------------------------------------------------------------------------------------------------- function printf(fmt,...) -- formatted logging if not (fmt) then return end local fmt = tostring(fmt) if (select('#',...) >= 1) then local i = 0 local p = {...} local function sr(a) i = i + 1 if (type(p[i]) == 'userdata') then if (p[i].x and p[i].y) then return vec_to_str(p[i]) end return 'userdata' end return tostring(p[i]) end fmt = string_gsub(fmt,"%%s",sr) end if (log) then log(fmt) --exec_console_cmd("flush") else exec_console_cmd("load ~#debug msg:"..fmt) end end function printe(fmt,...) -- error logging if DEV_DEBUG and db.actor and (not device():is_paused()) and ui_options.get("other/debug_error") then local hud = get_hud() if (hud) then local err = hud:GetCustomStatic("debug_error") if (err == nil) then hud:AddCustomStatic("debug_error", true) end end end printf(fmt,...) end function printdbg(fmt,...) -- debug logging if DEV_DEBUG then printf(fmt,...) end end function abort(msg, ...) if not (msg) then return end local fmt = tostring(msg) if (select('#',...) >= 1) then local i = 0 local p = {...} local function sr(a) i = i + 1 if (type(p[i]) == 'userdata') then return 'userdata' end return tostring(p[i]) end fmt = string_gsub(fmt,"%%s",sr) end callstack() log(fmt) --[[ error(fmt, 2) --]] end function callstack(c1, to_str) -- stack trace logging -- Tronex: modified this for less shit to read -- c1: only print caller function, useful to trace calls happening for specific function -- to_str: return stack trace as string if we want to use it in custom prints if (log and debug and type(debug.traceback) == 'function') then --log( debug.traceback('\n', 2) ) -- Path is not needed local str = string_gsub( debug.traceback('', 2) , "%.%.%.(.-)\\scripts\\" , "... " ) -- Line text for ss in string_gmatch(str,":(%d*):") do local num = string_gsub( ss , ":","") str = string_gsub( str , ":" .. num .. ":" , " (line: " .. num .. ")" ) end str = string_gsub( str , "^(.-):" , "" ) -- In case we want first caller only if c1 then local cnt = 0 for ss in string_gmatch(str,"'(.-)'") do cnt = cnt + 1 local f = string_gsub( ss , "'","") str = string_gsub( str , "'" .. f .. "'" , "'" .. f .. "'[" .. cnt .. "]" ) -- mark lines with numbers end str = string_gsub( str , "%[2%](.*)$" , "") -- remove everything after second line str = string_gsub( str , "^(.*)%.%.%." , "") -- remove crap at beginning end -- in case we want a string back if to_str then return str elseif c1 then log( "STACK TRACEBACK: " .. str ) else -- Framing log( "~ ------------------------------------------------------------------------" ) log( "~ STACK TRACEBACK:" ) log( str ) log( "~ ------------------------------------------------------------------------" ) end end end function get_console_cmd(typ, name) if (type(name) ~= "string") then callstack() printe("!ERROR exec_console_cmd | missing command") return end if typ == 0 then return get_console():get_string(name) elseif typ == 1 then return get_console():get_bool(name) elseif typ == 2 then return get_console():get_float(name) end return get_console():get_token(name) end function exec_console_cmd(name) if (type(name) ~= "string") then callstack() printe("!ERROR exec_console_cmd | missing command") return end get_console():execute(name) SendScriptCallback("on_console_execute", unpack(str_explode(name," "))) end ------------------------------------------------------------------------------------------------------- -- MATHEMATICAL ------------------------------------------------------------------------------------------------------- -- іонстанта, которуі использовать в местах, где нужно задать неограниченное время действия time_infinite = 100000000 function time_global() return device():time_global() end function time_continual() return device():time_continual() end function round(value) return math.floor(value+0.5) end function round_idp(num, idp) local mult = 10^(idp or 0) return math_floor(num * mult + 0.5) / mult end function round_100(num) return math_floor(num * 100 + 0.5) / 100 end function odd(x) return not ( ( math_floor( x * 0.5 ) * 2 ) == math_floor( x ) ) end function clamp(val, min, max) return (val < min and min) or (val > max and max) or val end function normalize(val, min, max) local d = (val-min)/(max-min) return d < 0 and 0 or d end function normalize_100(val, min, max) return (normalize(val, min, max) * 100) end function random_choice(...) local arg = {...} if (#arg > 0) then local r = math.random(1, #arg) return arg[r] end end function random_number(min_value, max_value) if min_value == nil and max_value == nil then return math.random () else return math.random (min_value, max_value) end end function random_float(min_value, max_value) return min_value + math.random() * (max_value - min_value) 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 yaw_degree( 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 ) ) ) * 57.2957) end function yaw_degree3d( v1, v2 ) return (math_acos((v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)/(math_sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z )*math_sqrt(v2.x*v2.x + v2.y*v2.y + v2.z*v2.z)))*57.2957) end function vector_cross(v1, v2) return vector():set(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x) end function vec_to_str (vector) -- переводит вектор в строку. if vector == nil then return "nil" end return string_format("[%s:%s:%s]", vector.x, vector.y, vector.z) end --Поворачивает вектор вокруг оси y против часовой стрелки. function vector_rotate_y(v, angle) angle = angle * 0.017453292519943295769236907684886 local c = math_cos (angle) local s = math_sin (angle) return vector():set(v.x * c - v.z * s, v.y, v.x * s + v.z * c) end function distance_2d( a, b ) return math.sqrt( (b.x-a.x)^2 + (b.z-a.z)^2 ) end function distance_2d_sqr(a,b) return (b.x-a.x)^2 + (b.z-a.z)^2 end ------------------------------------------------------------------------------------------------------- -- STRINGS ------------------------------------------------------------------------------------------------------- function string.gsplit(s, sep, plain) local start = 1 local done = false local function pass(i, j, ...) if i then local seg = s:sub(start, i - 1) start = j + 1 return seg, ... else done = true return s:sub(start) end end return function() if done then return end if sep == '' then done = true return s end return pass(s:find(sep, start, plain)) end end function trim(s) return (string_gsub(s, "^%s*(.-)%s*$", "%1")) end function strformat(text,...) if not (text) then return end local i = 0 local p = {...} local function sr(a) i = i + 1 if (type(p[1]) == "userdata") then return "userdata" end return tostring(p[i]) end -- so that it doesn't return gsub's multiple returns local s = string_gsub(text,"%%s",sr) return s end function str_explode(str, sep, plain) if not (str and sep) then printe("!ERROR str_explode | missing parameter str = %s, sep = %s",str,sep) callstack() end if not (sep ~= "" and string_find(str,sep,1,plain)) then return { str } end local t = {} local size = 0 for s in str:gsplit(sep,plain) do size = size + 1 t[size] = trim(s) end return t end function parse_list(ini, key, val, convert) if not (ini and key and val) then error(strformat("parse_list ini is nil for key=%s val=%s",key,val)) end local str = ini:r_string_ex(key,val) local t = str and str ~= "" and str_explode(str,",") or {} if (convert and #t > 0) then local l = {} for i=1,#t do l[t[i]] = true end return l end return t end function parse_names( s ) local t = {} local size_t = 0 for name in string_gmatch( s, "([%w_%-.\\]+)%p*" ) do size_t = size_t + 1 t[size_t] = name end return t end function parse_key_value( s ) local t = {} if s == nil then return nil end local key, nam = nil, nil for name in string_gmatch( s, "([%w_\\]+)%p*" ) do if key == nil then key = name else t[key] = name key = nil end end return t end function parse_nums( s ) local t = {} for entry in string_gmatch( s, "([%-%d%.]+)%,*" ) do t[#t+1] = tonumber(entry) end return t end function parse_func(sec, param, ...) local func = ini_sys:r_string_ex(sec, param) func = str_explode(func,"%.") return func and _G[func[1]] and _G[func[1]][func[2]] and _G[func[1]][func[2]](...) or nil end function starts_with(str, start_txt) return str:sub(1, #start_txt) == start_txt end function has_translation(string_id) return (game.translate_string(string_id) ~= string_id) end function get_param_string(src_string, obj) -- преобразует строку в соответствии со значением --printf("src_string is [%s] obj name is [%s]", tostring(src_string), obj:name()) local script_ids = db.script_ids[obj:id()] local out_string, num = string_gsub(src_string, "%$script_id%$", tostring(script_ids)) if num > 0 then return out_string , true else return src_string , false end end ------------------------------------------------------------------------------------------------------- -- ACTIONS ------------------------------------------------------------------------------------------------------- function execute_func(file, func, ...) if file and func and _G[file] and _G[file][func] then return _G[file][func](...) end end function reset_action (npc, script_name) if npc:get_script () then npc:script (false, script_name) end npc:script (true, script_name) end function stop_play_sound(obj) if (IsStalker(obj) and not obj:alive()) then return end obj:set_sound_mask(-1) obj:set_sound_mask(0) end --[[ does not work? 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 = time_global() + time_to_wait while time_global() <= time_to_stop do coroutine.yield() end end end --]] function action(obj,...) local arg = {...} local e_act = entity_action() for i=1,#arg do e_act:set_action(arg[i]) end if (obj ~= nil) then obj:command(e_act,false) end return entity_action(e_act) end function action_first(obj,...) local arg = {...} local e_act = entity_action() for i=1,#arg do e_act:set_action(arg[i]) end if (obj ~= nil) then obj:command(e_act,true) end return entity_action(e_act) end -- +сли в даннvй момент вvполняется какое-то действие, прерvвает его и отклічает скриптовvй режим function interrupt_action(who, script_name) if who:get_script() then who:script(false, script_name) end end function get_clsid(obj) if not (obj) then callstack() printe("!ERROR: get_clsid - obj is nil!") return end if not (obj.clsid) then callstack() printe("!ERROR: no clsid method for %s",obj:name()) return end return obj:clsid() end --' Lсталость function on_actor_critical_power() end function on_actor_critical_max_power() end --' іровотечение function on_actor_bleeding() end function on_actor_satiety() end --' іадиация function on_actor_radiation() end --' іаклинило оружие function on_actor_weapon_jammed() end --' не может ходить изза веса function on_actor_cant_walk_weight() end --' пси воздействие function on_actor_psy() end local save_marker_result = {} -- Функции для проверки корректности сейв лоад function set_save_marker(p, mode, check, prefix) prefix = tostring(prefix) if (check ~= true) then if mode == "save" then save_marker_result[prefix] = p:w_tell() or 0 if p:w_tell() > 16000 then abort("ERROR: You are saving too much") end else save_marker_result[prefix] = p:r_tell() or 0 end return end if not (save_marker_result[prefix]) then abort("ERROR set_save_marker:%s: Trying to check without marker mode=%s",prefix,mode) if (mode == "save") then p:w_u16(0) elseif (mode == "load") then p:r_u16() end return end if mode == "save" then local dif = p:w_tell() - save_marker_result[prefix] if dif >= 8000 then printf("!ERROR set_save_marker:%s: WARNING! may be this is problem save point dif=%s",prefix,dif) end p:w_u16(dif) else local c_dif = p:r_tell() - save_marker_result[prefix] local dif = p:r_u16() if dif ~= c_dif then printf("!ERROR set_save_marker:%s: INCORRECT LOAD dif=%s c_dif=%s", prefix, dif, c_dif) end end save_marker_result[prefix] = nil end ------------------------------------------------------------------------------------------------------ -- ENGINE EXPORTS!!! (Player utilities) ------------------------------------------------------------------------------------------------------ -- added: flags, CInventory__eat, CActor__BeforeHitCallback, local flags = { ret_value = true } -- called when an inventory item is eaten/used -- returning false will prevent the item from being used function CInventory__eat(item) flags.ret_value = true SendScriptCallback("actor_on_item_before_use",item,flags) return flags.ret_value end -- called after actor hit callbacks, can be used to replace engine's artefact resistance calculation -- How to override hit_power: -- hit_table["override"] = true -- hit_table["hit_power"] = {new damage value} function CActor__HitArtefactsOnBelt(hit_table, hit_power, hit_type) SendScriptCallback("actor_on_before_hit_belt",hit_table, hit_power, hit_type) return hit_table end -- Called on CHudItem Motion Mark function CHudItem__OnMotionMark(state, mark) SendScriptCallback("actor_on_hud_animation_mark",state,mark) end function CHudItem__PlayHUDMotion(anm_table, obj) SendScriptCallback("actor_on_hud_animation_play",anm_table,obj) return anm_table end function player_hud__OnMovementChanged(cmd) SendScriptCallback("actor_on_movement_changed",cmd) end -- Called before actor hit callback -- returning false will ignore the hit completely function CActor__BeforeHitCallback(actor,shit,bone_id) --printf("power=%s impuse=%s type=%s dir=%s who=%s",shit.power,shit.impulse,shit.type,shit.direction and vec_to_str(shit.direction),shit.draftsman and shit.draftsman:name()) if (shit.type ~= hit.strike) then if (bind_stalker_ext.invulnerable_time and time_global() < bind_stalker_ext.invulnerable_time) then bind_stalker_ext.invulnerable_time = bind_stalker_ext.invulnerable_time - 500 return false end end if (shit.power > 0) then if (shit.draftsman and shit.draftsman:id() ~= 0 and IsStalker(shit.draftsman) and shit.draftsman:relation(db.actor) == game_object.friend) then return false end end flags.ret_value = true SendScriptCallback("actor_on_before_hit",shit,bone_id,flags) return flags.ret_value end -- called in ai_stalker_fire.cpp CAI_Stalker::Hit() -- returning false will ignore the hit completely function CAI_Stalker__BeforeHitCallback(npc,shit,bone_id) flags.ret_value = true SendScriptCallback("npc_on_before_hit",npc,shit,bone_id,flags) return flags.ret_value end -- called in base_monster.cpp CBaseMonster::Hit() -- returning false will ignore the hit completely function CBaseMonster__BeforeHitCallback(monster,shit,bone_id) flags.ret_value = true SendScriptCallback("monster_on_before_hit",monster,shit,bone_id,flags) return flags.ret_value end -- called in step_manager.cpp CStepManager::update() -- returning false will not play a footstep sound function CActor__FootstepCallback(material,power,hud_view) flags.ret_value = true SendScriptCallback("actor_on_footstep",material,power,hud_view,flags) return flags.ret_value end -- Called upon weapon fire keybind, weapons can be stopped from firing if returns false function CActor_Fire() flags.ret_value = true SendScriptCallback("actor_on_weapon_before_fire",flags) return flags.ret_value end -- called in CustomZone.cpp CCustomZone::shedule_Update() -- returning false will make the CustomZone ignore the object function CCustomZone_BeforeActivateCallback(zone,obj) flags.ret_value = true SendScriptCallback("anomaly_on_before_activate",zone,obj,flags) return flags.ret_value end -- called in burer.cpp CBurer::StaminaHit() -- returning false will stop the burer from knocking player weapon away function CBurer_BeforeWeaponDropCallback(monster,wpn) flags.ret_value = true SendScriptCallback("burer_on_before_weapon_drop",monster,wpn,flags) return flags.ret_value end -- Called upon thowing bolt, returning false will prevent the bolt from being released aftering throwing. Parameter is id of npc who threw the bolt function CBolt__State(id) if (id == AC_ID) and game_difficulties.get_eco_factor("limited_bolts") then return true end return false end -- Called when actor is close/in-touch with an anomaly field function CZone_Touch(obj) flags.ret_value = true SendScriptCallback("actor_on_feeling_anomaly", obj, flags) return flags.ret_value end -- Called on resolution change (after vid_restart) function CHUDManager_OnScreenResolutionChanged() DestroyAll_UI() SendScriptCallback("on_screen_resolution_changed") end -- Called when the actor jumps function CActor_on_jump() SendScriptCallback("actor_on_jump") end -- Called when the actor lands function CActor_on_land(landing_speed) SendScriptCallback("actor_on_land", landing_speed) end get_console():execute("r__clear_models_on_unload 0") function CALifeUpdateManager__on_before_change_level(packet) --[[ C++: net_packet.r (&graph().actor()->m_tGraphID,sizeof(graph().actor()->m_tGraphID)); net_packet.r (&graph().actor()->m_tNodeID,sizeof(graph().actor()->m_tNodeID)); net_packet.r_vec3 (graph().actor()->o_Position); net_packet.r_vec3 (graph().actor()->o_Angle); --]] -- Here you can do stuff when level changes BEFORE save is called, even change destination!. Packet is constructed as stated above -- Release dead bodies on level change (TODO: Determine if it's a bad idea to do this here) -- local rbm = release_body_manager.get_release_body_manager() if (rbm) then rbm:clear(true) end -- -- READ PACKET local pos,angle = vector(),vector() local gvid = packet:r_u16() local lvid = packet:r_u32() packet:r_vec3(pos) packet:r_vec3(angle) -- crazy hack to help prevent crash on Trucks Cemetery --[[local gg = game_graph() if (gg:valid_vertex_id(gvid) and alife():level_name(gg:vertex(gvid):level_id()) == "k02_trucks_cemetery") then log("k02_trucks_cemetery hack r__clear_models_on_unload 1") exec_console_cmd("r__clear_models_on_unload 1") end --]] --printf("CALifeUpdateManager__on_before_change_level pos=%s gvid=%s lvid=%s angle=%s",pos,gvid,lvid,angle) -- fix for car in 1.6 (TODO*kinda For some reason after loading a game ALL physic objects will not be teleported by TeleportObject need to investigate as to why, possibly something to do with object flags) local car = db.actor and db.actor:get_attached_vehicle() if (car) then TeleportObject(car:id(),pos,lvid,gvid) end -- REPACK it for engine method to read as normal --[[ packet:w_begin(13) packet:w_u16(gvid) packet:w_u32(lvid) packet:w_vec3(pos) packet:w_vec3(angle) --]] -- reset read pointer packet:r_seek(2) if (bind_container.containers) then for id,t in pairs(bind_container.containers) do if (t.id) then pos.y = pos.y+100 TeleportObject(t.id,pos,lvid,gvid) end end end end -- 'Запуск динамического окна. function run_dynamic_element(folder,close_inv) if close_inv==false then folder:ShowDialog(true) elseif close_inv==true then folder:ShowDialog(true) hide_hud_all() level.show_weapon(false) else folder:ShowDialog(true) end end -- 'Создание предмета в рюкзаке ГГ. function give_object_to_actor(sec,count) if count==nil then count=1 end for i=1, count do alife_create_item(sec, db.actor) end end actor_move_states = { ['mcFwd'] = 1, ['mcBack'] = 2, ['mcLStrafe'] = 4, ['mcRStrafe'] = 8, ['mcCrouch'] = 16, ['mcAccel'] = 32, ['mcTurn'] = 64, ['mcJump'] = 128, ['mcFall'] = 256, ['mcLanding'] = 512, ['mcLanding2'] = 1024, ['mcClimb'] = 2048, ['mcSprint'] = 4096, ['mcLLookout'] = 8192, ['mcRLookout'] = 16384, ['mcAnyMove'] = 15, ['mcAnyAction'] = 1935, ['mcAnyState'] = 6192, ['mcLookout'] = 24576, } function IsMoveState(state, compare_state) local bit = actor_move_states[state] if (not bit) then printf("~IsMoveState | state {%s) not found ", state) return false end if (not compare_state) then compare_state = level.actor_moving_state() end return (bit_and(compare_state, bit) ~= 0) end ------------------------------------------------------------------------------------------------------- -- INI EXTENSIONS ------------------------------------------------------------------------------------------------------- local ini_cache = { [ini_sys] = {} } function clear_ini_cache(ini) ini_cache[ini] = empty_table(ini_cache[ini]) empty_table(INISYS_CACHE) end function reload_ini_sys() if (reload_system_ini) then reload_system_ini() ini_sys = system_ini() printf("# reload_system_ini") else printf("! reload_system_ini is not supported in engine") end end if (USE_INI_MEMOIZE) then -- memoize ini results local function r_memoize(ini,s,k,def,typ) if not (s) then callstack() end if (ini_cache[ini]) then local key = s.."&"..k if (ini_cache[ini][key]) then return ini_cache[ini][key] end end if not (ini:section_exist(s) and ini:line_exist(s,k)) then return def end local result = ini:r_string(s,k) if (result) then if (typ == 0) then result = result == "true" or result == "1" or false elseif (typ == 1) then result = tonumber(result) end if (result ~= nil) then if (ini_cache[ini]) then local key = s.."&"..k if (ini_cache[ini][key]) then ini_cache[ini][key] = result end end end end return result == nil and def or result end function ini_file.r_string_ex(ini,s,k,def) return r_memoize(ini,s,k,def) end -- It is wise to use the def with r_bool_ex, because false and nil are consider 'not'. def is only returned on nil function ini_file.r_bool_ex(ini,s,k,def) return r_memoize(ini,s,k,def,0) end function ini_file.r_float_ex(ini,s,k,def) return r_memoize(ini,s,k,def,1) end function ini_file.r_sec_ex(ini,s,k,def) local result = ini_file.r_string_ex(ini,s,k,def) if result and ini_sys:section_exist(result) then return result end callstack() printe("!ERROR: section [%s] don't exist! Reading from section: %s - value: %s", result, s, k) return nil end function ini_file.r_line_ex(ini,s,k) if not (ini_cache[ini]) then ini_cache[ini] = {} end if (ini_cache[ini]) then local key = s .. "&" .. k if (ini_cache[ini][key]) then return unpack(ini_cache[ini][key]) end end local a,b,c = ini:r_line(s,k,"","") if (ini_cache[ini]) then local key = s.."&"..k if (ini_cache[ini][key]) then ini_cache[ini][key] = {a,b,c} end end return a,b,c end else function ini_file.r_string_ex(ini,s,k) --callstack() --printf("r_string_ex(%s,%s)",s,k) return ini:section_exist(s) and ini:line_exist(s,k) and ini:r_string(s,k) or nil end function ini_file.r_float_ex(ini,s,k) --callstack() --printf("r_float_ex(%s,%s)",s,k) return ini:section_exist(s) and ini:line_exist(s,k) and ini:r_float(s,k) or nil end function ini_file.r_bool_ex(ini,s,k,def) --callstack() if not (ini:section_exist(s) and ini:line_exist(s,k)) then return def end --printf("r_bool_ex(%s,%s)",s,k) local v = ini:r_string(s,k) return v == nil and def or v == "true" or v == "1" or false end function ini_file.r_sec_ex(ini,s,k,def) local result = ini_file.r_string_ex(ini,s,k,def) if result and ini_sys:section_exist(result) then return result end callstack() printe("!ERROR: section [%s] don't exist! Reading from section: %s - value: %s", result, s, k) return nil end function ini_file.r_line_ex(ini,s,k) --callstack() return ini:r_line(s,k,"","") end end function ini_file.r_string_to_condlist(ini,s,k,def) local src = ini:r_string_ex(s,k) or def if (src) then return xr_logic.parse_condlist(nil, s, k, src) end end function ini_file.r_list(ini,s,k,def) local src = ini:r_string_ex(s,k) or def if (src) then return parse_names(src) end end function ini_file.r_mult(ini,s,k,...) local src = ini:r_string_ex(s,k) if (src) then return unpack(parse_names(src)) end return ... end ----------------------------------------- -- New INI wrapper to replace utils_data.cfg_file class "ini_file_ex" function ini_file_ex:__init(fname,advanced_mode) self.fname = getFS():update_path('$game_config$', '')..fname self.ini = ini_file(fname) self.cache = {} if (advanced_mode) then self.ini:set_override_names(true) self.ini:set_readonly(false) self.ini:save_at_end(false) end end function ini_file_ex:save() self.ini:save_as(self.fname) end -- r_value and w_value cache results function ini_file_ex:r_value(s,k,typ,def) local cache_result = self.cache[s.."&"..k] if (cache_result) then return cache_result end if not (self.ini:section_exist(s) and self.ini:line_exist(s,k)) then return def end local v = self.ini:r_string(s,k) if (typ == 1) then v = v == nil and def or v == "true" or false elseif (typ == 2) then v = tonumber(v) or def end self.cache[s.."&"..k] = v return v == nil and def or v end function ini_file_ex:w_value(s,k,val,comment) self.cache[s.."&"..k] = val self.ini:w_string(s,k,val ~= nil and tostring(val) or "",comment ~= nil and tostring(comment) or "") end function ini_file_ex:collect_section(section) local _t = {} local n = self.ini:section_exist(section) and self.ini:line_count(section) or 0 if (n > 0) then for i = 0,n-1 do local res,id,val = self.ini:r_line(section,i,"","") _t[id] = val end end return _t end function ini_file_ex:get_sections(keytable) local t = {} local function itr(section) if (keytable) then t[section] = true else t[#t+1] = section end return false end self.ini:section_for_each(itr) return t end function ini_file_ex:remove_line(section,key) self.ini:remove_line(section,key) end function ini_file_ex:section_exist(section) return self.ini:section_exist(section) end function ini_file_ex:line_exist(section,key) return self.ini:section_exist(section) and self.ini:line_exist(section,key) end function ini_file_ex:r_string_ex(s,k) return self.ini:section_exist(s) and self.ini:line_exist(s,k) and self.ini:r_string(s,k) or nil end function ini_file_ex:r_bool_ex(s,k,def) if not(self.ini:section_exist(s) and self.ini:line_exist(s,k)) then return def end local v = self.ini:r_string(s,k) return v == nil and def or v == "true" or v == "1" or false end function ini_file_ex:r_float_ex(s,k) return self.ini:section_exist(s) and self.ini:line_exist(s,k) and tonumber(self.ini:r_string(s,k)) or nil end function ini_file_ex:r_string_to_condlist(s,k,def) local src = self:r_string_ex(s,k) or def if (src) then return xr_logic.parse_condlist(nil, s, k, src) end end function ini_file_ex:r_list(s,k,def) local src = self:r_string_ex(s,k) or def if (src) then return parse_names(src) end end function ini_file_ex:r_mult(s,k,...) local src = self:r_string_ex(s,k) or def if (src) then return unpack(parse_names(src)) end return ... end -- param caching INISYS_CACHE = {} function SYS_GetParam(typ, sec, param, def) if not (sec and param) then printe("!ERROR SYS_GetParam(%s, %s, %s) | What's missing?", typ, sec, param) callstack() return end if (INISYS_CACHE[sec] == nil) then INISYS_CACHE[sec] = {} end if (INISYS_CACHE[sec][param] == nil) then if typ == 0 then INISYS_CACHE[sec][param] = ini_sys:r_string_ex(sec,param) or def elseif typ == 1 then INISYS_CACHE[sec][param] = ini_sys:r_bool_ex(sec,param) elseif typ == 2 then INISYS_CACHE[sec][param] = ini_sys:r_float_ex(sec,param) or def end end return INISYS_CACHE[sec][param] end ------------------------------------------------------------------------------------------- -- TABLES HANDLERS ------------------------------------------------------------------------------------------- function is_empty(t) if not (t) then return true end for i,j in pairs(t) do return false end return true end function is_not_empty(t) return (not is_empty(t)) end function iempty_table(t) if not (t) then return {} end while #t > 0 do table.remove(t) end return t end function empty_table(t) if not (t) then return {} end for k,v in pairs(t) do t[k] = nil end return t end function size_table(t) assert(t and type(t) == "table", "global function size_table() called with invalid argument (nil or not table)") local n = 0 for k,v in pairs(t) do n = n + 1 end return n end function random_key_table(t) local check = t and type(t) == "table" if not check then callback() assert(nil, "global function random_key_table() called with invalid argument (nil or not table)") end local n = {} for k,v in pairs(t) do n[#n+1] = k end return #n > 0 and n[math.random(#n)] or nil end function copy_table(dest, src) for k,v in pairs(src) do if type(v) == "table" then --' рекурсивный вызов себя же для подтаблиц dest[k] = {} copy_table(dest[k], v) else dest[k] = v end end end function dup_table(src) local t = {} copy_table(t, src) return t end local function swap(array, index1, index2) array[index1], array[index2] = array[index2], array[index1] end function shuffle_table(t) local counter = #t while counter > 1 do local index = math.random(counter) swap(t, index, counter) counter = counter - 1 end end function invert_table(t) local a = {} for k,v in pairs(t) do a[v] = k end return a end function t2k_table(t) local a = {} for i=1,#t do a[t[i]] = true end empty_table(t) copy_table(t,a) end function k2t_table(t) local a = {} for k,v in pairs(t) do a[#a+1] = k end empty_table(t) copy_table(t,a) end function print_table(table, subs) --[[ local sub if subs ~= nil then sub = subs else sub = "" end for k,v in pairs(table) do if type(v) == "table" then print_table(v, sub.."["..k.."]----->") elseif type(v) == "function" then printf(sub.."%s = function",k) elseif type(v) == "userdata" then if (v.x) then printf(sub.."%s = %s",k,utils_data.vector_to_string(v)) else printf(sub.."%s = userdata", k) end elseif type(v) == "boolean" then if v == true then if(type(k)~="userdata") then printf(sub.."%s = true",k) else printf(sub.."userdata = true") end else if(type(k)~="userdata") then printf(sub.."%s = false", k) else printf(sub.."userdata = false") end end else if v ~= nil then printf(sub.."%s = %s", k,v) else printf(sub.."%s = nil", k,v) end end end --]] end function store_table(table, subs) local sub if subs ~= nil then sub = subs else sub = "" end printf(sub.."{") for k,v in pairs(table) do if type(v) == "table" then printf(sub.."%s = ", tostring(k)) store_table(v, sub.." ") elseif type(v) == "function" then printf(sub.."%s = \"func\",", tostring(k)) elseif type(v) == "userdata" then printf(sub.."%s = \"userdata\",", tostring(k)) elseif type(v) == "string" then printf(sub.."%s = \"%s\",", tostring(k), tostring(v)) else printf(sub.."%s = %s,", tostring(k), tostring(v)) end end printf(sub.."},") end function spairs(t, order) -- collect the keys local keys = {} for k in pairs(t) do keys[#keys+1] = k end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t, a, b) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end ------------------------------------------------------------------------------------------- -- VECTORS ------------------------------------------------------------------------------------------- VEC_ZERO = vector():set(0,0,0) VEC_X = vector():set(1,0,0) VEC_Y = vector():set(0,1,0) VEC_Z = vector():set(0,0,1) function vec_sub(a,b) return vector():set(a):sub(b) end function vec_add(a,b) return vector():set(a):add(b) end function vec_set(vec) return vector():set(vec) end ------------------------------------------------------------------------------------------- -- CONSTANTS ------------------------------------------------------------------------------------------- BoneID = { -- [""] = 1 , ["bip01_pelvis"] = 2 , ["bip01_l_thigh"] = 3 , ["bip01_l_calf"] = 4 , ["bip01_l_foot"] = 5 , -- [""] = 6 , ["bip01_r_thigh"] = 7 , ["bip01_r_calf"] = 8 , ["bip01_r_foot"] = 9 , -- [""] = 10, ["bip01_spine"] = 11, ["bip01_spine1"] = 12, ["bip01_spine2"] = 13, ["bip01_neck"] = 14, ["bip01_head"] = 15, ["eye_left"] = 16, ["eye_right"] = 17, ["eyelid_1"] = 18, ["jaw_1"] = 19, ["bip01_l_clavicle"] = 20, ["bip01_l_upperarm"] = 21, ["bip01_l_forearm"] = 22, ["bip01_l_hand"] = 23, -- [""] = 24, } HitTypeID = { ["Burn"] = 0 , ["Shock"] = 1 , ["ChemicalBurn"] = 2 , ["Radiation"] = 3 , ["Telepatic"] = 4 , ["Wound"] = 5 , ["FireWound"] = 6 , ["Strike"] = 7 , ["Explosion"] = 8 , ["Wound_2"] = 9 , ["LightBurn"] = 10, } BoosterID = { ["HpRestore"] = 0, ["PowerRestore"] = 1, ["RadiationRestore"] = 2, ["BleedingRestore"] = 3, ["MaxWeight"] = 4, ["RadiationProtection"] = 5, ["TelepaticProtection"] = 6, ["ChemicalBurnProtection"] = 7, ["BurnImmunity"] = 8, ["ShockImmunity"] = 9, ["RadiationImmunity"] = 10, ["TelepaticImmunity"] = 11, ["ChemicalBurnImmunity"] = 12, ["ExplImmunity"] = 13, ["StrikeImmunity"] = 14, ["FireWoundImmunity"] = 15, ["WoundImmunity"] = 16, ["MaxCount"] = 17, } SCANNED_SLOTS = { [1] = true, --knife [2] = true, --wpn 1 [3] = true, --wpn 2 [4] = true, --grenades [5] = true, --binoculars [6] = true, --bolt [7] = true, --outfit [8] = true, --PDA [9] = true, --detector [10] = true, --torch [11] = true, --artefact [12] = true, --helmet [13] = true, --backpack --[14] = true, --script animation } ------------------------------------------------------------------------------------------------------- -- SERVER OBJECTS ------------------------------------------------------------------------------------------------------- _ALIFE_CNT = 0 -- collects total number of server objects _ALIFE_WARNING = 64000 -- warn user if alife storage exceeds this number _ALIFE_CACHE = {} -- collect names of objects that get created or released in order _ALIFE_CACHE_RECORD = false -- record _ALIFE_CACHE _ALIFE_UNREGISTER = {} function alife_object(id) if (id == nil or id >= 65535) then callstack() printe("!ALIFE OBJECT ID IS %s!",id) return end return alife():object(id) end function alife_create(sec,pos,lid,gid,id,state) --[[ Use it just like how you use alife():create Alternative use: alife_create(sec,obj,with_id,state) This is fast way to spawn something using other object properties with_id: [bool] count object id (to spawn item inside object) state: [bool - false] to spawn unregisted object Example: alife_create(sec,obj) will spawn object based on obj properties, no id included so it will spawn on world --]] --[[ return if alife storage is almost filled if alife_on_limit() then return end --]] -- In case we passed object to spawn at if (type(pos) == "userdata") and (not pos.x) then local obj = pos local with_id = (lid == true) state = gid if (type(obj.id) == "function") then pos = obj:position() lid = obj:level_vertex_id() gid = obj:game_vertex_id() id = with_id and obj:id() elseif obj.id then pos = obj.position lid = obj.m_level_vertex_id gid = obj.m_game_vertex_id id = with_id and obj.id else callstack() printe("!ERROR: alife_create | invalid usedata type") return end end -- Validate if not (sec and ini_sys:section_exist(sec)) then callstack() printe("!ERROR: alife_create | section [%s] is invalid!",sec) return elseif not (pos and lid and gid) then callstack() printe("!ERROR: invalid parameter for alife():create!",sec) return end --callstack() local se_obj if (state ~= nil) then --printf("- alife_create [%s] unregistered", sec) se_obj = alife():create(sec, pos, lid, gid, id, state) elseif id then --printf("- alife_create [%s] on owner (%s)", sec, id) se_obj = alife():create(sec, pos, lid, gid, id) else --printf("- alife_create [%s] in world", sec) se_obj = alife():create(sec, pos, lid, gid) end if (not se_obj) then printe("!ERROR: alife_create | failed to create object [%s]!",sec) return else alife_record(se_obj,true) return se_obj end end function alife_create_item(section, obj, t) --[[ section: [string] (required) item section obj: [object/table] (required) owner of the item we want to spawn. can be a game or server object it can be a table {pos,lvl_id,game_id,id} for custom spawn in the Zone t: [table] (optional) item property table, can be used to process spawned item, like spawning multiuse item with specific uses for example. available keys: cond [num] = to apply custom condition on the item with condition bars uses [num] = to apply custom uses on the multi-use items ammo [num] = to apply custom ammo amount on the ammo boxes cond_r [table] = same as cond, but it picks a random condition in specified range (if it has more than 2 keys, then it will pick up a value randomly) cond_ct [string] = specific item type that it accept different condition range (from cond_cr) cond_cr [table] = same as cond_r, but applies it to specified item type (by cond_ct) NOTES: item property table (except ammo) only works on online items, if an item is spawned somewhere outside of online radius then it won't be processed TODO: - Add support for offline items properties - Add support for spawning unregistered items --]] --printf("alife_create_item | [%s]",section) --callstack() --[[ return if alife storage is almost filled if alife_on_limit() then return end --]] return itms_manager.create_item(section, obj, t) end function alife_process_item(section, id, t) -- id = item id to process -- t.cond [num] = apply passed condition -- t.uses [num] = apply passed uses count -- t.ammo [num] = apply passed ammo count itms_manager.process_item(section, id, t) end function alife_release(se_obj, msg) --printf("alife_release") --msg = msg or "" --callstack() -- Convert to server object if its a game object if se_obj and (type(se_obj.id) == "function") then se_obj = alife_object(se_obj:id()) end if (not se_obj) then callstack() printe("!ERROR: alife_release | no server object!") return nil end -- Custom prints for debugging if msg then printf("~ alife_release [%s] (%s) | %s", se_obj:section_name(), se_obj.id, msg) end -- Walkaround for unregisted server objects callbacks for objects that can't be used at the moment for class registor local cls = se_obj:clsid() local class = ini_sys:r_string_ex(se_obj:section_name(),"class") or "" if _ALIFE_UNREGISTER[class] then SendScriptCallback("server_entity_on_unregister", se_obj, _ALIFE_UNREGISTER[class]) end -- NPC if IsStalker(nil,cls) or IsMonster(nil,cls) then local squad = se_obj.group_id and se_obj.group_id ~= 65535 and alife_object(se_obj.group_id) if squad then squad:remove_npc(se_obj.id, true) return true end -- Squads elseif (cls == clsid.online_offline_group_s or cls == clsid.online_offline_group) then se_obj:remove_squad() return true -- Others else alife_record(se_obj,false) alife():release(se_obj,true) return true end return false end function alife_release_id(id, msg) if (not id) then callstack() printe("!ERROR: alife_release_id | no id passed!") return end return alife_release( alife_object(id), msg) end function alife_clone_weapon(se_obj, section, parent_id) if (not se_obj) then callstack() printe("!ERROR: alife_clone_weapon | no server object passed!") return end local se_obj_new = alife():clone_weapon(se_obj, section or se_obj:section_name(), se_obj.position, se_obj.m_level_vertex_id, se_obj.m_game_vertex_id, parent_id or se_obj.parent_id, false) if (se_obj_new) then -- Clone server object data local m_data_se = alife_storage_manager.get_state().se_object if m_data_se[se_obj.id] then m_data_se[se_obj_new.id] = {} copy_table(m_data_se[se_obj_new.id], m_data_se[se_obj.id]) end -- Clone parts states if item_parts then item_parts.copy_parts_con(se_obj.id, se_obj_new.id) item_parts.clear_parts_con(se_obj.id) end -- Release the old weapon. alife_record(se_obj_new,true) alife_release(se_obj) -- Register the new weapon. alife():register(se_obj_new) end return se_obj_new end function alife_character_community(se_obj) if not (se_obj) then return end if IsStalker(nil, se_obj:clsid()) then return se_obj:community() end return "monster" end function alife_on_limit() return (_ALIFE_CNT > _ALIFE_WARNING) end function alife_record(se_obj,state) _ALIFE_CNT = _ALIFE_CNT + (state and 1 or -1) if _ALIFE_CACHE_RECORD and se_obj then local str = (state and "+" or "-") .. se_obj:section_name() .. " (" .. se_obj.id .. ")" table.insert(_ALIFE_CACHE, 1, str) end if alife_on_limit() then --callstack() printe("~WARNING alife ID storage is almost filled: %s", _ALIFE_CNT) end end function alife_first_update() _ALIFE_CNT = 0 --empty_table(_ALIFE_CACHE) local sim = alife() local se_obj for i=1,65534 do se_obj = sim:object(i) if se_obj then alife_record(nil,true) end end end function create_ammo(section, position, lvi, gvi, pid, num) local num_in_box = ini_sys:r_u32(section, "box_size") local sim = alife() local t = {} while num > num_in_box do t[#t+1] = sim:create_ammo(section, position, lvi, gvi, pid, num_in_box) alife_record(t[#t],true) num = num - num_in_box end local se_obj = sim:create_ammo(section, position, lvi, gvi, pid, num) alife_record(se_obj,true) table.insert(t, se_obj) return t end function SetSwitchDistance(dist) if (alife()) then local p = net_packet() p:w_begin(18) p:w_float(dist or 2.0) level.send(p,true,true) end end ------------------------------------------------------------------------------------------------------- -- GAME OR SERVER OBJECTS ------------------------------------------------------------------------------------------------------- is_squad_monster = { ["monster_predatory_day"] = true, ["monster_predatory_night"] = true, ["monster_vegetarian"] = true, ["monster_zombied_day"] = true, ["monster_zombied_night"] = true, ["monster_special"] = true, ["monster"] = true, ["zoo_monster"] = true } squad_community_by_behaviour = { ["stalker"] = "stalker", ["bandit"] = "bandit", ["csky"] = "csky", ["dolg"] = "dolg", ["freedom"] = "freedom", ["army"] = "army", ["ecolog"] = "ecolog", ["killer"] = "killer", ["zombied"] = "zombied", ["monolith"] = "monolith", ["greh"] = "greh", ["isg"] = "isg", ["renegade"] = "renegade", ["greh_npc"] = "greh_npc", ["army_npc"] = "army_npc", ["monster"] = "monster", ["monster_predatory_day"] = "monster", ["monster_predatory_night"] = "monster", ["monster_vegetarian"] = "monster", ["monster_zombied_day"] = "monster", ["monster_zombied_night"] = "monster", ["monster_special"] = "monster", ["zoo_monster"] = "monster" } function get_object_community(obj) if type(obj.id) == "function" then return character_community(obj) else return alife_character_community(obj) end end function get_object_by_id(id) if not (id and type(id) == "number") then callstack() printe("!ERROR get_object_by_id | id (%s) is invalid", id) return end if (id == AC_ID) then return db.actor end local obj = db.storage[id] and db.storage[id].object or level.object_by_id(id) if (not obj) then printe("!ERROR get_object_by_id | no game object recieved from id (%s)", id) return end return obj end function character_community(obj) if not (obj) then return end if IsStalker(obj) then return obj:character_community() end return "monster" end function get_actor_true_community() -- no "actor_" return gameplay_disguise.get_default_comm() end function set_actor_true_community(new_comm, now) -- no "actor_" local curr_comm = get_actor_true_community() -- Update disguise default faction gameplay_disguise.update_default(new_comm, now) if IsStoryMode() then -- Change LTTZ info portion if needed first dialogs_lostzone.update_lttz_faction_info(curr_comm, new_comm) -- Recycle DRX availability to the new faction xr_effects.drx_sl_cancel_questlines(nil,nil,{}) xr_effects.drx_sl_setup_questlines(nil,nil,{new_comm}) end end function get_object_squad(object,caller) -- Получить сквад обьекта!!!!! if not (object) then return end if (object.group_id ~= nil and object.group_id ~= 65535) then return alife_object(object.group_id) end local sim = alife() local se_obj = type(object.id) == "function" and sim:object(object:id()) return se_obj and se_obj.group_id ~= 65535 and sim:object(se_obj.group_id) or nil end function set_inactivate_input_time(delta) db.storage[AC_ID].disable_input_time = game.get_game_time() db.storage[AC_ID].disable_input_idle = delta level.disable_input() end function npc_in_actor_frustrum(npc) --' находится ли NPC во фруструме игрока local actor_dir = device().cam_dir --local actor_dir = db.actor:direction() local npc_dir = vec_sub(npc:position(),db.actor:position()) local yaw = yaw_degree3d(actor_dir, npc_dir) --printf("YAW %s", tostring(yaw)) return yaw < 35 end function change_team_squad_group(se_obj, team, squad, group) -- меняет team:squad:group обьекта. local cl_obj = db.storage[se_obj.id] and db.storage[se_obj.id].object if cl_obj ~= nil then cl_obj:change_team(team, squad, group) else se_obj.team = team se_obj.squad = squad se_obj.group = group end --printf("_G:TSG: [%s][%s][%s]", tostring(se_obj.team), tostring(se_obj.squad), tostring(se_obj.group)) end function get_speaker(safe, all) if all then for k,v in pairs(db.storage) do local npc = v.object if npc ~= nil then if npc:is_talking() and npc:id() ~= AC_ID then return npc end end end else local npc_id = GetEvent("used_npc_id") local npc = npc_id and (db.storage[npc_id] and db.storage[npc_id].object or level.object_by_id(npc_id)) if safe then if npc and npc:is_talking() and npc:id() ~= AC_ID then return npc end else return npc end end return nil end function distance_between(obj1, obj2) return obj1:position():distance_to(obj2:position()) end function distance_between_safe(obj1, obj2) if (obj1 == nil or obj2 == nil) then return 100000 end return obj1:position():distance_to(obj2:position()) end ------------------------------------------------------------------------------------------------------- -- GLOBAL ALIFE INFO ------------------------------------------------------------------------------------------------------- function has_alife_info(info_id) return alife():has_info(0, info_id) end function give_info(info) db.actor:give_info_portion(info) --printf("DEBUG: GIVE INFO %s",info) --if (xrs_debug_tools and xrs_debug_tools.actor_info) then -- xrs_debug_tools.actor_info[info] = true --end end function disable_info(info) if has_alife_info(info) then --printf("DEBUG: DISABLE INFO %s",info) --printf("*INFO*: disabled npc='single_player' id='%s'", info) db.actor:disable_info_portion(info) --if (xrs_debug_tools and xrs_debug_tools.actor_info) then -- xrs_debug_tools.actor_info[info] = nil --end end end ------------------------------------------------------------------------------------------------------- -- OBJECT DATA STORAGE ------------------------------------------------------------------------------------------------------- function pstor_is_registered_type(tv) if tv ~= "boolean" and tv ~= "string" and tv ~= "number" then return false end return true end function save_var(obj, varname, val) if (obj == nil) then callstack() end local id = obj:id() if not (db.storage[id]) then printe("!save_var: Warning: no db.storage for %s, db.storage is for online objects only",id) return end if not (db.storage[id].pstor) then db.storage[id].pstor = {} end if not (USE_MARSHAL) then local tv = type(val) if val ~= nil and not pstor_is_registered_type(tv) then printe("!save_var: not registered type '%s' encountered [name=%s varname=%s ]", tv,obj:name(),varname) return end end if (type(val) == "userdata") then printe("!WARNING: save_var ONLY USE LUA DATA TYPES!!! %s",varname) else --printf("* save_var | id: %s - name: %s, value: %s", id, varname, val) db.storage[id].pstor[varname] = val end end function load_var(obj, varname, defval) if (obj == nil) then callstack() printf("load_var failed for %s", varname) return defval end local id = obj:id() if not (db.storage[id]) then printe("!load_var: Warning: no db.storage for %s, db.storage is for online objects only", id) return defval end --printf("* load_var | id: %s - name: %s", id, varname) return db.storage[id].pstor and db.storage[id].pstor[varname] or defval end function save_ctime(obj, varname, val) if (obj == nil) then callstack() end local id = obj:id() if not (db.storage[id]) then printe("!save_ctime: Warning: no db.storage for %s, db.storage is for online objects only",id) return end if not (db.storage[id].pstor_ctime) then db.storage[id].pstor_ctime = {} end db.storage[id].pstor_ctime[varname] = val --printf("* se_save_var | id: %s - name: %s", id, varname) end function load_ctime(obj, varname) if (obj == nil) then callstack() end local id = obj:id() if not (db.storage[id]) then printe("!load_var: Warning: no db.storage for %s, db.storage is for online objects only",id) return end --printf("* load_ctime | id: %s - name: %s", id, varname) return db.storage[id].pstor_ctime and db.storage[id].pstor_ctime[varname] or nil end function se_save_var(id, name, varname, val) local m_data = alife_storage_manager.get_state() if not (m_data.se_object[id]) then if not (val) then return -- already nil end local se_obj = alife_object(id) if not (se_obj) then return end m_data.se_object[id] = {} m_data.se_object[id].name = se_obj:name() end m_data.se_object[id][varname] = val --printf("* se_save_var | id: %s - name: %s", id, varname) end function se_load_var(id, name, varname) local m_data = alife_storage_manager.get_state() if (m_data.se_object[id]) then --printf("* se_load_var | id: %s - name: %s", id, varname) return m_data.se_object[id][varname] end end ------------------------------------------------------------------------------------------------------- -- STORY ID HANDLERS ------------------------------------------------------------------------------------------------------- function get_story_se_object(story_id) local obj_id = story_objects.object_id_by_story_id[story_id] return obj_id and alife_object(obj_id) end function get_story_se_item(story_id) return story_id and story_objects.get_story_se_item(story_id) end function get_story_object(story_id) local obj_id = story_objects.object_id_by_story_id[story_id] return obj_id and level.object_by_id(obj_id) end function get_object_story_id(obj_id) return obj_id and story_objects.story_id_by_object_id[obj_id] end function get_story_object_id(story_id) return story_id and story_objects.object_id_by_story_id[story_id] end function get_story_squad(story_id) return get_story_se_object(story_id) end function unregister_story_object_by_id(obj_id) -- remove story_objects.unregister(obj_id) end function level_object_by_sid( story_id ) -- get game object by story id local sim = alife() if sim then local se_obj = sim:story_object( story_id ) if se_obj then return level.object_by_id( se_obj.id ) end end return nil end function id_by_sid( story_id ) -- get object ID by story id local sim = alife() if sim then local se_obj = sim:story_object( story_id ) if se_obj then return se_obj.id end end return nil end ------------------------------------------------------------------------------------------------------- -- EVENT STORAGE ------------------------------------------------------------------------------------------------------- -- global temp table to store and get values in global scope. Handy for scripts to pick up values without the need for callbacks or accessing other scripts _EVENT = {} function GetEvent(k, v) --printf("GetEvent | [%s] [%s] | caller: %s", k, v, callstack(true,true)) if v then return _EVENT[k] and _EVENT[k][v] end return _EVENT[k] end function SetEvent(k, v1, v2) --printf("SetEvent | [%s] [%s] [%s] | caller: %s", k, v1, v2, callstack(true,true)) if (not k) then printf("!ERROR SetEvent | no key is passed") return end if (v2 ~= nil) then if (type(_EVENT[k]) ~= "table") then if _EVENT[k] then printf("~WARNING SetEvent | key: %s has value (%s) that got overwritten with a new table", k, _EVENT[k]) end _EVENT[k] = {} end if (type(v1) ~= "string") then printf("!ERROR SetEvent | invalid arguments | k: %s - v: %s", k, v1) return end _EVENT[k][v1] = v2 else _EVENT[k] = v1 end end ------------------------------------------------------------------------------------------------------- -- MODE TESTING ------------------------------------------------------------------------------------------------------- function IsAzazelMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_azazel_mode",1) == true or alife_storage_manager.get_state().enable_azazel_mode == true end function IsHardcoreMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_hardcore_mode",1) == true or (alife_storage_manager.get_state().ironman and alife_storage_manager.get_state().ironman.uuid ~= nil) end function IsStoryMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_story_mode",1) == true or not has_alife_info("story_mode_disabled") end function IsSurvivalMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_survival_mode",1) == true or alife_storage_manager.get_state().enable_survival_mode == true end function IsAgonyMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_conditions_mode",1) == true or alife_storage_manager.get_state().enable_conditions_mode == true end function IsTimerMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_timer_mode",1) == true or type(alife_storage_manager.get_state().enable_timer_mode) == "number" end function IsCampfireMode() return axr_main.config and axr_main.config:r_value("character_creation","new_game_campfire_mode",1) == true or alife_storage_manager.get_state().enable_campfire_mode == true end function IsWarfare() return axr_main.config and axr_main.config:r_value("character_creation","new_game_warfare",1) == true or alife_storage_manager.get_state().enable_warfare_mode == true end function IsTestMode() return (level.name() == "fake_start") end local story_comm = { ["csky"] = true, ["dolg"] = true, ["ecolog"] = true, ["freedom"] = true, ["killer"] = true, ["stalker"] = true, } function IsStoryPlayer() local comm = gameplay_disguise.get_default_comm() return story_comm[comm] end ------------------------------------------------------------------------------------------------------- -- CLASS TESTING ------------------------------------------------------------------------------------------------------- local monster_classes local weapon_classes local artefact_classes local anomaly_classes function IsStalker(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.script_stalker or c == clsid.script_actor or c == clsid.stalker or c == clsid.actor) or false end function IsMonster(o,c) if not (c) then c = o and o:clsid() end if not (monster_classes) then monster_classes = { [clsid.bloodsucker] = true, [clsid.bloodsucker_s] = true, [clsid.boar] = true, [clsid.boar_s] = true, [clsid.dog_red] = true, [clsid.dog_s] = true, [clsid.flesh] = true, [clsid.flesh_s] = true, [clsid.dog_black] = true, [clsid.pseudodog_s] = true, [clsid.burer] = true, [clsid.burer_s] = true, [clsid.cat] = true, [clsid.cat_s] = true, [clsid.rat] = true, [clsid.rat_s] = true, [clsid.chimera] = true, [clsid.chimera_s] = true, [clsid.controller] = true, [clsid.controller_s] = true, [clsid.fracture] = true, [clsid.fracture_s] = true, [clsid.poltergeist] = true, [clsid.poltergeist_s] = true, [clsid.pseudo_gigant] = true, [clsid.gigant_s] = true, [clsid.zombie] = true, [clsid.zombie_s] = true, [clsid.snork] = true, [clsid.snork_s] = true, [clsid.tushkano] = true, [clsid.tushkano_s] = true, [clsid.psy_dog_s] = true, [clsid.psy_dog_phantom_s] = true } end return c and monster_classes[c] or false end function IsAnomaly(o,c) if not (c) then c = o and o:clsid() end if not (anomaly_classes) then anomaly_classes = { [clsid.zone] = true, [clsid.ameba_zone] = true, [clsid.zone_ameba_s] = true, [clsid.zone_acid_fog] = true, [clsid.zone_bfuzz] = true, [clsid.zone_campfire] = true, [clsid.zone_dead] = true, [clsid.zone_galantine] = true, [clsid.zone_mincer] = true, [clsid.zone_mosquito_bald] = true, [clsid.zone_radioactive] = true, [clsid.zone_rusty_hair] = true, [clsid.zone_bfuzz] = true, [clsid.zone_bfuzz_s] = true, [clsid.zone_mosquito_bald] = true, [clsid.zone_mbald_s] = true, [clsid.zone_galantine] = true, [clsid.zone_galant_s] = true, [clsid.zone_mincer] = true, [clsid.zone_mincer_s] = true, [clsid.zone_radioactive] = true, [clsid.zone_radio_s] = true, [clsid.torrid_zone] = true, [clsid.zone_torrid_s] = true, [clsid.nogravity_zone] = true, [clsid.zone_nograv_s] = true, } end return c and anomaly_classes[c] or false end function IsTrader(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.script_trader or c == clsid.trader) or false end function IsCar(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.car or c == clsid.car_s) or false end function IsHelicopter(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.helicopter or c == clsid.script_heli) or false end function IsInvbox(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.inventory_box_s or c == clsid.inventory_box) end function isLc(obj) return (obj:clsid() == clsid.level_changer or obj:clsid() == clsid.level_changer_s) end function IsWounded(o) if not ((o:clsid() == clsid.stalker or o:clsid() == clsid.script_stalker) and o:alive()) then return false end if (o:critically_wounded() or o:in_smart_cover()) then return false end if o:best_enemy() and load_var(o, "wounded_fight") == "true" then return false end local state = tostring(load_var(o, "wounded_state")) if (state == "nil") then return false end return true end -- Items function IsOutfit(o,c) if not c then c = o and o:clsid() end return c and (c == clsid.equ_stalker_s or c == clsid.equ_stalker) end function IsHeadgear(o,c) if not c then c = o and o:clsid() end return c and (c == clsid.equ_helmet_s or c == clsid.helmet) end function IsExplosive(o,c) if not c then c = o and o:clsid() end return c and (c == clsid.obj_explosive_s or c == clsid.obj_explosive) end function IsPistol(o,c) if not (c) then c = o and o:clsid() end local pistol = { [clsid.wpn_pm_s] = true, [clsid.wpn_walther_s] = true, [clsid.wpn_usp45_s] = true, [clsid.wpn_hpsa_s] = true, [clsid.wpn_lr300_s] = true, [clsid.wpn_pm] = true, [clsid.wpn_walther] = true, [clsid.wpn_usp45] = true, [clsid.wpn_hpsa] = true, [clsid.wpn_lr300] = true } return c and pistol[c] or false end function IsMelee(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.wpn_knife or c == clsid.wpn_knife_s) or false end function IsSniper(o,c) if not (c) then c = o and o:clsid() end local sniper = { [clsid.wpn_svu_s] = true, [clsid.wpn_svd_s] = true, [clsid.wpn_vintorez_s] = true, [clsid.wpn_svu] = true, [clsid.wpn_svd] = true, [clsid.wpn_vintorez] = true } return c and sniper[c] or false end function IsLauncher(o,c) if not (c) then c = o and o:clsid() end local launcher = { [clsid.wpn_rg6_s] = true, [clsid.wpn_rpg7_s] = true, [clsid.wpn_rg6] = true, [clsid.wpn_rpg7] = true } return c and launcher[c] or false end function IsShotgun(o,c) if not (c) then c = o and o:clsid() end local shotgun = { [clsid.wpn_bm16_s] = true, [clsid.wpn_shotgun_s] = true, [clsid.wpn_auto_shotgun_s] = true, [clsid.wpn_bm16] = true, [clsid.wpn_shotgun] = true } return c and shotgun[c] or false end function IsRifle(o,c) if not (c) then c = o and o:clsid() end local rifle = { [clsid.wpn_ak74_s] = true, [clsid.wpn_groza_s] = true, [clsid.wpn_val_s] = true, [clsid.wpn_ak74] = true, [clsid.wpn_groza] = true, [clsid.wpn_val] = true } return c and rifle[c] or false end function IsWeapon(o,c) if not (c) then c = o and o:clsid() end if not (weapon_classes) then weapon_classes = { [clsid.wpn_vintorez_s] = true, [clsid.wpn_ak74_s] = true, [clsid.wpn_lr300_s] = true, [clsid.wpn_hpsa_s] = true, [clsid.wpn_pm_s] = true, [clsid.wpn_shotgun_s] = true, [clsid.wpn_auto_shotgun_s] = true, [clsid.wpn_bm16_s] = true, [clsid.wpn_svd_s] = true, [clsid.wpn_svu_s] = true, [clsid.wpn_rg6_s] = true, [clsid.wpn_rpg7_s] = true, [clsid.wpn_val_s] = true, [clsid.wpn_walther_s] = true, [clsid.wpn_usp45_s] = true, [clsid.wpn_groza_s] = true, [clsid.wpn_knife_s] = true, [clsid.wpn_vintorez] = true, [clsid.wpn_ak74] = true, [clsid.wpn_lr300] = true, [clsid.wpn_hpsa] = true, [clsid.wpn_pm] = true, [clsid.wpn_shotgun] = true, [clsid.wpn_bm16] = true, [clsid.wpn_svd] = true, [clsid.wpn_svu] = true, [clsid.wpn_rg6] = true, [clsid.wpn_rpg7] = true, [clsid.wpn_val] = true, [clsid.wpn_walther] = true, [clsid.wpn_usp45] = true, [clsid.wpn_groza] = true, [clsid.wpn_knife] = true } end return c and weapon_classes[c] or false end function IsAmmo(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.wpn_ammo or c == clsid.wpn_ammo_s) end function IsGrenade(o,c) if not (c) then c = o and o:clsid() end if not (grenade_classes) then grenade_classes = { [clsid.wpn_grenade_f1_s] = true, [clsid.wpn_grenade_rgd5_s] = true, [clsid.wpn_grenade_launcher_s] = true, [clsid.wpn_grenade_fake] = true, [clsid.wpn_grenade_f1] = true, [clsid.wpn_grenade_launcher] = true, [clsid.wpn_grenade_rgd5] = true, [clsid.wpn_grenade_rpg7] = true } end return c and grenade_classes[c] or false end function IsBolt(o,c) if not (c) then c = o and o:clsid() end return c and (c == clsid.obj_bolt) end function IsArtefact(o,c) if not (c) then c = o and o:clsid() end if not (artefact_classes) then artefact_classes = { [clsid.art_bast_artefact] = true, [clsid.art_black_drops] = true, [clsid.art_dummy] = true, [clsid.art_electric_ball] = true, [clsid.art_faded_ball] = true, [clsid.art_galantine] = true, [clsid.art_gravi] = true, [clsid.art_gravi_black] = true, [clsid.art_mercury_ball] = true, [clsid.art_needles] = true, [clsid.art_rusty_hair] = true, [clsid.art_thorn] = true, [clsid.art_zuda] = true, [clsid.artefact] = true, [clsid.artefact_s] = true } end return c and artefact_classes[c] or false end ------------------------------------------------------------------------------------------- -- ITEMS LOOKUP TABLE ------------------------------------------------------------------------------------------- -- parsed list of item sections and values grouped in types, easy to deal with instead of reading system_ini _ITM = {} function IsItem(typ, sec, obj) sec = obj and obj:section() or sec return sec and _ITM[typ] and _ITM[typ][sec] or nil end function GetItemList(typ) return _ITM[typ] or {} end function Parse_ITM() local function compare_ITM_value( param, value ) if (param == nil) then return false end if (value == nil) then return true end return param == value end local function Parse_ITM_value(section, typ, condition, value ) if (not _ITM[typ]) then _ITM[typ] = {} end if condition == true then _ITM[typ][section] = true elseif type(condition) == "table" then if (condition[2] == 0) and compare_ITM_value( ini_sys:r_string_ex(section,condition[1]), condition[3]) then _ITM[typ][section] = true elseif (condition[2] == 1) and ini_sys:r_bool_ex(section,condition[1]) then _ITM[typ][section] = true elseif (condition[2] == 2) and compare_ITM_value( ini_sys:r_float_ex(section,condition[1]), condition[3]) then _ITM[typ][section] = true elseif (condition[2] == 5) and compare_ITM_value( ini_sys:r_s32(section,condition[1]), condition[3]) then _ITM[typ][section] = true end end if _ITM[typ][section] and value and type(value) == "table" then if (value[2] == 0) then _ITM[typ][section] = ini_sys:r_string_ex(section,value[1]) or value[3] elseif (value[2] == 1) then _ITM[typ][section] = ini_sys:r_bool_ex(section,value[1]) or value[3] elseif (value[2] == 2) then _ITM[typ][section] = ini_sys:r_float_ex(section,value[1]) or value[3] elseif (value[2] == 3) then _ITM[typ][section] = parse_list(ini_sys,section,value[1],value[3]) elseif (value[2] == 4) then _ITM[typ][section] = (string_find(section,"1") and 1) or (string_find(section,"2") and 2) or 3 elseif (value[2] == 5) then _ITM[typ][section] = ini_sys:r_s32(section,value[1]) or value[3] end end end ini_sys:section_for_each(function(section) if ini_sys:r_float_ex(section,"inv_grid_x") then local cls = ini_sys:r_string_ex(section,"class") or "unknown" local kind = ini_sys:r_string_ex(section,"kind") or "unknown" local bind = ini_sys:r_string_ex(section,"script_binding") Parse_ITM_value( section , "anim" , {"anim_item",1} ) Parse_ITM_value( section , "release" , {"release_on_first_update",1}) Parse_ITM_value( section , "fake_ammo" , {"fake_ammo",1} ) Parse_ITM_value( section , "grenade_ammo", {"grenade_ammo",1} ) Parse_ITM_value( section , "ammo" , (cls == "AMMO") , {"box_size",5}) Parse_ITM_value( section , "ammo" , (cls == "AMMO_S") , {"box_size",5}) Parse_ITM_value( section , "sil" , (cls == "WP_SILEN") ) Parse_ITM_value( section , "scope" , {"repair_type",0,"scope"} ) Parse_ITM_value( section , "scope" , (cls == "WP_SCOPE") ) Parse_ITM_value( section , "gl" , (cls == "WP_GLAUN") ) Parse_ITM_value( section , "helmet" , (cls == "E_HLMET") ) Parse_ITM_value( section , "outfit" , (cls == "E_STLK") or (cls == "EQU_STLK")) Parse_ITM_value( section , "backpack" , (cls == "EQ_BAKPK") ) Parse_ITM_value( section , "artefact" , (cls == "ARTEFACT") or (cls == "SCRPTART") , {"af_rank",2}) Parse_ITM_value( section , "device" , (bind == "item_device.bind") ) Parse_ITM_value( section , "craft" , {"craft_tool",1} ) Parse_ITM_value( section , "repair" , {"repair_tool",1} , {"repair_only",3,true}) Parse_ITM_value( section , "workshop" , {"workshop_tool",1} , {"workshop_support",3,true}) Parse_ITM_value( section , "disassemble" , {"disassemble_tool",1} , {"degradation_factor",2,0.02}) Parse_ITM_value( section , "cook" , {"cooking_tool",1} ) Parse_ITM_value( section , "map" , {"use_map",0} ) Parse_ITM_value( section , "money" , {"money_amount",0} , {"money_amount",3}) Parse_ITM_value( section , "recipe" , {"recipe",1} ) Parse_ITM_value( section , "letter" , {"letter",1} ) Parse_ITM_value( section , "meal" , {"meal",2} , {"meal",2}) Parse_ITM_value( section , "eatable" , (kind == "i_mutant_cooked") ) Parse_ITM_value( section , "eatable" , (kind == "i_food") ) Parse_ITM_value( section , "eatable" , (kind == "i_drink") ) Parse_ITM_value( section , "tool" , (kind == "i_tool") ) Parse_ITM_value( section , "tool" , (kind == "i_kit") ) Parse_ITM_value( section , "part" , (kind == "i_part") ) Parse_ITM_value( section , "upgrade" , (kind == "i_upgrade") , {"",4}) Parse_ITM_value( section , "quest" , {"quest_item",1} ) Parse_ITM_value( section , "quest" , (kind == "i_quest") ) -- Multi-use items if (cls == "II_FOOD") then Parse_ITM_value( section , "consumable" , true ) local max_uses = ini_sys:r_float_ex(section,"max_uses") local condition = ini_sys:r_bool_ex(section,"use_condition") if condition and max_uses and (max_uses > 1) then Parse_ITM_value( section , "multiuse" , true , {"max_uses",2} ) if (not ini_sys:r_bool_ex(section,"remove_after_use")) then Parse_ITM_value( section , "multiuse_r" , true , {"max_uses",2} ) end end end -- Cache weapons with fake ammo local ammo = ini_sys:r_string_ex(section,"ammo_class") if ammo then local ammo_list = parse_list(ini_sys,section,"ammo_class") Parse_ITM_value( section , "fake_ammo_wpn" , IsItem("fake_ammo", ammo_list[1]) ) end end end) --[[ for typ,v in pairs(_ITM) do for sec,val in pairs(v) do printf("$_ITM[%s][%s] = %s", typ, sec, val) end end --]] end Parse_ITM() function add_console_command(name, f) cmd = debug_cmd_list.command_get_list() if cmd[name] then printf('a command named "%s" already exists', name) return false else cmd[name] = f return true end end local __g_last_level local __g_last_lvid function get_player_level_id() if IsTestMode() or level.name() == "fake_start" then return end if level.name() == __g_last_level then return __g_last_lvid end __g_last_level = level.name() __g_last_lvid = game_graph():vertex(alife():actor().m_game_vertex_id):level_id() end -- if return game_object then it ignores engine. If return nil, then engine tries to find item to kill -- called from engine! function update_best_weapon(npc,cur_wpn) --[[ local st = db.storage[npc:id()] if not (st) then return cur_wpn end -- we want to choose new best_weapon every n seconds unless we don't have one local tg = time_global() if (cur_wpn and st._choose_bw_timer and tg < st._choose_bw_timer) then return cur_wpn end st._choose_bw_timer = tg+10000 -- weapon priority local prior = {} local be = npc:best_enemy() if (be) then if (IsMonster(nil,be:clsid())) then if (be:position():distance_to_sqr(npc:position()) > 2500) then prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsLauncher else prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsLauncher end elseif (be:position():distance_to_sqr(npc:position()) > 1000) then prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsLauncher elseif (be:position():distance_to_sqr(npc:position()) > 2500) then prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsLauncher else prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsLauncher end elseif (axr_npc_vs_heli.is_under_npc_vs_heli(npc)) then prior[#prior+1] = _G.IsLauncher prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsShotgun else prior[#prior+1] = _G.IsRifle prior[#prior+1] = _G.IsSniper prior[#prior+1] = _G.IsPistol prior[#prior+1] = _G.IsShotgun prior[#prior+1] = _G.IsLauncher end local best_weapons = {} local function itr(npc,wpn) local cls = wpn:clsid() if (IsWeapon(nil,cls) ~= true) then return false end --if (wpn:get_ammo_in_magazine() > 0) then for i=1,#prior do if (prior[i](nil,cls)) then best_weapons[wpn] = ini_sys:r_float_ex(wpn:section(),"cost") break end end --end return false end npc:iterate_inventory(itr,npc) for wpn,cost in spairs(best_weapons,function(t,a,b) return t[a] > t[b] end) do DEBUG_NPC = level.get_target_obj and level.get_target_obj() if (DEBUG_NPC) then actor_menu.set_msg(1, strformat("%s bw=%s",DEBUG_NPC:name(),wpn and wpn:name()), 2) end return wpn end --]] local flags = {gun_id = nil} SendScriptCallback("npc_on_choose_weapon", npc, cur_wpn, flags) if flags.gun_id and tonumber(flags.gun_id) then local se_gun = alife_object(tonumber(flags.gun_id)) if se_gun and IsWeapon(nil, se_gun:clsid()) and se_gun.parent_id == npc:id() then local obj = level.object_by_id(tonumber(flags.gun_id)) if obj then return obj else printf('%s online object is not available, %s will choose gun from engine', se_gun:name(), npc:name()) return end else printf('%s does not point to a valid gun in npc inventory, %s will choose gun from engine', flags.gun_id, npc:name()) return end end return end