diff --git a/gamedata/scripts/_g_patches.script b/gamedata/scripts/_g_patches.script index a2a9730c4..a66f2a6a1 100644 --- a/gamedata/scripts/_g_patches.script +++ b/gamedata/scripts/_g_patches.script @@ -666,3 +666,28 @@ _G.IsLauncher = function(o, c) } return c and launcher[c] or false end + +-- alife():max_id replacement +_G._ALIFE_WARNING = alife():max_id() - 1500 + +_G.alife_object = function(id) + if (id == nil or id >= alife():max_id()) then + callstack() + printe("!ALIFE OBJECT ID IS %s!",id) + return + end + return alife():object(id) +end + +_G.alife_first_update = function() + _G._ALIFE_CNT = 0 + --empty_table(_ALIFE_CACHE) + + local sim = alife() + for i = 1, sim:max_id() - 1 do + local se_obj = sim:object(i) + if se_obj then + alife_record(nil,true) + end + end +end \ No newline at end of file diff --git a/gamedata/scripts/actor_status.script b/gamedata/scripts/actor_status.script new file mode 100644 index 000000000..2c3608c76 --- /dev/null +++ b/gamedata/scripts/actor_status.script @@ -0,0 +1,469 @@ + +--[[ + + Tronex + 2020/4/9 + + Actor status / info - Handly for checking actor related info like current safe cover by other scripts + Custom HUD indicators + +--]] + +-- Update rate +local tg_update = 0 +local tg_update_step = 1000 --[ms] + +---------------------------------- +-- Current Boosters and states +---------------------------------- +local active_boosters = {} +local boost_name = {} +function prepare_boosters_effect() + boost_name = invert_table(BoosterID) +end +function scan_boosters_effect() + for name,t in pairs(active_boosters) do + t.value = nil + t.period = nil + end + db.actor:cast_Actor():conditions():BoosterForEach( scan_current_booster_effect ) +end +function scan_current_booster_effect(typ, period, value) + active_boosters[ boost_name[typ] ] = { value=value , period=period } +end + +function get_boost(name, time_only) + if time_only then + return active_boosters[name] and active_boosters[name].period + end + return active_boosters[name] and active_boosters[name].value +end + +function get_satienty(visual) + local conditions = db.actor:cast_Actor():conditions() + local satienty = conditions:GetSatiety() + if (not visual) then + return satienty + end + + local satiety_critical = conditions:SatietyCritical() + local satiety_koef = (satienty - satiety_critical) / (satienty >= satiety_critical and (1 - satiety_critical) or satiety_critical) + if (satiety_koef > 0.5) then + return 0 + else + if (satiety_koef > 0.0) then + return 1 + elseif (satiety_koef > -0.5) then + return 2 + else + return 4 + end + end +end + +function get_radiation(visual) + local radiation = db.actor.radiation + if (not visual) then + return radiation + end + + -- for indicator + if radiation and radiation > 0 then + return math.ceil(radiation * 4) + end + return 0 +end + +function get_overweight(visual) + local actor = db.actor + local tot_weight = actor:get_total_weight() + local max_weight = actor:get_actor_max_walk_weight() + + local outfit = actor:item_in_slot(7) + local backpack = actor:item_in_slot(13) + max_weight = max_weight + (outfit and outfit:get_additional_max_weight() or 0) + max_weight = max_weight + (backpack and backpack:get_additional_max_weight() or 0) + actor:iterate_belt( function(owner, obj) + local c_arty = obj:cast_Artefact() + max_weight = max_weight + (c_arty and c_arty:AdditionalInventoryWeight() or 0) + end) + --max_weight = max_weight + actor:cast_Actor():conditions().eBoostMaxWeight + + if (not visual) then + return (tot_weight > max_weight), tot_weight, max_weight + end + + -- for indicator + if (tot_weight > max_weight) then + return 4 + elseif (tot_weight + 10 > max_weight) then + return 2 + end + return 0 +end + + +---------------------------------- +-- Current safe cover +---------------------------------- +local safe_covers = {} +local near_cover_dist +local last_cover, last_cover_2, near_cover +local last_cover_o, near_cover_o + +function prepare_safe_zone() + local sur_ini = ini_file("misc\\surge_manager.ltx") + local safe_covers_list = utils_data.collect_section(sur_ini,"list",true) + + -- Only collect safe covers in the same level + local sim = alife() + local is_on_the_actor_level = simulation_objects.is_on_the_actor_level + for i=1, sim:max_id() - 1 do + local se_obj = sim:object(i) + if se_obj and safe_covers_list[se_obj:name()] and is_on_the_actor_level(se_obj) then + safe_covers[se_obj:name()] = se_obj.id + end + end +end +function scan_safe_zone() + --printf("Safe cover: %s", GetEvent("current_safe_cover")) + if GetEvent("underground") then + return true, false, 0 + + elseif last_cover and db.actor_inside_zones[last_cover] then + return db.actor_inside_zones[last_cover]:id(), false, 1 + + elseif last_cover_2 and db.zone_by_name[last_cover_2] and db.zone_by_name[last_cover_2]:inside(db.actor:position()) then + return safe_covers[last_cover_2], false, 2 + + elseif (size_table(db.actor_inside_zones) > 1) then + for name, obj in pairs(db.actor_inside_zones) do + last_cover = name + return obj:id(), false, 3 + end + + else + + local pos = db.actor:position() + last_cover = nil + last_cover_2 = nil + near_cover = nil + near_cover_dist = nil + + for name,id in pairs(safe_covers) do + local zone = db.zone_by_name[name] + if (zone) then + local dist = zone:position():distance_to_sqr(pos) + if (near_cover_dist == nil or dist < near_cover_dist) then + near_cover = name + near_cover_dist = dist + end + end + end + + if near_cover then + local zone = db.zone_by_name[near_cover] + if (zone) and zone:inside(pos) then + last_cover_2 = near_cover + return zone:id(), false, 4 + end + end + + -- Tents are considered as save covers + if item_tent and item_tent.get_nearby_tent(1.5) then + return true, near_cover and safe_covers[near_cover] or false, 5 + end + + return false, near_cover and safe_covers[near_cover] or false, 6 + end +end +function scan_safe_zone_old() + --printf("Safe cover: %s", GetEvent("current_safe_cover")) + if GetEvent("underground") then + return true, false, 0 + + elseif last_cover_o and db.zone_by_name[last_cover_o] and db.zone_by_name[last_cover_o]:inside(db.actor:position()) then + return safe_covers[last_cover_o], false, 2 + + else + + local pos = db.actor:position() + last_cover_o = nil + near_cover_o = nil + near_cover_dist = nil + + for name,id in pairs(safe_covers) do + local zone = db.zone_by_name[name] + if (zone) then + local dist = zone:position():distance_to_sqr(pos) + if (near_cover_dist == nil or dist < near_cover_dist) then + near_cover_o = name + near_cover_dist = dist + end + end + end + + if near_cover_o then + local zone = db.zone_by_name[near_cover_o] + if (zone) and zone:inside(pos) then + last_cover_o = near_cover_o + return zone:id(), false, 4 + end + end + + -- Tents are considered as save covers + if item_tent and item_tent.get_nearby_tent(1.5) then + return true, near_cover_o and safe_covers[near_cover_o] or false, 5 + end + + return false, near_cover_o and safe_covers[near_cover_o] or false, 6 + end +end + +---------------------------------- +-- HUD (Indicators) +---------------------------------- + +HUD = nil +indicators = {} + +function prepare_indictors_list() +-- functor must return number [1-4] if it's a state (to indicate the color), boosters will always use one color + + if is_not_empty(indicators) then return end + indicators["Radiation"] = { index= 1 ,typ= "state" ,functor= {"actor_status","get_radiation",true} ,icon= "ui_inGame2_booster_rad" ,background= "ui_inGame2_indicator_slot" ,anim_icon= true ,anim_bk= false } + indicators["Hunger"] = { index= 2 ,typ= "state" ,functor= {"actor_status","get_satienty",true} ,icon= "ui_inGame2_indicator_hunger" ,background= "ui_inGame2_indicator_slot" ,anim_icon= false ,anim_bk= false } + indicators["Overweight"] = { index= 5 ,typ= "state" ,functor= {"actor_status","get_overweight",true} ,icon= "ui_inGame2_indicator_overweight" ,background= "ui_inGame2_indicator_slot" ,anim_icon= false ,anim_bk= false } + + indicators["HpRestore"] = { index= 6 ,typ= "booster" ,functor= {"actor_status","get_boost","HpRestore",true} ,icon= "ui_inGame2_booster_health" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["BleedingRestore"] = { index= 7 ,typ= "booster" ,functor= {"actor_status","get_boost","BleedingRestore",true} ,icon= "ui_inGame2_booster_bleed" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["RadiationRestore"] = { index= 8 ,typ= "booster" ,functor= {"actor_status","get_boost","RadiationRestore",true} ,icon= "ui_inGame2_booster_rad" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["MaxWeight"] = { index= 9 ,typ= "booster" ,functor= {"actor_status","get_boost","MaxWeight",true} ,icon= "ui_inGame2_booster_carry_weight" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["PowerRestore"] = { index= 10 ,typ= "booster" ,functor= {"actor_status","get_boost","PowerRestore",true} ,icon= "ui_inGame2_booster_power" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["RadiationProtection"] = { index= 11 ,typ= "booster" ,functor= {"actor_status","get_boost","RadiationProtection",true} ,icon= "ui_inGame2_booster_ext_rad" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["TelepaticProtection"] = { index= 12 ,typ= "booster" ,functor= {"actor_status","get_boost","TelepaticProtection",true} ,icon= "ui_inGame2_booster_psi" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } + indicators["ChemicalBurnProtection"] = { index= 13 ,typ= "booster" ,functor= {"actor_status","get_boost","ChemicalBurnProtection",true} ,icon= "ui_inGame2_booster_chem" ,background= "ui_inGame2_booster_slot" ,anim_icon= false ,anim_bk= false ,anim_period= 5 } +end + +function add_indicator(k,v) + prepare_indictors_list() + indicators[k] = v +end + +function func_index(t,a,b) + return (t[a].index) < (t[b].index) +end + +------- +function activate_hud() + RegisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy) + RegisterScriptCallback("on_console_execute",on_console_execute) + RegisterScriptCallback("GUI_on_show",update_hud) + RegisterScriptCallback("GUI_on_hide",update_hud) + + if HUD == nil then + HUD = UIIndicators() + get_hud():AddDialogToRender(HUD) + end + HUD:Update(true) +end + +function deactivate_hud() + if HUD ~= nil then + get_hud():RemoveDialogToRender(HUD) + HUD = nil + end + + UnregisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy) + UnregisterScriptCallback("on_console_execute",on_console_execute) + UnregisterScriptCallback("GUI_on_show",update_hud) + UnregisterScriptCallback("GUI_on_hide",update_hud) +end + +function update_hud() + if HUD ~= nil then + HUD:Update(true) + end +end + +function actor_on_net_destroy() + if HUD ~= nil then + get_hud():RemoveDialogToRender(HUD) + HUD = nil + end +end + +function on_console_execute(name) + if name == "hud_draw" and HUD then + HUD:Update(true) + end +end + +------- +class "UIIndicators" (CUIScriptWnd) + +function UIIndicators:__init() super() + + self.mirrored = false + self.slot = {} + self.clr_list = { + [0] = GetARGB(255,255,255,255), -- white + [1] = GetARGB(255,200,200,200), -- grey + [2] = GetARGB(255,255,255,50), -- yellow + [3] = GetARGB(255,255,125,50), -- orange + [4] = GetARGB(255,255,50,50), -- red + } + + self.ratio = utils_xml.screen_ratio() + self._tmr = time_global() + self.index = 0 + self.W = 40 + self.offset = 10 + + prepare_indictors_list() + + self:InitControls() +end + +function UIIndicators:__finalize() +end + +function UIIndicators:InitControls() + local xml = utils_xml.get_hud_xml() + + self.dialog = xml:InitStatic("indicators", self) + --utils_xml.correct_ratio(self.dialog) + self.dialog:Show(false) + + local t_size = size_table(indicators) + for i=1,t_size do + local x = (i-1)*(self.W + self.offset) + if self.mirrored then + x = (1-i)*(self.W + self.offset) + end + + self.slot[i] = {} + self.slot[i].back_s = xml:InitStatic("indicators:static", self.dialog) + self.slot[i].back_f = xml:InitStatic("indicators:flashing", self.dialog) + self.slot[i].icon_s = xml:InitStatic("indicators:static", self.dialog) + self.slot[i].icon_f = xml:InitStatic("indicators:flashing", self.dialog) + for k, ele in pairs(self.slot[i]) do + ele:SetWndPos( vector2():set( x , 0 ) ) + utils_xml.correct_ratio(ele) + end + end +end + +function UIIndicators:Clear() + for i=1,size_table(indicators) do + if self.slot[i] then + for k, ele in pairs(self.slot[i]) do + ele:Show(false) + end + end + end + self.index = 0 +end + +function UIIndicators:Update(force) + CUIScriptWnd.Update(self) + + local tg = time_global() + if force then + self._tmr = tg - 1 + end + if self._tmr >= tg then + return + else + self._tmr = tg + 1000 + end + + -- Clear all + self:Clear() + + -- Hide HUD when it's occupied by a GUI class + if not main_hud_shown() then + return + end + + -- Gather info + for name,t in spairs(indicators, func_index) do + local value = t.functor and execute_func(unpack(t.functor)) + + -- Determine if we should show the indicator on HUD according to type and value + local pass = false + if t.typ == "state" then + pass = value and value <= 4 and value >= 1 + elseif t.typ == "booster" then + pass = value and true or false + end + + if pass then + local i = self.index + 1 + + if t.icon then + local ico + if t.anim_period and (t.typ == "booster") and (value < t.anim_period) then + ico = self.slot[i].icon_f + else + ico = t.anim_icon and self.slot[i].icon_f or self.slot[i].icon_s + end + + ico:InitTexture( t.icon ) + ico:Show(true) + end + + if t.background then + local bk + if t.anim_period and (t.typ == "booster") and (value < t.anim_period) then + bk = self.slot[i].back_f + else + bk = t.anim_bk and self.slot[i].back_f or self.slot[i].back_s + end + + bk:InitTexture( t.background ) + bk:SetTextureColor( t.typ == "state" and self.clr_list[value] or self.clr_list[0] ) + bk:Show(true) + end + + self.index = i + end + end + + self.dialog:Show(self.index > 0) +end + + +---------------------------------- +-- Callbacks +---------------------------------- + +local function actor_on_first_update() + prepare_boosters_effect() + prepare_safe_zone() + + activate_hud() +end + +local function actor_on_update() + + local tg = time_global() + if tg < tg_update then + return + end + tg_update = tg + tg_update_step + + --local cover_curr, cover_near, num = scan_safe_zone() + local cover_curr, cover_near, num = scan_safe_zone_old() + --actor_menu.set_msg(nil, strformat("cover_curr: %s | cover_near: %s | num: %s", cover_curr, cover_near, num)) + --printf("cover_curr: %s | cover_near: %s | num: %s", cover_curr, cover_near, num) + SetEvent("current_safe_cover", cover_curr) + SetEvent("nearest_safe_cover", cover_near) + + scan_boosters_effect() +end + +function on_game_start() + RegisterScriptCallback("actor_on_first_update",actor_on_first_update) + RegisterScriptCallback("actor_on_update",actor_on_update) +end diff --git a/gamedata/scripts/alife_storage_manager.script b/gamedata/scripts/alife_storage_manager.script new file mode 100644 index 000000000..8e2909e27 --- /dev/null +++ b/gamedata/scripts/alife_storage_manager.script @@ -0,0 +1,311 @@ +--[[ + This script handles the serialization of data using the marshal library and the saving of this data to disk depending + on save game name. What this means is that you can save entire tables to disk instead of saving to object packet. + This script was created to alleviate issues with using packets to save dynamic data (pstor) which lead to save corruption. + Also it removes some restrictions on what you can save. + + *only store valid lua types such as numbers, strings, boolean, functions or tables that contain these valid types. Userdata needs to have a special + __persist function defined in it's metatable. See how it is done for CTime in _G.script + + *Supposedly you can save userdata if you write a proper __persist method for the metatable but I have failed to achieve proper results with serializing CTime. + + *You must register for 'save_state' and 'load_state' and add your own table to m_data for it to be encoded then stored in *.scoc + *Although marshal is pretty fast, keep in mind that encoding/decoding a ton of data, saves will start to noticeablely take longer to save/load. + *For testing/debugging you can uncomment the print_table calls in save_state and load_state. It will save the before and after tables to print_table.txt in your main directory. + + + by: Alundaio +--]] +local m_data = {} + +-- store stuff that you want to persist even offline +m_data.se_object = {} + +-- store stuff only for online objects. When object goes offline this table is purged. +m_data.game_object = {} + +-- PDA known contacts +m_data.actor_contacts = {} + +local function on_pstor_load_all(obj,packet) + local id = obj:id() + local state = get_game_object_state(obj) + if (state and db.storage[id]) then + if (state.pstor_all) then + db.storage[id].pstor = state.pstor_all + state.pstor_all = nil + end + + if (state.pstor_ctime) then + db.storage[id].pstor_ctime = state.pstor_ctime + state.pstor_ctime = nil + end + end +end + +local function server_entity_on_unregister(se_obj, typ) + local id = se_obj.id + if (m_data.se_object[id]) then + m_data.se_object[id] = nil + --printf("$ alife_storage | cleaning server object [%s](%s) mdata", se_obj:name(), id) + end + if (m_data.game_object[id]) then + m_data.game_object[id] = nil + --printf("$ alife_storage | cleaning game object [%s](%s) mdata", se_obj:name(), id) + end +end + +local function actor_on_first_update() + alife_first_update() -- _G + on_after_load_state() +end + +function on_game_start() + if not (USE_MARSHAL) then + return + end + RegisterScriptCallback("on_pstor_load_all",on_pstor_load_all) + RegisterScriptCallback("actor_on_first_update",actor_on_first_update) + --RegisterScriptCallback("server_entity_on_unregister",server_entity_on_unregister) -- NOTE: axr_main already handles this at last +end + +-- Use these to perform changes on stored data between updates, the first is called +function on_before_load_state() -- before loading + local prev_version = m_data.GAME_VERSION + local new_version = GAME_VERSION + + if prev_version and (prev_version ~= new_version) then + printf("~ Marshal Library | Old save detected, switching data from (%s) to (%s) - BEFORE load", prev_version, new_version) + + if m_data.comp_mule and m_data.comp_mule.assigned_items then + m_data.companion_assigned_items = dup_table(m_data.comp_mule.assigned_items) + m_data.comp_mule = nil + printf("~ Marshal Library | companion_assigned_items is adjusted") + end + end +end + +function on_after_load_state() -- after first actor update + local prev_version = m_data.GAME_VERSION + local new_version = GAME_VERSION + + if prev_version and (prev_version ~= new_version) then + printf("~ Marshal Library | Old save detected, switching data from (%s) to (%s) - AFTER load", prev_version, new_version) + if m_data.npc_pda then + for id, info in pairs(m_data.npc_pda) do + local se_pda = alife_object(id) + if se_pda and info then + local sec = se_pda:section_name() + local p_id = se_pda.parent_id + alife_release(se_pda) + if p_id then + local se_parent = p_id == AC_ID and db.actor or alife_object(p_id) + if se_parent then + local se_pda_new = alife_create_item(sec, se_parent) + if se_pda_new then + se_save_var( se_pda_new.id, se_pda_new:name(), "info", dup_table(info) ) + printf("~ Marshal Library | NPC PDA [%s] is shifted to new object [%s]", id, se_pda_new.id) + end + end + end + end + end + m_data.npc_pda = nil + end + + if m_data.item_parts_state then + for id, data in pairs(m_data.item_parts_state) do + local se_item = alife_object(id) + if se_item and data then + se_save_var( se_item.id, se_item:name(), "parts", dup_table(data) ) + printf("~ Marshal Library | Item [%s] has parts", se_item:name()) + end + end + m_data.item_parts_state = nil + end + + if m_data.disguise_unpatched_outfit then + for id, state in pairs(m_data.disguise_unpatched_outfit) do + local se_item = alife_object(id) + if se_item and state then + se_save_var( se_item.id, se_item:name(), "unpatched", true ) + printf("~ Marshal Library | Outfit [%s] has no patch", se_item:name()) + end + end + m_data.disguise_unpatched_outfit = nil + end + + local to_replace = { + ["detector_anomaly"] = true, + ["detector_radio"] = true, + ["detector_geiger"] = true, + } + local sim = alife() + for i=1,sim:max_id() - 1 do + local se_obj = sim:object(i) + local sec = se_obj and se_obj:section_name() + if sec and to_replace[sec] then + local p_id = se_obj.parent_id + alife_release(se_obj) + if p_id then + local se_parent = p_id == AC_ID and db.actor or alife_object(p_id) + alife_create_item(sec, se_parent) + printf("~ Marshal Library | Created [%s] for [%s]", sec, se_parent:name()) + end + end + end + end +end + +-- called from engine! +function CALifeStorageManager_before_save(fname) + if not (USE_MARSHAL) then + return + end + + --printf("CALifeStorageManager_before_save BEFORE callback") + + m_data.GAME_VERSION = GAME_VERSION + + SendScriptCallback("save_state",m_data) + + --printf("CALifeStorageManager_before_save AFTER callback") + + -- save pstor + for id,t in pairs(db.storage) do + if (m_data.game_object[id]) then + if (t.pstor and not is_empty(t.pstor)) then + m_data.game_object[id].pstor_all = t.pstor + end + + -- serialization with game.CTime.__persist + if (t.pstor_ctime and not is_empty(t.pstor_ctime)) then + m_data.game_object[id].pstor_ctime = t.pstor_ctime + end + end + end + + --ProcessEventQueueState(m_data,true) + + --[[ clean out se_object table of empty sub tables + for k,v in pairs(m_data.se_object) do + if (type(v) == "table" and is_empty(v)) then + v = nil + end + end + + -- clean out game_object table of empty sub tables + for k,v in pairs(m_data.game_object) do + if (type(v) == "table" and is_empty(v)) then + v = nil + end + end + --]] + + -- clean out game_object table of empty sub tables + for id,tbl in pairs(m_data.game_object) do + for k,v in pairs(tbl) do + if (type(v) == "table" and is_empty(v)) then + m_data.game_object[id][k] = nil + end + end + end + + local data = marshal.encode(m_data) + if not (data) then + return + end + + local path = getFS():update_path('$game_saves$', '') + + lfs.mkdir(path) -- incase savegame folder doesn't exist yet + + path = path .. fname:sub(0,-6):lower() .. ".scoc" + + local savegame = io.open(path,"wb") + if not (io.type(savegame) == "file") then + printe("!Error: Unable to write to %s",path) + return + end + + --printf("axr_main: saving custom data %s",path) + savegame:write(data) + savegame:close() + + --printf("CALifeStorageManager_before_save FINISHED") +end + +-- called from engine! +function CALifeStorageManager_save(fname) + --printf("CALifeStorageManager_save START FINISHED") +end + +-- called from engine +function CALifeStorageManager_load(fname) + if not (USE_MARSHAL) then + return + end + + local path = fname:sub(0,-6) .. ".scoc" + + --utils_data.debug_write(strformat("CALifeStorageManager_load %s",path)) + + local savegame = io.open(path,"rb") + if not (io.type(savegame) == "file") then + return + end + + local data = savegame:read("*all") + savegame:close() + + if not (data and data ~= "") then + printe("!Error: Failed to read %s",path) + return + end + + m_data = marshal.decode(data) + + --ProcessEventQueueState(m_data,false) + + -- For debugging save state + --utils_data.print_table(m_data,"m_data_on_load ("..path..")") + + on_before_load_state() + + SendScriptCallback("load_state",m_data) + + --utils_data.debug_write(strformat("CALifeStorageManager_load END")) +end + +function get_state() + return m_data +end + +function decode(t) + return marshal.decode(t) +end + +-- storage based on ID but verified by object name +function get_game_object_state(obj,create_if_dont_exist) + local id = obj:id() + if not (m_data.game_object[id]) then + if not (create_if_dont_exist) then + return + end + m_data.game_object[id] = {} + m_data.game_object[id].name = obj:name() + end + return m_data.game_object[id] +end + +function get_se_obj_state(se_obj,create_if_dont_exist) + local id = se_obj.id + if not (m_data.se_object[id]) then + if not (create_if_dont_exist) then + return + end + m_data.se_object[id] = {} + m_data.se_object[id].name = se_obj:name() + end + return m_data.se_object[id] +end diff --git a/gamedata/scripts/bind_awr.script b/gamedata/scripts/bind_awr.script new file mode 100644 index 000000000..beea74e79 --- /dev/null +++ b/gamedata/scripts/bind_awr.script @@ -0,0 +1,501 @@ +-------------------------------------------------------------------- +-- Reworked by Tonex +-- Last edit: 2019/5/18 + +-- Adapted new changes for the system +-- Cleaned a lot of bloated code that was previously used for updating vice meshes +-------------------------------------------------------------------- + +local MDATA = {} +local settings_list = {} +local angles_t = {} +local mech_list_key = {} +local debug_mode = false +local debug_show_tables = false +local ini_manager, settings, mesh_list, delay, ui + +-- Cache vice and their lamps in table to reuse +local vice_id = {} +local lamp_id = {} + +function access(obj) --| Access to a vice, taking into account possible death of the mechanic + + if not (obj) then + return + end + + --// You should be able to use any workshop in Warfare mode + if _G.WARFARE then + return true + end + + local name = obj:name() + dout(nil, "Request access for %s", name) + + --// Mechanic is dead - access is unlimited + if angles_t[name] then + if angles_t[name][3] == 'dead' then + dout(nil, "Mechanic is dead. Full access") + return true + end + end + + --// Access was granted by a mechanic. + if db.actor:has_info(string.format("awr_%s_access",angles_t[name][1])) then + dout(nil, "Mechanic gave access") + return true + end + + --// Debugging + local npc = get_story_object(angles_t[name][1]) + if npc then + if npc:alive() then + dout(nil, "Access not granted. NPC %s by id %s is alive", angles_t[name][1], npc:id()) + else + dout(nil, "![ERROR] NPC %s by id %s bugged, because he is online and is not alive, but function OnDeath was not called", angles_t[name][1], npc:id()) + end + end +end + +function OnDeath(npc) --| Callback at the death of a mechanic + dout(nil, "Called for NPC, %s", npc:section()) + if IsStalker(npc) and (not npc:alive()) then + local npc_s = npc:section() + dout(nil, "NPC %s exist and dead", npc:section()) + for key, val in pairs(angles_t) do + if angles_t[key][1] == npc_s then + + local story_obj = get_story_object(angles_t[key][1]) + if story_obj then + level.map_remove_object_spot(story_obj:id(), "ui_pda2_mechanic_location") + end + + --// Add a marker + SetMarker(key) + + full_access(key,npc_s) + + break + end + end + end +end + +function SetMarker(name) --| The function of adding a marker to the map + local id = name and vice_id[name] + local s_obj = id and alife_object(id) + if s_obj then + dout(nil, "Add marker on parent object %s with ID %s", name, s_obj.id) + level.map_add_object_spot_ser(s_obj.id, "ui_pda2_mechanic_location", "st_mech_tiski") + end +end + +function CloseDl() -- The closing function of the UI AWR at the end of time (provided that the UI is called and the actor is within 2 meters of the mesh) + if (ui) and ui:IsShown() then + dout(nil, "UI exist and already open") + ui:Close() + end +end + + +--=======================================< Callbacks >=======================================-- + +function physic_object_on_use_callback(_obj,who) -- Binder function using vise mesh + if not (_obj) or not (string.match(_obj:name(), "awr")) then + return + end + + --// The condition for the "parent" vise, spawn through all.spawn + if access(_obj) and string.match(_obj:name(), "awr_tiski") and _obj:position():distance_to(db.actor:position()) < 1.5 then + + --// Checking the table for "empty" + r_unused() + + --// Gather mechanic info + local flag_1,flag_2,flag_3,flag_4 = false,false,false,false + local name = _obj:name() + local mechanic = name and angles_t[name] and angles_t[name][1] + if mechanic then + flag_1 = db.actor:has_info(mechanic .. "_upgrade_tier_1") + flag_2 = db.actor:has_info(mechanic .. "_upgrade_tier_2") + flag_3 = db.actor:has_info(mechanic .. "_upgrade_tier_3") + + local drugkit_done = ini_manager:r_string_ex("drugkit_access",mechanic) + if drugkit_done and db.actor:has_info(drugkit_done) then + flag_4 = true + end + end + + local function start_ui() + --// Call UI + local hud = get_hud() + --if ui then + --ui:HideDialog() + --end + ui = ui_workshop and ui_workshop.get_workshop_ui(hud, mechanic, {flag_1,flag_2,flag_3,flag_4,false}) + if (ui) then + dout(nil, "call UI") + ui:ShowDialog(true) + end + return true + end + + local delay = actor_effects.is_animations_on() and 2 or 0 + actor_effects.play_item_fx("workshop_dummy") + CreateTimeEvent(0,"delay_workshop",delay,start_ui) + end +end + +function actor_on_first_update() --| Callback of the first Update actor. It is executed one-time after loading, _after_ spawn of all objects from all.spawn, unlike on_game_load + local smatch = string.match + local sim = alife() + for i=1, sim:max_id() - 1 do + local s_obj = sim:object(i) + if s_obj then + local name = s_obj:name() + + --// If the vise is spawned all.spawn - save their values in the store + if smatch(name, '%w+%_awr%_tiski%_%d+') then + --// Cache vice id + vice_id[name] = i + angles_t[name] = l_v(name, angles_t[name]) + + if debug_mode then + printf("/ Registered vice [%s] = %s", name, i) + end + + --// Cache vice lamps IDs + elseif smatch(name, '_awr_lamp') then + lamp_id[name] = i + + if debug_mode then + printf("/ Registered vice lamp [%s] = %s", name, i) + end + end + end + end + + --// Turn on lamps for all mechanics with a dead flag, otherwise it should be off + for key, val in pairs(angles_t) do + + --// Mechanic is dead -> grant full acces + turn on lamp + if angles_t[key][3] == 'dead' then + dout('actor_on_first_update', "%s is dead. Enable lamp(s) which assigned for %s", angles_t[key][1], key) + Lamp(angles_t[key][1], true) + full_access(key,npc_s) + + --// Mechanic is unmarked for death -> turn off lamp + else + dout('actor_on_first_update', "%s is alive. Disable lamp(s) which assigned for %s", angles_t[key][1], key) + Lamp(angles_t[key][1], false) + + --[[ + // If Mechanic isn't around, grant unlimited access + local npc_s = angles_t[key][1] + local se_npc = get_story_se_object(npc_s) + if (not se_npc) then + full_access(key,npc_s) + end + --]] + end + + if db.actor:has_info(string.format('awr_%s_access', angles_t[key][1])) then + dout('actor_on_first_update', "%s gave access. Enable lamp(s) which assigned for %s", angles_t[key][1], key) + Lamp(angles_t[key][1], true) + end + end + + --// Remove extra tables for weapons whose parts have not been replaced. + r_unused() +end + +function npc_on_death_callback(victim, who) -- Callback, NPC caused by death + if not (victim and who) then + return + end + + local name = victim:section() + local killer_name + + if mech_list_key[name] then + + dout('npc_on_death_callback', "NPC %s was killed by %s", victim:name(), who:name()) + OnDeath(victim) + + if IsStalker(who) then + killer_name = who:character_name() + else + killer_name = nil + end + + if who:id() == AC_ID then + local alife = alife() + local se_actor = alife:actor() + killer_name = se_actor:character_name() + end + actor_menu.set_item_news('success', 'npc', "st_awr_dead_mechanic", victim:character_name(), killer_name or game.translate_string("st_by_unknown")) + else + -- if IsStalker(victim) then + -- awr_sf.dout('npc_on_death_callback', "NPC %s is not in list -> return", victim:name()) -- Отрабатывает для всех смертей, включать при необходимости + -- end + return + end +end + +function save_state(m_data) + m_data.workshop = MDATA +end + +function load_state(m_data) + MDATA = m_data.workshop or {} +end + +function on_game_start() + ini_manager = itms_manager.ini_manager + settings = utils_data.collect_section(ini_manager,"workshop_settings") + mesh_list = utils_data.collect_section(ini_manager,"workshop_angles") + + --for _, k in ipairs(settings) do + --settings_list[k] = ini_manager:r_float_ex("workshop_settings", k) + --end + + for _, k in ipairs(mesh_list) do + local t = parse_list(ini_manager,"workshop_angles", k) + angles_t[k] = {} + for _, v in ipairs(t) do + table.insert(angles_t[k], v) + end + end + + --// We enter data into the table keys for quick indexing by key, without using a loop + for key, val in pairs(angles_t) do + mech_list_key[val[1]] = 0 + end + + RegisterScriptCallback("physic_object_on_use_callback", physic_object_on_use_callback) + RegisterScriptCallback("actor_on_first_update", actor_on_first_update) + RegisterScriptCallback("npc_on_death_callback", npc_on_death_callback) + RegisterScriptCallback("save_state", save_state) + RegisterScriptCallback("load_state", load_state) +end + + +--=======================================< Utility >=======================================-- +function Lamp(npc_name, state) --| Toggle online workshop lamps on/off + for lamp_name,id in pairs(lamp_id) do + if string.match(lamp_name, string.format('_awr_lamp_%s', npc_name)) then + local se_lamp = alife_object(id) + if se_lamp then + local lamp = level.object_by_id(id) + if lamp then + if (state == true) then + lamp:get_hanging_lamp():turn_on() + dout(nil, "Lamp %s was turned on", lamp_name) + elseif (state == false) then + lamp:get_hanging_lamp():turn_off() + dout(nil, "Lamp %s was turned off", lamp_name) + end + end + end + end + end +end + +function full_access(vice,npc_s) --| Give unlimited access to a workshop + --// 'Dead' flag in the table + angles_t[vice][3] = 'dead' + + --// Delete information if, before the death of a mechanic, he had access + if db.actor:has_info(string.format('awr_%s_access', npc_s)) then + db.actor:disable_info_portion(string.format('awr_%s_access', npc_s)) + dout(nil, 'NPC is dead. Infoportion awr_%s_access has been removed', npc_s) + end + + --// Issuance of information + db.actor:give_info_portion(string.format("awr_%s_dead", npc_s)) + + --// Turn on the lamps + Lamp(npc_s, true) + + --// Store data + s_v(vice, angles_t[vice]) +end + +function s_v(name,val) --| +--// Функция сохранения данных в Store + dout(nil, "Save data to Store, table %s on value %s", name, val) + + MDATA[name] = val + if debug_show_tables then print_table(MDATA, 'On Save') end +end + +function l_v(name, def) --| +--// Функция загрузки данных из Store + local function len(t) + local i = 0 + for _ in pairs(t) do i = i + 1 end + return i + end + + dout(nil, 'Trying to load %s from Store', name) + local m_data = alife_storage_manager.get_state() + if MDATA[name] and len(MDATA[name]) > 0 then + dout(nil, "Table %s with %s keys was loaded", name, len(MDATA[name])) + if debug_show_tables then print_table(MDATA, 'On Load') end + return MDATA[name] or def + else + dout(nil, 'Table %s does not exist or is empty. Skipped', name) + end + return def or nil +end + +function r_unused() --| +--// Функция удаления "пустых" таблиц с замененными деталями для оружия и флагами + local chk = 0 + dout(nil, 'Searching unused tables in AWR Store table...') + if MDATA then + for k, _ in pairs(MDATA) do + if k:match('_upg') then + dout(nil, 'Checking %s table...', k) + local count = 0 + for key, val in pairs(MDATA[k]) do + count = count + val + end + if count == 5 then + local flags = string.format('%s%s', k:gsub('[^%d+]', ''), '_flags') + dout(nil, '%s table have default values, tables %s and %s will be removed', k, k, flags) + MDATA[k] = nil + MDATA[flags] = nil + chk = chk + 2 + else + dout(nil, 'Data in table %s is used. Skipped', k) + end + end + end + if chk == 0 then + dout(nil, 'AWR Store table have no unused tables') + else + dout(nil, 'Removed %s tables', chk) + end + else + dout(nil, 'AWR table does not exist') + end + if debug_show_tables then print_table(MDATA, 'On Remove Unused') end +end + +function dout(call,fmt,...) --| +--// Функция отладочного вывода (включен при debug_mode = true в awr_settings.ltx) + if not (debug_mode) then return end + if not (fmt) then return end + local fmt = tostring(fmt) + + --// Пытаемся определить из какой функции произошел вызов целевой функции + local caller_n = debug.getinfo(3, "n") and debug.getinfo(3, "n").name or "not specified" + local f_name = debug.getinfo(2, "n") and debug.getinfo(2, "n").name or "not specified" + + if call then + caller_n = tostring(call) + end + + 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 + local str = string.format('[AWR]{%s->%s} %s', caller_n, f_name, fmt) + log(str) + --exec_console_cmd("flush") + else + exec_console_cmd("load ~#debug msg:"..str) + end +end + +function print_table(tbl,header,format_only) --| +--// Функция для вывода содержимого таблицы в строковом виде + local txt = header and ("-- " .. tostring(header) .. "\n{\n\n") or "{\n\n" + local depth = 1 + + local function tab(amt) + local str = "" + for i=1,amt, 1 do + str = str .. "\t" + end + return str + end + + local function table_to_string(tbl) + local size = 0 + for k,v in pairs(tbl) do + size = size + 1 + end + + local key + local i = 1 + + for k,v in pairs(tbl) do + if (type(k) == "number") then + key = "[" .. k .. "]" + elseif (type(k) == "function" or type(k) == "string" or type(k) == "boolean" or type(k) == "table") then + key = "[\""..tostring(k) .. "\"]" + else + key = "[____unknown_type]" + end + + if (type(v) == "table") then + txt = txt .. tab(depth) .. key .. " =\n"..tab(depth).."{\n" + depth = depth + 1 + table_to_string(v,tab(depth)) + depth = depth - 1 + txt = txt .. tab(depth) .. "}" + elseif (type(v) == "number" or type(v) == "boolean") then + txt = txt .. tab(depth) .. key .. " = " .. tostring(v) + elseif (type(v) == "userdata") then + if (v.diffSec) then + local Y, M, D, h, m, s, ms = 0,0,0,0,0,0,0 + Y, M, D, h, m, s, ms = v:get(Y, M, D, h, m, s, ms) + txt = strformat("%s%s%s = { Y=%s, M=%s, D=%s, h=%s, m=%s, s=%s, ms=%s } ",txt,tab(depth),key,Y, M, D, h, m, s, ms) + else + txt = txt .. tab(depth) .. key .. " = \"userdata\"" + end + elseif (type(v) == "function") then + txt = txt .. tab(depth) .. key .. " = \"" .. tostring(v) .. "\"" + elseif (type(v) == "string") then + txt = txt .. tab(depth) .. key .. " = '" .. v .. "'" + else + txt = txt .. tab(depth) .. key + end + + if (i == size) then + txt = txt .. "\n" + else + txt = txt .. ",\n" + end + + i = i + 1 + end + end + + table_to_string(tbl) + + txt = txt .. "\n}" + + if (format_only) then + return txt + end + + printf(txt) + local file = io.open("gamedata\\awr_table.txt","a+") + file:write(txt.."\n\n") + file:close() +end diff --git a/gamedata/scripts/bind_stalker_ext.script b/gamedata/scripts/bind_stalker_ext.script new file mode 100644 index 000000000..c9b4053ff --- /dev/null +++ b/gamedata/scripts/bind_stalker_ext.script @@ -0,0 +1,441 @@ +--==============( Ported from CoC 1.5, used for Azazel mode & invulnerability time )==============-- + +--[[ + bind_stalker_ext.script + + Extends the functionality of bind_stalker.script without filling it with clutter + + by: Alundaio +--]] + + +local K_CTRL = DIK_keys.DIK_LCONTROL +local K_SHIFT = DIK_keys.DIK_LSHIFT +local K_ALT = DIK_keys.DIK_LMENU + +------------------- +-- actor_on_init +------------------- +function actor_on_init(binder) + level_weathers.get_weather_manager() + SendScriptCallback("actor_on_init",binder) +end + +function actor_on_reinit(binder) + SendScriptCallback("actor_on_reinit",binder) + AddUniqueCall(ProcessEventQueue) +end +------------------- +-- actor_on_net_spawn +------------------- +function actor_on_net_spawn() + if (ui_options.get("gameplay/general/hardcore_ai_aim")) then + exec_console_cmd("ai_aim_max_angle 20.0") + exec_console_cmd("ai_aim_min_angle 17.0") + exec_console_cmd("ai_aim_min_speed 2.00") + exec_console_cmd("ai_aim_predict_time 0.28") + else + exec_console_cmd("ai_aim_max_angle 0.7854") + exec_console_cmd("ai_aim_min_angle 0.19635") + exec_console_cmd("ai_aim_min_speed 0.24") + exec_console_cmd("ai_aim_predict_time 0.40") + end + + -- some hacks + local sim = alife() + local remove_outfits = not db.actor:has_info("remove_outfits_hack") + for i=1,sim:max_id() - 1 do + local se_obj = sim:object(i) + if (se_obj) then + -- hack to remove phantoms + if (se_obj:clsid() == clsid.phantom) then + --printf("release phantom hack!") + safe_release_manager.release(se_obj) + end + end + end + if (remove_outfits) then + db.actor:give_info_portion("remove_outfits_hack") + end + + SendScriptCallback("on_game_load",binder) +end + +------------------------------------- +-- actor_on_net_destroy +------------------------------------- +function actor_on_net_destroy(binder) + SendScriptCallback("actor_on_net_destroy",binder) +end + +------------------- +-- actor_on_update +------------------- +invulnerable_time = nil +function actor_on_first_update(binder,delta) + invulnerable_time = invulnerable_time or time_global() + 7000 + SendScriptCallback("actor_on_first_update",binder,delta) +end + +function actor_on_update(binder,delta) + if not (db.actor:alive()) then + return + end + + -- WISH GRANTER ZOMBIED-IMMORTALITY + if (db.actor:has_info("actor_made_wish_immortal") or invulnerable_time and invulnerable_time > time_global() + 7000) then + db.actor.health = 1 + db.actor.bleeding = 1 + db.actor.psy_health = 1 + db.actor.radiation = 0 + end + --[[ enable if you need it + local torch = db.actor:object("device_torch") + if (torch and not actor_light and torch:torch_enabled()) then + actor_light = true + SendScriptCallback("actor_on_torch_enabled") + elseif (torch and not torch:torch_enabled()) then + if (actor_light) then + actor_light = false + SendScriptCallback("actor_on_torch_disabled") + end + end + --]] + SendScriptCallback("actor_on_update",binder,delta) +end + +------------------- +-- actor_on_weapon_fire(actor,wpn,elapsed) +------------------- +function actor_on_weapon_fired(binder,obj,wpn,ammo_elapsed,grenade_elapsed,ammo_type,grenade_type) + SendScriptCallback("actor_on_weapon_fired",obj,wpn,ammo_elapsed,grenade_elapsed,ammo_type,grenade_type) +end + +------------------- +-- actor_on_weapon_no_ammo(wpn) +------------------- +function actor_on_weapon_no_ammo(binder,wpn) + SendScriptCallback("actor_on_weapon_no_ammo",wpn) +end + +------------------- +-- actor_on_weapon_zoom_in(wpn) +------------------- +function actor_on_weapon_zoom_in(binder,obj,wpn) + SendScriptCallback("actor_on_weapon_zoom_in",wpn) +end + +------------------- +-- actor_on_weapon_zoom_out(wpn) +------------------- +function actor_on_weapon_zoom_out(binder,obj,wpn) + SendScriptCallback("actor_on_weapon_zoom_out",wpn) +end + +------------------- +-- actor_on_item_take(item) +------------------- +function actor_on_item_take(binder,item) + if not (db.actor) then + return -- don't remove, for some reason this callback is triggered before actor is fully loaded + end + SendScriptCallback("actor_on_item_take",item) +end + +------------------- +-- actor_on_item_take_from_box +------------------- +function actor_on_item_take_from_box(binder,box,item) + SendScriptCallback("actor_on_item_take_from_box",box,item) +end + +------------------- +-- +------------------- +function actor_on_item_drop(binder,item) + SendScriptCallback("actor_on_item_drop",item) +end + +------------------- +-- actor_on_item_use(item) +------------------- +function actor_on_item_use(binder,item) + local sec = item:section() + if (sec == "drug_anabiotic") then + xr_effects.disable_ui_only(db.actor, nil) + level.add_cam_effector("camera_effects\\surge_02.anm", 10, false, "bind_stalker_ext.anabiotic_callback") + level.add_pp_effector("surge_fade.ppe", 11, false) + give_info("anabiotic_in_process") + _G.mus_vol = get_console_cmd(2,"snd_volume_music") + _G.amb_vol = get_console_cmd(2,"snd_volume_eff") + exec_console_cmd("snd_volume_music 0") + exec_console_cmd("snd_volume_eff 0") + elseif (sec == "drug_psy_blockade" or sec == "drug_radioprotector" or sec == "drug_antidot") then + local boost_time = ini_sys:r_float_ex(sec,"boost_time") + save_var(db.actor,sec.."_expiration",utils_data.CTimeAddSec(game.get_game_time(),boost_time*level.get_time_factor())) + elseif (sec == "radio_connections_pda") then + run_dynamic_element(pda_dialog.pda_dialog_box(),true) + end + SendScriptCallback("actor_on_item_use",item,sec) +end + +------------------- +-- actor_on_trade(item) +------------------- +function actor_on_trade(binder,item,sell_bye,money) + -- if sell_bye == true then + -- game_stats.money_trade_update (money) + -- else + -- game_stats.money_trade_update (-money) + -- end + SendScriptCallback("actor_on_trade",item,sell_bye,money) +end + +------------------- +-- actor_on_save(bind_stalker,packet) +-- IMPORTANT: Cannot be used as callback because this needs to be done in specific order, just call script directly! +------------------- +function actor_on_save(binder,packet) + local sim,gg = alife(),game_graph() + local actor_gv = sim and gg:vertex(sim:actor().m_game_vertex_id) + + if (actor_gv and actor_gv:level_id() ~= sim:level_id()) then + --printf("DEBUG: actor changing level") + --task_manager.get_task_manager():set_task_cancelled("faction_base_defense") -->> LD + SendScriptCallback("on_level_changing") + end + + if (treasure_manager) then + treasure_manager.save(packet) + end + + surge_manager.actor_on_save(binder,packet) + psi_storm_manager.actor_on_save(binder,packet) +end + +------------------- +-- actor_on_load(bind_stalker,packet) +-- IMPORTANT: Cannot be used as callback because this needs to be done in specific order, just call script directly! +------------------- +function actor_on_load(binder,packet) + if (treasure_manager) then + treasure_manager.load(packet) + end + surge_manager.actor_on_load(binder,packet) + psi_storm_manager.actor_on_load(binder,packet) +end + +local flags = { ret_value = true } + +function actor_on_item_before_pickup(item) -- called from engine! +-- Called when actor is pressing use key to pick up an item +-- return false will prevent actor from pickup the item + + SendScriptCallback("actor_on_item_before_pickup",item,flags) + return flags.ret_value +end + +--------------------------------------------- +-- on_key_press +--------------------------------------------- +function on_key_press(binder,key) + if (not KEYS_UNLOCK) then + if key == K_CTRL or key == K_SHIFT or key == K_ALT then + else + return + end + end + + SendScriptCallback("on_key_press",key) +end + +--------------------------------------------- +-- on_key_release +--------------------------------------------- +function on_key_release(binder,key) + if (not KEYS_UNLOCK) then + if key == K_CTRL or key == K_SHIFT or key == K_ALT then + else + return + end + end + + SendScriptCallback("on_key_release",key) +end + +--------------------------------------------- +-- on_key_hold +--------------------------------------------- +function on_key_hold(binder,key) + --[[ + if (not KEYS_UNLOCK) then + if key == K_CTRL or key == K_SHIFT or key == K_ALT then + else + return + end + end + --]] + + SendScriptCallback("on_key_hold",key) +end + +--------------------------------------------- +-- on_actor_before_death +--------------------------------------------- +function actor_on_before_death(binder,whoID) + local flags = { ret_value = true } + SendScriptCallback("actor_on_before_death",whoID,flags) + if (flags.ret_value == true) then + db.actor:kill(db.actor,true) + end +end + +--------------------------------------------- +-- on_attach_vehicle +--------------------------------------------- +function actor_on_attach_vehicle(binder,obj) + SendScriptCallback("actor_on_attach_vehicle",obj) +end + +--------------------------------------------- +-- on_detach_vehicle +--------------------------------------------- +function actor_on_detach_vehicle(binder,obj) + SendScriptCallback("actor_on_detach_vehicle",obj) + if (obj:section() == "deployable_mgun") then + alife_release_id(obj:id()) + alife_create_item("itm_deployable_mgun", db.actor) + end +end + +--------------------------------------------- +-- on_use_vehicle +--------------------------------------------- +function actor_on_use_vehicle(binder,obj) + SendScriptCallback("actor_on_use_vehicle",obj) +end + +--------------------------------------------- +-- actor_on_task_callback +--------------------------------------------- +function actor_on_task_callback(binder,_task, _state) + if _state ~= task.fail then + if _state == task.completed then + news_manager.send_task(db.actor, "complete", _task) + else + news_manager.send_task(db.actor, "new", _task) + end + end + task_manager.task_callback(_task, _state) +end + +------------------------------------------ +-- anabiotic_callback +------------------------------------------ +function anabiotic_callback() + level.add_cam_effector("camera_effects\\surge_01.anm", 10, false, "bind_stalker_ext.anabiotic_callback2") + local rnd = math.random(35,45) + local m = surge_manager.get_surge_manager() + if(m.started) then + local tf = level.get_time_factor() + local diff_sec = math.ceil(game.get_game_time():diffSec(m.inited_time)/tf) + if(rnd>(m.surge_time-diff_sec)*tf/60) then + m.time_forwarded = true + m.ui_disabled = true + m:end_surge() + end + end + m = psi_storm_manager.get_psi_storm_manager() + if(m and m.started) then + local tf = level.get_time_factor() + local diff_sec = math.ceil(game.get_game_time():diffSec(m.inited_time)/tf) + if(rnd>(m.psi_storm_duration-diff_sec)*tf/60) then + m.time_forwarded = true + --m.ui_disabled = true + m:finish() + end + end + level.change_game_time(0,0,rnd) + level_weathers.get_weather_manager():forced_weather_change() + --printf("anabiotic_callback: time forwarded on [%d]", rnd) +end + +function anabiotic_callback2() + xr_effects.enable_ui(db.actor, nil) + exec_console_cmd("snd_volume_music "..tostring(_G.mus_vol)) + exec_console_cmd("snd_volume_eff "..tostring(_G.amb_vol)) + _G.amb_vol = 0 + _G.mus_vol = 0 + disable_info("anabiotic_in_process") +end + +function actor_on_info_callback(binder,obj,info_id) + SendScriptCallback("actor_on_info_callback",obj,info_id) + --printf("info_id = %s",info_id) +end + +function actor_item_to_belt(binder,obj) + SendScriptCallback("actor_item_to_belt",obj) +end + +function actor_item_to_slot(binder,obj) + if (ui_options.get("gameplay/general/outfit_portrait") == true) then + local outfit = db.actor:item_in_slot(7) + if (outfit and outfit:id() == obj:id()) then + local icon = ini_sys:r_string_ex(outfit:section(),"character_portrait") + if (icon) then + db.actor:set_character_icon(icon) + elseif (db.actor_binder.character_icon) then -- use default portrait if no outfit portrait exists + db.actor:set_character_icon(db.actor_binder.character_icon) + end + end + end + SendScriptCallback("actor_item_to_slot",obj) +end + +function actor_item_to_ruck(binder,obj) + if (ui_options.get("gameplay/general/outfit_portrait") == true) and IsOutfit(obj) then + local outfit = db.actor:item_in_slot(7) + if (outfit == nil and db.actor_binder.character_icon) then + db.actor:set_character_icon(db.actor_binder.character_icon) + end + end + SendScriptCallback("actor_item_to_ruck",obj) +end + +function actor_on_weapon_jammed(binder,actor,wpn) + SendScriptCallback("actor_on_weapon_jammed",wpn) +end + +function actor_on_weapon_magazine_empty(binder,wpn,ammo_total) + --printf("weapon_no_ammo wpn=%s ammo_total=%s",wpn and wpn:name(),ammo_total) + SendScriptCallback("actor_on_weapon_no_ammo",wpn,ammo_total) +end + +function actor_on_weapon_lowered(binder,wpn) + SendScriptCallback("actor_on_weapon_lower",wpn) +end + +function actor_on_weapon_raised(binder,wpn) + SendScriptCallback("actor_on_weapon_raise",wpn) +end + +function actor_on_weapon_reload(binder,wpn,ammo_total) + SendScriptCallback("actor_on_weapon_reload",wpn,ammo_total) +end + +function actor_on_hud_animation_end(binder,item,section,motion,state,slot) + --printf("hud_animation_end [%s] sec=%s motion=%s state=%s slot=%s",item and item:name(),section,motion,state,slot) + SendScriptCallback("actor_on_hud_animation_end",item,section,motion,state,slot) +end + +function actor_on_hit_callback(binder, obj, amount, local_direction, who, bone_index) + if (amount <= 0) then + return + end + SendScriptCallback("actor_on_hit_callback",obj, amount, local_direction, who, bone_index) +end + +function actor_on_foot_step(binder,obj,power,b_play,b_on_ground,b_hud_view) + SendScriptCallback("actor_on_foot_step",obj,power,b_play,b_on_ground,b_hud_view) +end \ No newline at end of file diff --git a/gamedata/scripts/callbacks_gameobject.script b/gamedata/scripts/callbacks_gameobject.script index ecb6679d1..d456b515f 100644 --- a/gamedata/scripts/callbacks_gameobject.script +++ b/gamedata/scripts/callbacks_gameobject.script @@ -43,6 +43,19 @@ _G.game_objects_iter = function() end end +-- Game objects without actor +_G.game_objects_without_actor_iter = function() + local k = nil + local obj = nil + return function() + k, obj = next(gameobjects_registry, k) + if k == AC_ID then + k, obj = next(gameobjects_registry, k) + end + return obj + end +end + -- Pseudogiant callbacks AddScriptCallback("pseudogiant_on_start_stomp_animation") AddScriptCallback("pseudogiant_before_stomp") diff --git a/gamedata/scripts/debug_cmd_list.script b/gamedata/scripts/debug_cmd_list.script new file mode 100644 index 000000000..ef39b06b0 --- /dev/null +++ b/gamedata/scripts/debug_cmd_list.script @@ -0,0 +1,3479 @@ +local cmd = {} + +function command_get_list() + return cmd +end + +function command_get_count() + local count = 0 + for k,v in pairs(cmd) do + count = count + 1 + end + return count +end + +function command_exists(name) + return cmd[name] ~= nil +end + +function command_give(name,...) + return cmd[name](name,...) +end + +function split(txt) + args = {} + for x in txt:gmatch("%S+") do table.insert(args, x) end + return args +end + +local function trim(s) + return string.gsub(s, "^%s*(.-)%s*$", "%1") +end + +local function first_word(txt) + local _strt, _end = string.find(txt,".%S+") + if (_strt and _end) then + return trim(string.lower(string.sub(txt,_strt, _end))),trim(string.sub(txt,_end+1)) + end + return "","" +end + +function check_and_set_help(caller,txt,owner,msg) + local wrd,rest = first_word(txt) + + if (wrd == "") then + owner:SendOutput("For usage type %s help",caller) + return + end + + if (string.find(wrd,"help")) then + owner:SendOutput("%s %s",caller,msg) + return + end + return wrd,rest +end + +-- HELP +function cmd.help(me,txt,owner,p) + local wrd,rest = first_word(txt) + local page = tonumber(txt) or 1 + local sz = owner.console_size + + if not (sz) or (sz == 0) then return "help := error" end + + local a = {} + for n in pairs(cmd) do + if (n ~= "help") then + table.insert(a, n) + end + end + table.sort(a) + + local page_count = math.ceil(#a/sz) + if (page > page_count) then + return "help:= Invaild page" + end + + owner:SendOutput("help := Command List [%s of %s]",page,page_count) + owner:SendOutputList(a,(page*sz)-sz,sz-1) +end + +-- ECHO +function cmd.echo(me,txt,owner,p) + return txt +end + +function cmd.char_desc(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[checkall]") + if not (wrd) then return end + if not (wrd == "checkall") then return "char_desc:= try char_desc checkall" end + + local f = { "character_desc_agroprom", + "character_desc_bar", + "character_desc_darkvalley", + "character_desc_deadcity", + "character_desc_escape", + "character_desc_general", + "character_desc_jupiter", + "character_desc_katacomb", + "character_desc_marsh", + "character_desc_military", + "character_desc_pripyat", + "character_desc_red_forest", + "character_desc_simulation", + "character_desc_underpass", + "character_desc_yantar", + "character_desc_zaton" + } + + local tags = { ["name"] = false, + ["icon"] = false, + ["map_icon"] = false, + --["bio"] = false, + ["class"] = false, + ["community"] = false, + ["terrain_sect"] = false, + ["money"] = false, + ["rank"] = false, + ["reputation"] = false, + ["visual"] = false, + --["snd_config"] = false, + ["supplies"] = false, + --["actor_dialog"] = false + } + + local xml,node,id,char_node,p + for i=1,#f do + xml = utils_xml.XmlParser:loadFile(getFS():update_path('$game_config$', '').."gameplay\\"..f[i]..".xml") + if (xml) then + for index,node in pairs(xml.ChildNodes) do + if (type(node) == "table") then + + -- Check if all specific_character nodes for mandatory child nodes + for k,child in pairs(node.ChildNodes) do + if (type(child) == "table") then + if (tags[child.Name] == false) then + tags[child.Name] = true + end + end + end + + -- Validate that all nodes by tag names exist + for k,v in pairs(tags) do + if (v == false) then + owner:SendOutput("char_desc:= specific character id=%s missing a mandatory node by name %s",node.Attributes.id,k) + end + -- reset for next loop + tags[k] = false + end + + end + end + else + owner:SendOutput("char_desc:= no xml by name %s",f[i]) + end + end + return "char_desc:= scanning complete" +end + +-- print +function cmd.print(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[_G]") + if not (wrd) then return end + + if (wrd == "_G" or wrd == "_g") then + local list = { f={}, t={}, u={}, b={}, n={}, s={}, unknown={} } + + local msg = "" + for k,v in pairs(_G) do + if (type(v) == "userdata") then + table.insert(list.u,k) + elseif (type(v) == "function") then + table.insert(list.f,k) + elseif (type(v) == "boolean") then + table.insert(list.b,k) + elseif (type(v) == "number") then + table.insert(list.n,k) + elseif (type(v) == "string") then + table.insert(list.s,k) + elseif (type(v) == "table") then + table.insert(list.t,k) + else + table.insert(list.unknown,k) + end + end + + for k,v in pairs(list) do + table.sort(v) + end + + msg = msg .. "\n\nNUMBER" .. "\n" + for i=1,#list.n do + msg = msg .. list.n[i] .. " = " .. tostring(_G[list.n[i]]) .. "\n" + end + + msg = msg .. "\n\nBOOLEAN" .. "\n" + for i=1,#list.b do + msg = msg .. list.b[i] .. " = " .. tostring(_G[list.b[i]]) .. "\n" + end + + msg = msg .. "\n\nSTRING" .. "\n" + for i=1,#list.s do + msg = msg .. list.s[i] .. " = " .. _G[list.s[i]] .. "\n" + end + + msg = msg .. "\n\nTABLE" .. "\n" + for i=1,#list.t do + msg = msg .. list.t[i] .. "\n" + end + + msg = msg .. "\n\nFUNCTION" .. "\n" + for i=1,#list.f do + msg = msg .. list.f[i] .. "\n" + end + + msg = msg .. "\n\nUSERDATA" .. "\n" + for i=1,#list.u do + msg = msg .. list.u[i] .. "\n" + end + + msg = msg .. "\n\nUNKNOWN" .. "\n" + for i=1,#list.unknown do + msg = msg .. list.unknown[i] .. "\n" + end + + local file = io.open("debug_print.txt","w") + file:write(msg) + file:close() + return "print:= output sent to debug_print.txt" + end + + return "print:= incorrect subcommands. Type 'print help'" +end + +-- reload_system_ini and language +function cmd.reload_system_ini(me,txt,owner,p) + reload_ini_sys() + game.reload_language() + + return "reload_system_ini:= success" +end + +-- Relations +function cmd.relations(me,txt,owner,p) + owner:SendOutput("Stalker = %s",relation_registry.community_goodwill("stalker", 0)) + owner:SendOutput("Bandit = %s",relation_registry.community_goodwill("bandit", 0)) + owner:SendOutput("Mercenary = %s",relation_registry.community_goodwill("killer", 0)) + owner:SendOutput("CSky = %s",relation_registry.community_goodwill("csky", 0)) + owner:SendOutput("Freedom = %s",relation_registry.community_goodwill("freedom", 0)) + owner:SendOutput("Duty = %s",relation_registry.community_goodwill("dolg", 0)) + owner:SendOutput("Army = %s",relation_registry.community_goodwill("army", 0)) + owner:SendOutput("Monolith = %s",relation_registry.community_goodwill("monolith", 0)) + owner:SendOutput("Ecolog = %s",relation_registry.community_goodwill("ecolog", 0)) + owner:SendOutput("Renegade = %s",relation_registry.community_goodwill("renegade", 0)) + owner:SendOutput("Sin = %s",relation_registry.community_goodwill("greh", 0)) + owner:SendOutput("ISG = %s",relation_registry.community_goodwill("isg", 0)) + return "relations:= command finished" +end + +-- Lua Count All +function cmd.lua_countall(me,txt,owner,p) + local seen = {} + local function count_all(f) + local function count_table(t) + if seen[t] then return end + f(t) + seen[t] = true + for k,v in pairs(t) do + if type(v) == "table" then + count_table(v) + else + f(v) + end + end + end + count_table(_G) + end + + local count = 0 + local function track(o) + if (type(o) == "function") then + count = count + 1 + end + end + + count_all(track) + + output = strformat("%s functions. %s bytes used",count,count*20) + + return "lua_countall:= " .. output +end + +-- Community +function cmd.community(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"") + if not (wrd) then return end + + local comm_list = utils_obj.get_communities_list() + for i=1,#comm_list do + if (wrd == comm_list[i]) then + set_actor_true_community(wrd) + return "community:= actor community successfully changed to "..wrd + end + end + return "community:= invalid community" +end +-- SPAWN +--[[ +example usage: + +spawn s:stalker id:%s amt:5 $AC_ID -- Spawns 5 stalkers at actor's position + +spawn s:wpn_pm id:5424 -- Spawns Pistol on ID's position + +spawn s:wpn_pm id:$AC_ID$ + +spawn s:wpn_pm pos:244,355,123 lvid:11344 gvid:12 -- Spawns pistol at exact location + +--]] +function cmd.spawn(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"s:
id: amt: [ pos: lvid: gvid: ]") + if not (wrd) then return end + + if (wrd == "reset") then + owner.spawn_pos = nil + owner.spawn_lvid = nil + owner.spawn_gvid = nil + return "spawn:= position reset to always actor" + end + + if (wrd == "items") then + if not (ini_sys:section_exist("inv_backpack")) then + return "spawn:= requires inv_backpack from items_minimod" + end + local stash = alife_create("inv_backpack",db.actor:position(),db.actor:level_vertex_id(),db.actor:game_vertex_id()) + local itm + if not (se_item.registered_items) then + return "spawn:= registered items table needs uncommented in se_item.script" + end + for k,v in pairs(se_item.registered_items) do + alife_create_item(k, stash) + end + return "spawn:= all registered items in game spawned in a stash near you" + end + + local sec + if (string.find(txt,"s:")) then + for s in string.gmatch(txt,"s:(.%S+)") do + sec = s + end + end + + if not (sec) or not (ini_sys:section_exist(sec)) then + return "spawn:= Section %s does not exist.",sec + end + + local pos,lvid,gvid,id + + if (string.find(txt,"id:")) then + for s in string.gmatch(txt,"id:(%d+)") do + id = tonumber(s) + end + + local obj = level.object_by_id(id) + if (obj) then + pos = obj:position() + lvid = obj:level_vertex_id() + gvid = obj:game_vertex_id() + end + else + if (string.find(txt,"pos:")) then + for s in string.gmatch(txt,"pos:([-]?%d+[.]?%d*,[-]?%d+[.]?%d*,[-]?%d+[.]?%d*)") do + local p = str_explode(s,",") + pos = vector():set(tonumber(p[1]),tonumber(p[2]),tonumber(p[3])) + end + + if (string.find(txt,"lvid:")) then + for s in string.gmatch(txt,"lvid:(%w+)") do + lvid = tonumber(s) + end + else + return "spawn:= Must specify level_vertex_id along with position." + end + + if (string.find(txt,"gvid:")) then + for s in string.gmatch(txt,"gvid:(%w+)") do + gvid = tonumber(s) + end + else + return "spawn:= Must specify game_vertex_id along with position." + end + else + return "spawn:= Must specify id or position. Type spawn help" + end + end + + local amt = 1 + if (string.find(txt,"amt:")) then + for s in string.gmatch(txt,"amt:(%d+)") do + amt = tonumber(s) + end + end + + if (pos) and (lvid) and (gvid) then + for i=1,amt do + local obj = alife_create_item(sec, {pos,lvid,gvid,id}) + end + + if (amt > 1) then + return "Spawn:= Objects created" + end + + if not (obj) then + return "spawn:= Object nil" + end + return "spawn:= Object %s created as ID %s.",obj:name(),obj.id + end + + local p = string.format("%0.2f,%0.2f,%0.2f",pos.x,pos.y,pos.z) + return "spawn:= Error pos=%s lvid=%s gvid=%s",p,lvid,gvid +end + +-- collectgarbage +function cmd.collectgarbage(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[full|count|step]") + if not (wrd) then return end + + if (wrd == "full") then + local before = collectgarbage("count") + collectgarbage() + local after = collectgarbage("count") + return "collectgarbage:= |before|=>"..before.." |after|=> "..after + elseif (wrd == "count") then + local count = collectgarbage("count") + return "collectgarbage:= "..count + elseif (wrd == "step") then + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + local amt = wrd and wrd ~= "" and tonumber(wrd) or 0 + if (amt) then + xrs_debug_tools.STEP = amt + end + end + else + return "collectgarbage:= wrong argument passed." + end +end + +function cmd.attach(me,txt,owner,p) + local wpn = db.actor:active_item() + if (wpn) then + local firearm = IsWeapon(wpn) + if (firearm) then + if (wpn.weapon_addon_attach) then + local addons = { + "wpn_addon_scope", + "wpn_addon_scope_x2.7", + "wpn_addon_scope_detector", + "wpn_addon_scope_night", + "wpn_addon_scope_susat", + "wpn_addon_scope_susat_x1.6", + "wpn_addon_scope_susat_custom", + "wpn_addon_scope_susat_dusk", + "wpn_addon_scope_susat_night", + "wpn_addon_silencer", + "wpn_addon_grenade_launcher", + "wpn_addon_grenade_launcher_m203" + } + local addon + local attached = false + for k,v in pairs(addons) do + addon = db.actor:object(k) + if (addon) then + wpn:weapon_addon_attach(addon) + attached = true + end + end + + if (attached) then + return "attach:= addon attached" + end + return "attach:= no addons attached" + else + return "attach:= weapon does not have weapon_addon_attach method" + end + end + end + return "attach:= addon was not attached for active weapon" +end + +function cmd.detach(me,txt,owner,p) + local wpn = db.actor:active_item() + if (wpn) then + local firearm = IsWeapon(wpn) + if (firearm and wpn.weapon_addon_detach) then + local addons = { + "wpn_addon_scope", + "wpn_addon_scope_x2.7", + "wpn_addon_scope_detector", + "wpn_addon_scope_night", + "wpn_addon_scope_susat", + "wpn_addon_scope_susat_x1.6", + "wpn_addon_scope_susat_custom", + "wpn_addon_scope_susat_dusk", + "wpn_addon_scope_susat_night", + "wpn_addon_silencer", + "wpn_addon_grenade_launcher", + "wpn_addon_grenade_launcher_m203" + } + local addon + for k,v in pairs(addons) do + wpn:weapon_addon_detach(k) + end + + return "detach:= check item" + end + end + return "detach:= addon was not detached from active weapon" +end + +local function parse_waypoint(pathname, wpflags, wpname, owner, tRet) + + local rslt = {} + + rslt.flags = wpflags + + local at + if string.find(wpname, "|", at, true) == nil then + return rslt + end + + --[[ + file = io.open("axr_debug_log.txt","a+") + if (file) then + file:write(strformat("[%s] %s", pathname, wpname).."\n") + file:close() + end + --]] + + local par_num + local fld + local val + + par_num = 1 + for param in string.gmatch(wpname, "([%w%+~_\\%=%{%}%s%!%-%,%*]+)|*") do + if par_num == 1 then + -- continue + else + if param == "" then + owner:SendOutput("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) + printf("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) + else + local t_pos = string.find(param, "=", 1, true) + if (t_pos) then + fld = string.sub(param, 1,t_pos - 1) + if not (fld and fld ~= "") then + tRet[#tRet+1] = strformat("path '%s': waypoint '%s': syntax error while parsing the param '%s': no field specified",pathname, wpname, param) + printf("path '%s': waypoint '%s': syntax error while parsing the param '%s': no field specified",pathname, wpname, param) + else + val = string.sub(param, t_pos + 1) + if not (val and val ~= "") then + val = "true" + end + + if fld == "a" then + rslt[fld] = xr_logic.parse_condlist(db.actor, "waypoint_data", "anim_state", val) + + if not (state_lib.states[val]) then + tRet[#tRet+1] = strformat("path '%s': waypoint '%s': not a valid state_lib anim %s", pathname, wpname,val) + printf("path '%s': waypoint '%s': not a valid state_lib anim %s", pathname, wpname,val) + end + else + rslt[fld] = val + end + end + else + tRet[#tRet+1] = strformat("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) + printf("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) + end + end + end + par_num = par_num + 1 + end + return rslt +end + +function cmd.waypoint(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[checkall | checklevel | checkindex]") + if not (wrd) then return end + + if not (wrd == "checkall" or wrd == "checklevel" or wrd == "checkindex") then + return "waypoint:= invalid argument. Type waypoint help for a list of subcommands" + end + + local npc = xrs_debug_tools.get_debug_npc() + if (npc == nil and wrd == "checklevel" and npc:id() ~= 0) then + return "waypoint:= need NPC to validate waypoints, no nearest stalker found." + end + + local path_jobs = {"surge","collector","walker","patrol","guard","sniper","camper","sleep"} + + local smart,gname,name,ptr,cnt,i,vec + local b = {} + local last_index + + local sim = alife() + local actor_level = wrd == "checklevel" and sim:level_name(game_graph():vertex(sim:actor().m_game_vertex_id):level_id()) or "nil" + + local checked = false + local gg = game_graph() + + local t = {} + for id=1,sim:max_id() - 1 do + smart = sim:object(id) + if (smart and smart:clsid() == clsid.smart_terrain) then + gname = smart:name() + if (wrd == "checkindex") then + checked = true + for index,job in ipairs(path_jobs) do + b=empty_table(b) + last_index = 0 + for add=1,20 do + name = job == "sleep" and gname.."_"..job.."_"..add or gname.."_"..job.."_"..add.."_walk" + ptr = patrol(name) + if (ptr) then + b[add] = name + last_index = add + end + end + for add=1,last_index do + if b[add] == nil then + t[#t+1] = strformat("path %s does not exist!",job == "sleep" and gname.."_"..job.."_"..add or gname.."_"..job.."_"..add.."_walk") + end + end + end + elseif (wrd == "checkall" or self.is_on_actor_level) then + for index,job in ipairs(path_jobs) do + i = 1 + while level.patrol_path_exists(job == "sleep" and gname.."_"..job.."_"..i or gname.."_"..job.."_"..i.."_walk") do + name = job == "sleep" and gname.."_"..job.."_"..i or gname.."_"..job.."_"..i.."_walk" + ptr = patrol(name) + if (ptr) then + checked = true + cnt = ptr:count() + for n=0,cnt-1 do + + if not (gg:valid_vertex_id(ptr:game_vertex_id(n))) then + t[#t+1] = strformat("path %s p%s:%s does not have valid game_vertex_id",name,n,ptr:name(n)) + printf("path %s p%s:%s does not have valid game_vertex_id",name,n,ptr:name(n)) + end + + if (actor_level ~= "nil") then + if not (utils_obj.accessible(npc,ptr:level_vertex_id(n))) then + t[#t+1] = strformat("path %s p%s:%s does not have valid level_vertex_id",name,n,ptr:name(n)) + printf("path %s p%s:%s does not have valid level_vertex_id",name,n,ptr:name(n)) + end + else + if (ptr:level_vertex_id(n) >= 4294967295) then + t[#t+1] = strformat("path %s p%s:%s does not have valid level_vertex_id",name,n,ptr:name(n)) + printf("path %s p%s:%s does not have valid level_vertex_id",name,n,ptr:name(n)) + end + end + parse_waypoint(name,ptr:flags(n),ptr:name(n),owner,t) + end + end + -- Validate look waypoint also + name = gname.."_"..job.."_"..i.."_look" + if (level.patrol_path_exists(name)) then + ptr = patrol(name) + if (ptr) then + checked = true + cnt = ptr:count() + for n=0,cnt-1 do + parse_waypoint(name,ptr:flags(n),ptr:name(n),owner,t) + end + end + end + i = i + 1 + end + end + end + end + end + + if (checked) then + if (#t > 0) then + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + + local sz = owner.console_size + + if not (sz) or (sz == 0) then return "waypoint:= error" end + + local page_count = math.ceil(#t/sz) + + if (page > page_count) then + return "waypoint:= Invalid page" + end + + table.sort(t) + + owner:SendOutput("waypoint:= Bad Waypoints [%s of %s]",page,page_count) + owner:SendOutputList(t,(page*sz)-sz,sz-1) + return + end + return "waypoint: successfully checked all gulag job paths." + end + return "waypoint: failed, no checking done." +end + + + +-- SURGE +function cmd.surge(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[start|stop]") + if not (wrd) then return end + + local sm = surge_manager.SurgeManager + if not (sm) then return "No surge manager!" end + + if (wrd == "start") then + sm:start(true) + return "surge:= started" + elseif (wrd == "stop") then + sm:end_surge(true) + return "surge:= ended" + else + return "surge:= wrong argument passed." + end +end + +-- PsiStorm +function cmd.psi_storm(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[start|stop]") + if not (wrd) then return end + + local sm = psi_storm_manager and psi_storm_manager.PsiStormManager + if not (sm) then return "No psi_storm manager!" end + + if (wrd == "start") then + sm:start(true) + return "psi_storm:= started" + elseif (wrd == "stop") then + sm:finish(true) + return "psi_storm:= ended" + else + return "psi_storm:= wrong argument passed." + end +end + + +-- Fallout +function cmd.fallout(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[start|stop]") + if not (wrd) then return end + + local sm = fallout_manager and fallout_manager.get_fallout_manager() + if not (sm) then return "No fallout manager!" end + + if (wrd == "start") then + sm:start(true) + return "fallout:= started" + elseif (wrd == "stop") then + sm:end_surge(true) + return "fallout:= ended" + else + return "fallout:= wrong argument passed." + end +end + + +-- Find +function cmd.find(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"") + if not (wrd) then return end + + if (p[1]) then + owner:FindNearest(p[1]) + else + owner:FindNearest(wrd) + end +end + +-- Execute +function cmd.execute(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"") + if not (wrd) then return end + + local f,err = assert(loadstring(wrd)) + if (f) then + f() + else + return err + end +end + +-- Alife + +function cmd.alife(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{switch}") + if not (wrd) then return end + + if (string.find(wrd,"switch")) then + --[[ + if (string.find(txt,"set:")) then + local d + for s in string.gmatch(txt,"set:(%d+)") do + d = tonumber(s) + end + if not (d) then + return "alife := Invalid value for switch distance set:%s",d + end + local old = alife():switch_distance() + alife():switch_distance(d) + return "alife := switch distance changed from %s to %s",old,d + elseif (string.find(txt,"get")) then + local sd = alife():switch_distance() + return "alife := switch distance is %s",sd + else + return "alife switch [get|set:]" + end + --]] + local sd = alife():switch_distance() + return "alife:= switch distance is %s",sd + end +end + +-- Clear +function cmd.clear(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"(Clears console)") + if (string.find(txt,"help")) then + return + end + + for i=1,owner.console_size do + owner.txt_console[i]:SetText("") + end +end + +-- Squad +function cmd.squad(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{assign}") + if not (wrd) then return end + + if (string.find(wrd,"teleport")) then + local id,squad,a_id,target + if (string.find(txt,"id:")) then + for s in string.gmatch(txt,"id:(%d+)") do + id = tonumber(s) + end + + squad = alife_object(id) + if not (squad) then + return "squad:= improper id used. id:%s",id + end + end + + if (string.find(txt,"target:")) then + for s in string.gmatch(txt,"target:(%d+)") do + a_id = tonumber(s) + end + + target = alife_object(a_id) + if not (target) then + return "squad:= target by id %s does not exist.",a_id + end + end + + if (squad and target) then + TeleportSquad(squad,target.position,target.m_level_vertex_id,target.m_game_vertex_id) + return "squad := %s teleported to %s.",squad:name(),target:name() + end + elseif (string.find(wrd,"assign")) then + local id,squad,a_id,target + if (string.find(txt,"id:")) then + for s in string.gmatch(txt,"id:(%d+)") do + id = tonumber(s) + end + + squad = alife_object(id) + if not (squad) then + return "squad:= improper id used. id:%s",id + end + end + + if (string.find(txt,"target:")) then + for s in string.gmatch(txt,"target:(%d+)") do + a_id = tonumber(s) + end + + target = alife_object(a_id) + if not (target) then + return "squad:= assign target does not exist. assign:%s",a_id + end + end + + if (squad and target) then + utils_obj.assign_squad_to_smart(squad.id,target.id) + return "squad := %s assigned to %s.",squad:name(),target:name() + end + end + + return "squad:= squad assign id: target:" +end + +-- Console +function cmd.console(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{relay}") + if not (wrd) then return end + + if (string.find(wrd,"relay")) then + if (owner.console_relay) then + owner.console_relay = false + return "Toggled off relay to game console" + else + owner.console_relay = true + end + return "Toggled on relay to game console" + end +end + +-- Teleport +function cmd.teleport(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"who: [ to: | pos: | $code$ | cam ]") + if not (wrd) then return end + + local who,to_pos,to_obj + + if (string.find(txt,"who:")) then + local who_id + for s in string.gmatch(txt,"who:(%w+)") do + who_id = tonumber(s) + end + + who = who_id and alife_object(who_id) + if not (who) then + return "teleport := Wrong argument given or object doesn't exist. who:<%s>",who_id + end + end + + if (string.find(txt,"to:")) then + local id + for s in string.gmatch(txt,"to:(%w+)") do + id = tonumber(s) + end + + to_obj = id and alife_object(id) + if not (to_obj) then + return "teleport := Wrong arugment given or object doesn't exist. id:%s",id + end + + to_pos = to_obj.position + else + local pos = p[1] + + if (string.find(txt,"cam")) then + pos = xrs_debug_tools.LastCameraPos + end + + if (string.find(txt,"pos:")) then + for s in string.gmatch(txt,"pos:([-]?%d+[.]?%d*,[-]?%d+[.]?%d*,[-]?%d+[.]?%d*)") do + local t = str_explode(s,",") + pos = vector():set(tonumber(t[1]),tonumber(t[2]),tonumber(t[3])) + end + end + + if not (pos) then + return "teleport := wrong format for position. pos:%s must be pos:x,y,z" + end + + to_pos = pos + end + + + if (who and to_pos) then + local success = false + if (who.id == AC_ID) then + db.actor:set_actor_position(to_pos) + success = true + elseif (to_obj) then + local squad + if (IsStalker(who) or IsMonster(who)) then + local object = level.object_by_id(who.id) + if (object) then + object:set_npc_position(to_pos) + success = true + else + squad = get_object_squad(who) + end + elseif (who:clsid() == clsid.online_offline_group_s) then + squad = who + end + + if (squad and alife().teleport_object) then + TeleportSquad(squad,db.actor:position(),db.actor:level_vertex_id(),db.actor:game_vertex_id()) + success = true + end + end + + if (success) then + local pos = string.format("%0.3f, %0.3f, %0.3f",to_pos.x,to_pos.y,to_pos.z) + return "teleport:= %s teleported to pos:%s",who:name(),pos + end + end + + return "teleport:= failed to teleport %s",who and who:name() +end + +-- Wound +function cmd.wound(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"id:") + if not (wrd) then return end + + local id + local vo + if (string.find(txt,"id:")) then + for s in string.gmatch(txt,"id:(%d+)") do + id = tonumber(s) + end + + vo = level.object_by_id(id) + if not (vo) then + return "wound:= Wrong arugument given or object doesn't exist. id:<%s>",id + end + end + + if (vo) then + vo:set_health_ex(0.05) + --[[ + local h = hit() + h.power = vo.health+0.05 + h.direction = vector() + h.bone = "bip01_spine" + h.draftsman = vo + h.impulse = 6000 + h.type = hit.wound + vo:hit(h) + --]] + return "wound:= %s has been wounded.",vo:name() + end +end + +local var_list = { ["actor"] = db.actor} +function get_var_list() + return var_list +end + +-- Var +function cmd.var(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner," $code$") + if not (wrd) then return end + + if (wrd == "clear") then + for k,v in pairs(var_list) do + var_list[k] = nil + end + return "var:= All vars cleared" + end + + if (wrd == "list") then + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + local sz = owner.console_size-1 + + if not (sz) or (sz == 0) then return "var:= error" end + + local list_sz = 0 + for k,v in pairs(var_list) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "var:= Invaild page" + end + + owner:SendOutput("var:= Variable List [%s of %s]",page,page_count) + + local a = {} + for n in pairs(var_list) do table.insert(a, n) end + table.sort(a) + + local ind, out, k + for i=1,sz do + ind = (page*sz - sz) + i + k = ind > 0 and a[ind] + out = " " + + if (k) then + local v = var_list[k] + if ( type(v) == "userdata" ) then + out = string.format("%s = %s",k,"userdata") + if (v.name and type(v.name) == "function" ) then + out = string.format("%s = userdata [%s]",k,v:name()) + elseif (v.position) then + local pos + if (type(v.position) == "function") then + pos = v:position() + out = string.format("%s = userdata [%s,%s,%s]",k,pos.x,pos.y,pos.z) + else + pos = v.position + out = string.format("%s = userdata [%s,%s,%s]",k,pos.x,pos.y,pos.z) + end + elseif (v.x and v.y and v.z) then + out = string.format("%s = userdata [%s,%s,%s]",k,v.x,v.y,v.z) + end + elseif ( type(v) == "number" or type(v) == "string" or type(v) == "table" or type(v) == "function") then + out = string.format("%s = %s",k,v) + end + end + owner:SendOutput(out) + end + return + end + + if (wrd) then + var_list[wrd] = p[1] + return "var:= variable %s set.",wrd + end +end + +-- Vector +function cmd.vector(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{add|sub}") + if not (wrd) then return end + + if (wrd == "add") then + local inc = {} + + inc.v = match_or_var(txt,"v:",".%S+",p[1]) + + if not (inc.v) then + return "vector:= Wrong arugument given to inc -> v:" + end + + inc.x = tonumber( match_or_var(txt,"x:",".%S+",p[2]) ) or 0 + inc.y = tonumber( match_or_var(txt,"y:",".%S+",p[3]) ) or 0 + inc.z = tonumber( match_or_var(txt,"z:",".%S+",p[4]) ) or 0 + + inc.v = inc.v:add( vector():set(inc.x,inc.y,inc.z) ) + return "vector:= vector updated to vector" + end + + if (wrd == "sub") then + local v = {} + + v[1] = match_or_var(txt,"1:",".%S+",p[1]) + v[2] = match_or_var(txt,"2:",".%S+",p[2]) + + if (v[1] and v[2]) then + local p1,p2 + if ( type(v[1]) == "string" ) then + p1 = level.object_by_id( tonumber(p1) ):position() + else + p1 = v[1] + end + + if ( type(v[2]) == "string" ) then + p2 = level.object_by_id( tonumber(p2) ):position() + else + p2 = v[2] + end + + local pr = p1 and p2 and p1:sub(p2) + if (pr) then + return "vector:= %s,%s,%s",pr.x,pr.y,pr.z + end + end + return "vector:= Wrong arugument given or object(s) do not exist." + end +end + +-- Offset +function cmd.offset(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{get}") + if not (wrd) then return end + + if (wrd == "get") then + + if (string.find(txt,"help")) then + return "offset get [ 1: 2: | 1:<$var$> 2:<$var$> ]" + end + + local v = {} + v[1] = match_or_var(txt,"1:",".%S+",p[1]) + v[2] = match_or_var(txt,"2:",".%S+",p[2]) + + if (v[1] and v[2]) then + local p1,p2 + if ( type(v[1]) == "string" ) then + p1 = level.object_by_id( tonumber(p1) ) + else + p1 = v[1] + end + + if ( type(v[2]) == "string" ) then + p2 = level.object_by_id( tonumber(p2) ) + else + p2 = v[2] + end + + local pr = get_position_offset(v[1],v[2]) + if (pr) then + return "offset:= position(%s,%s,%s)",pr.x,pr.y,pr.z + end + else + return "offset:= type offset get help" + end + end + +end + +function cmd.object(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"{count}") + if not (wrd) then return end + + if (wrd == "count") then + local count = 0 + local a = alife() + for i=1, a:max_id() - 1 do + local se_obj = a:object(i) + if (se_obj) then + count = count + 1 + end + end + return "object:= there are %s ids in use.",count + end + return "object:= try object count " +end + +function get_position_offset(npc,o) + local sec = o and type(o.section) == "function" and o:section() + if not (sec) then + return + end + + local attach_bone_name = ini_sys:r_string_ex(sec,"attach_bone_name") + if not (attach_bone_name) then + return + end + local pos1 = utils_obj.safe_bone_pos(npc,attach_bone_name)--npc:bone_position(attach_bone_name) + local pos2 = o:center() + return pos1:sub(pos2) +end + +function match_or_var(txt,token,pat,var) + if (var and var_list[var]) then + return var_list[var] + end + + if (string.find(txt,token)) then + local v + for s in string.gmatch(txt,token.."("..pat..")") do + v = s + end + return v + end +end + +-- Hud +function cmd.hud(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[on|off]") + if not (wrd) then return end + + if (wrd == "on") then + save_var(db.actor,"disable_debug_draw",false) + return "hud:= debug hud enabled" + end + + if (wrd == "off") then + save_var(db.actor,"disable_debug_draw",true) + return "hud:= debug hud disabled" + end + + return "hud:= type hud help for list of sub commands" +end + +-- God +function cmd.god(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[on|off]") + if not (wrd) then return end + + if (wrd == "on") then + xrs_debug_tools.debug_god = true + --exec_console_cmd("g_god on") + return "god:= invincibility enabled" + end + + if (wrd == "off") then + xrs_debug_tools.debug_god = nil + --exec_console_cmd("g_god off") + return "god:= invincibility disabled" + end + + return "god:= type god help for list of sub commands" +end + +-- Invisible +function cmd.invisible(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[on|off]") + if not (wrd) then return end + + if (wrd == "on") then + xrs_debug_tools.debug_invis = true + return "invisible:= invisibility enabled" + end + + if (wrd == "off") then + xrs_debug_tools.debug_invis = nil + return "invisible:= invisibility disabled" + end + + return "invisible:= type invisible help for list of sub commands" +end + +function cmd.crow(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[on|off]") + if not (wrd) then return end + + if (wrd == "on") then + local se_crow = utils_obj.nearest_object("m_crow") + if (se_crow) then + xrs_debug_tools.crow_fun = se_crow.id + return "crow:= enabled" + end + return "crow:= no crows found" + end + + if (wrd == "off") then + xrs_debug_tools.crow_fun = nil + return "crow:= disabled" + end + + return "crow:= type crow help for list of sub commands" +end + +local heli_id +function cmd.heli(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[spawn|move]") + if not (wrd) then return end + + if (wrd == "spawn") then + local se_heli = utils_stpk.spawn_heli() + if (se_heli) then + heli_id = se_heli.id + return "heli:= Spawned at actor position" + end + return "heli:= Spawning failed" + end + + if (wrd == "move") then + local str + if (string.find(txt,"pos:")) then + for s in string.gmatch(txt,"pos:(.%S+)") do + str = s + end + end + + if (str and str ~= "") then + str = str_explode(str,",") + str.x = tonumber(str[1]) + str.y = tonumber(str[2]) + str.z = tonumber(str[3]) + end + + local obj = level.object_by_id(heli_id) + local heli = obj and obj:get_helicopter() + if (heli) then + local pos = str or db.actor:position() + heli:SetDestPosition(vector():set(pos.x,pos.y,pos.z)) + return "heli:= destination set to actor position" + end + return "heli:= move failed" + end + + if (wrd == "attack") then + + + + + end + return "heli:= type heli help for list of sub commands" +end + +-- weather +local weather_list +function cmd.weather(me,txt,owner,p) + local wrd,rest = check_and_set_help(me,txt,owner,"[list | ]") + if not (wrd) then return end + + if (wrd == "list") then + if not (weather_list) then + weather_list = {} + local f = getFS() + local flist = f:file_list_open("$game_weathers$",1) + local f_cnt = flist:Size() + + for it=0, f_cnt-1 do + local file = flist:GetAt(it) + table.insert(weather_list,file) + end + + flist:Free() + end + + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + + local sz = owner.console_size + + if not (sz) or (sz == 0) then return "weather:= error" end + + local list_sz = 0 + for k,v in pairs(weather_list) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "weather:= Invaild page" + end + + table.sort(weather_list) + + owner:SendOutput("weather:= Weathers List [%s of %s]",page,page_count) + owner:SendOutputList(weather_list,(page*sz)-sz,sz-1) + + return + elseif (wrd == "set") then + level.set_weather(rest,true) + return "weather:= weather set to " .. rest + + elseif (wrd == "reload") then + weather.reload() + return "weather:= reloaded weather configs" + end + return "weather: type weather help for a list of commands. Exclude .ltx when setting weather" +end + +-- time +function cmd.time(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[day,hour,minute]") + if not (wrd) then return end + + local t = str_explode(txt,",") + if (t) then + --set_current_time(tonumber(t[1]),tonumber(t[2]),tonumber(t[3])) + level.change_game_time( tonumber(t[1]),tonumber(t[2]),tonumber(t[3]) ) + level_weathers.get_weather_manager():forced_weather_change() + end +end + +-- money +function cmd.money(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"") + if not (wrd) then return end + local amt = tonumber(wrd) or 1000 + db.actor:give_money(amt) + return "money:= gave actor "..amt.." RU. You dirty cheater, I'm going to find you and cut your legs off!" +end + +-- luabind (not working) +function cmd.luabind(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[list | info]") + if not (wrd) then return end + + if (wrd == "list") then + if not (_G.class_names) then + return "luabind:= error no class_names()" + end + + local class_names = _G.class_names() + table.sort(class_names) + + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + + local sz = owner.console_size + + if not (sz) or (sz == 0) then return "luabind:= error" end + + local list_sz = 0 + for k,v in pairs(class_names) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "luabind:= Invaild page" + end + + owner:SendOutput("luabind:= class_names() [%s of %s]",page,page_count) + owner:SendOutputList(class_names,(page*sz)-sz,sz-1) + return + elseif (wrd == "info") then + if not (_G.class_info) then + return "luabind:= error no class_info()" + end + + if (string.find(txt,"help")) then + return "luabind select userdata:<$var$>" + end + + local v = {} + v[1] = match_or_var(txt,"userdata:",".%S+",p[1]) + if (v[1]) then + local c = class_info(v[1]) + if not (c) then + return "luabind:= error; no class info for userdata (not luabind class?)" + end + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + + local sz = owner.console_size + + if not (sz) or (sz == 0) then return "luabind:= error" end + + local list_sz = 0 + for k,v in pairs(class_names) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "luabind:= invaild page" + end + owner:SendOutput("luabind:= class=%s",c.name) + owner:SendOutputList(c.methods,(page*sz)-sz,sz-1) + return + else + return "luabind:= no class/userdata specified. (ex. luabind select userdata:$npc$)" + end + end + return "luabind:= type luabind help for a list of commands" +end + +-- level +function cmd.level(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner," | [list]") + if not (wrd) then return end + + if (wrd == "list") then + + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + local page = wrd ~= "" and tonumber(wrd) or 1 + + local levels = utils_data.collect_section(game_ini(),"level_maps_single") + + local sz = owner.console_size + local list_sz = 0 + for k,v in pairs(levels) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "level:= Invaild page" + end + + owner:SendOutput("level:= list of available levels by name [%s of %s]",page,page_count) + owner:SendOutputList(levels,(page*sz)-sz,sz-1) + + return + end + + local level_name = level.name() + if (JumpToLevel(wrd)) then + return "level:= level changer successfully created at your position from"..level_name.." to "..wrd.." (Spawning at first found gvid)" + end + + --[[ depreciated + local levels = { ["zaton_jupiter"] = {467.306884765625,55.5276184082031,12.9044094085693}, + ["zaton_pripyat"] = {467.206878662109,55.5276184082031,3.20440793037415}, + ["jupiter_zaton"] = {-7.12386798858643,14.484338760376,154.085464477539}, + ["jupiter_jupiter_underground"] = {460.005798339844,46.0688896179199,-295.460784912109}, + ["jupiter_pripyat"] = {-16.3238716125488,14.484338760376,142.785461425781}, + ["pripyat_zaton"] = {150.91047668457,22.3456954956055,-287.9228515625}, + ["pripyat_jupiter"] = {139.08171081543,22.3457069396973,-287.870025634766}, + ["pripyat_labx8"] = {-78.0360107421875,-7.04113388061523,100.684921264648}, + ["labx8_pripyat"] = {-78.062873840332,23.8045539855957,100.718994140625} + } + local level_name = level.name() + if (levels[level_name.."_"..wrd]) then + db.actor:set_actor_position(vector():set(levels[level_name.."_"..wrd][1],levels[level_name.."_"..wrd][2],levels[level_name.."_"..wrd][3])) + return "level:= teleporting player from "..level_name.." to "..wrd + end + + local sim = alife() + local gg = game_graph() + local level,data + local default_vertex + local function create_lc(gvid,vertex) + local se_obj = sim:create("level_changer",db.actor:position(),db.actor:level_vertex_id(),db.actor:game_vertex_id()) + if (se_obj) then + bind_stalker_ext.REMOVE_ME_LC = se_obj.id + local lvid = vertex:level_vertex_id() + local pos = vertex:level_point() + + data = utils_stpk.get_level_changer_data(se_obj) + if (data) then + data.dest_game_vertex_id = gvid + data.dest_level_vertex_id = lvid + data.dest_position = pos + data.dest_direction = VEC_ZERO + data.dest_level_name = level + data.silent_mode = 1 + + data.shapes[1] = {} + data.shapes[1].shtype = 0 + data.shapes[1].offset = VEC_ZERO + data.shapes[1].radius = 5 + + data.hint = "level_changer_invitation" + + utils_stpk.set_level_changer_data(data,se_obj) + end + end + end + + for gvid=0, 4836 do + if gg:valid_vertex_id(gvid) then + vertex = gg:vertex(gvid) + level = sim:level_name(vertex:level_id()) + if (level == wrd) then + if (not vertex.name or string.find(vertex:name(),"actor_spawn")) then + create_lc(gvid,vertex) + return "level:= level changer successfully created at your position from"..level_name.." to "..wrd.." (Actor spawn point found!)" + elseif not (default_vertex) then + default_vertex = vertex + end + end + else + break + end + gvid = gvid + 1 + end + + if (default_vertex) then + create_lc(gvid,vertex) + return "level:= level changer successfully created at your position from"..level_name.." to "..wrd.." (Spawning at first found gvid)" + end + --]] + + return "level:= there is no level changer between "..level_name.." and "..wrd +end + +function cmd.warpall(me,txt,owner,p) + local levels = utils_data.collect_section(game_ini(),"level_maps_single") + if (levels[1]) then + save_var(db.actor,"debug_warpall",1) + _G.JumpToLevel(levels[1]) + end + return "warpall := warping to every single level then flushing log" +end + +-- dev_debug +function cmd.dev_debug(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[on | off]") + if not (wrd) then return end + if (wrd == "on") then + _G.DEV_DEBUG = true + elseif (wrd == "off") then + _G.DEV_DEBUG = false + end + return "dev_debug:= type dev_debug help for list of sub commands." +end + +-- game_graph +function cmd.game_graph(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[show | near | checkall]") + if not (wrd) then return end + + if (wrd == "show") then + local gg = game_graph() + local vertex, level_name, lvid, pos, se_obj + local sim = alife() + local gvid = 0 + local lgvid = 0 + local level = level + while gg:valid_vertex_id(gvid) do + vertex = gg:vertex(gvid) + level_name = alife():level_name(vertex:level_id()) + lvid = vertex:level_vertex_id() + pos = vertex:level_point() + + se_obj = sim:create("medkit", pos, lvid, gvid) + level.map_add_object_spot_ser(se_obj.id, "treasure", level_name.." Gvid:"..gvid) + + if (level_name == level.name()) then + lgvid = lgvid + 1 + end + gvid = gvid + 1 + end + return "game_graph:= spots created. There are "..lgvid.." in "..level.name().." and "..gvid.." total." + elseif (wrd == "near") then + local gg = game_graph() + local vertex, level_name, lvid, pos + local near_lvid, near_dist, near_gvid, dist + local gvid = 0 + local lgvid = 0 + local level = level + while gg:valid_vertex_id(gvid) do + vertex = gg:vertex(gvid) + level_name = alife():level_name(vertex:level_id()) + if (level_name == level.name()) then + pos = vertex:level_point() + + dist = db.actor:position():distance_to_sqr(pos) + + if not (near_dist) then + near_gvid = gvid + near_dist = dist + end + + if (dist < near_dist) then + near_gvid = gvid + near_dist = dist + end + lgvid = lgvid + 1 + end + gvid = gvid + 1 + end + return "game_graph:= nearest gvid = " .. near_gvid .. " [dist = " .. near_dist .. "] There are "..lgvid.." in "..level.name().." and "..gvid.." total." + elseif (wrd == "checkall") then + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + local page = wrd ~= "" and tonumber(wrd) or 1 + + local issues = {} + local gg = game_graph() + local vertex + local sim = alife() + for i=1,sim:max_id() - 1 do + local se_obj = sim:object(i) + if (se_obj) and not (gg:valid_vertex_id(se_obj.m_game_vertex_id)) then + issues[#issues+1] = se_obj:name() + end + end + + if (#issues == 0) then + return "game_graph:= no issues found" + end + + local sz = owner.console_size + local page_count = math.ceil(#issues/sz) + + if (page > page_count) then + return "game_graph:= Invalid page" + end + + owner:SendOutput("game_graph:= list of objects with invalid game_vertex_ids [%s of %s]",page,page_count) + owner:SendOutputList(issues,(page*sz)-sz,sz-1) + return + end + return "game_graph:= type game_graph help for list of commands" +end + +-- item +function cmd.item(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[list ]") + if not (wrd) then return end + + if (wrd == "list") then + if not (se_item.registered_items) then + return "item:= error no registered_items list" + end + + wrd = "" + local _strt, _end = string.find(txt,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + local sz = owner.console_size - 1 + + if not (sz) or (sz == 0) then return "item:= error" end + + local list_sz = 0 + for k,v in pairs(se_item.registered_items) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + + if (page > page_count) then + return "item:= Invaild page" + end + + owner:SendOutput("item:= Registered Item List [%s of %s]",page,page_count) + + local a = {} + for n in pairs(se_item.registered_items) do table.insert(a, n) end + table.sort(a) + + local ind, out, k + for i=1,sz do + ind = (page*sz - sz) + i + k = ind > 0 and a[ind] + out = " " + if (k) then + out = string.format("%s [count: %s]",k,se_item.registered_items[k]) + end + owner:SendOutput(out) + end + return + end + return "item:= type item help for a list of subcommands" +end + +-- info +function cmd.info(me,txt,owner,p) + local wrd,rest = check_and_set_help(me,txt,owner,"[list | give | take | pas_b400 ]") + if not (wrd) then return end + + if (wrd == "give") then + give_info(rest) + return "info:= info given" + elseif (wrd == "take") then + disable_info(rest) + return "info:= disabled" + elseif (wrd == "clear") then + local function itr(id,info) + disable_info(info) + end + alife():iterate_info(0,itr) + return "info:= all info portions removed from actor" + elseif (wrd == "pas_b400") then + give_info("pas_b400_task_given") + give_info("pas_b400_underpass_start") + give_info("pas_b400_sr_elevator_1_passed") + give_info("pas_b400_canalisation_done") + give_info("pas_b400_done") + give_info("jup_b218_monolith_hired") + give_info("pas_b400_elevator_done") + give_info("pas_b400_sr_elevator_5") + give_info("pas_b400_track_done") + give_info("pas_b400_sr_track_4") + give_info("pas_b400_downstairs_done") + give_info("pas_b400_sr_downstairs_2") + give_info("pas_b400_tunnel_done") + give_info("pas_b400_snork_tunnel_4_attack") + give_info("pas_b400_snork_tunnel_2_jumped") + give_info("pas_b400_snork_tunnel_3_jumped") + give_info("pas_b400_sr_tunnel_5_in") + give_info("pas_b400_hall_done") + give_info("pas_b400_hall_monolith_squad_dead") + give_info("pas_b400_hall_monolith_snipers_dead") + give_info("pas_b400_way_done") + give_info("pas_b400_sr_way_2") + give_info("pas_b400_canalisation_snork_rl_spawned") + give_info("pas_b400_disabled_ui") + give_info("jup_a10_vano_agree_go_und") + give_info("leader_achievement_gained") + give_info("jup_b218_soldier_hired") + + if (xr_conditions.squad_exist(db.actor,db.actor,{"jup_b15_zulus_squad"})) then + xr_effects.create_squad_member(db.actor,db.actor,{"jup_b218_vano_in_suit","jup_b15_zulus_squad","jup_a6_jup_b218_squad_member_arrive_walk"}) + xr_effects.create_squad_member(db.actor,db.actor,{"pri_a15_sokolov_sci","jup_b15_zulus_squad","jup_a6_jup_b218_squad_member_arrive_walk"}) + xr_effects.create_squad_member(db.actor,db.actor,{"jup_b4_monolith_squad_leader_freedom_mon_skin","jup_b15_zulus_squad","jup_a6_jup_b218_squad_member_arrive_walk"}) + end + return "info:= jupiter underpass info given to actor" + end + + + if (wrd == "list") then + if not (xrs_debug_tools.actor_info) then + return "info:= actor_info empty" + end + + wrd = "" + local _strt, _end = string.find(rest,"%d+") + if (_strt and _end) then + wrd = string.sub(txt,_strt, _end) + wrd = string.lower(wrd) + end + + local page = wrd ~= "" and tonumber(wrd) or 1 + + local sz = owner.console_size-1 + + if not (sz) or (sz == 0) then return "info:= error" end + + local list_sz = 0 + for k,v in pairs(xrs_debug_tools.actor_info) do + list_sz = list_sz + 1 + end + + local page_count = math.ceil(list_sz/sz) + if (page > page_count) then + return list_sz > 0 and "info: nothing in list" or "info:= Invaild page" + end + + owner:SendOutput("info:= Infoportions [%s of %s]",page,page_count) + + local a = {} + for n in pairs(xrs_debug_tools.actor_info) do table.insert(a, n) end + table.sort(a) + + local ind, out, k + for i=1,sz do + ind = (page*sz - sz) + i + k = ind > 0 and a[ind] + owner:SendOutput(k or " ") + end + return + end + return "info:= type info help for a list of subcommands" +end + +-- cmd.inventory_owner +function cmd.inventory_owner(me,txt,owner,p) + local wrd,rest = check_and_set_help(me,txt,owner,"[removeall]") + if (wrd == "removeall") then + wrd = first_word(rest) + if (wrd == "help") then + return "inventory_owner:= try 'inventory_owner removeall id:'" + end + + local id + for s in string.gmatch(txt,"id:(%d+)") do + id = tonumber(s) + end + if not (id) then + return "inventory_owner:= type 'inventory_owner removeall help'" + end + + local sim = alife() + local count = 0 + for i=1,sim:max_id() - 1 do + local se_obj = sim:object(i) + if (se_obj and se_obj.parent_id == id) then + sim:release(se_obj,true) + count = count + 1 + end + end + if (count > 0) then + return "inventory_owner:= removed %s children from inventory_owner",count + end + return "inventory_owner:= failed to remove any children from inventory_owner" + end + return "inventory_owner:= type 'inventory_owner help' for a list of subcommands" +end + +-- cmd.antifreeze +function antifreeze_switch(id) + local sim = alife() + local se_obj = sim:object(id) + if not (se_obj) then + return true + end + if (se_obj:can_switch_offline()) then + utils_obj.switch_offline(se_obj.id) + elseif (se_obj:can_switch_online()) then + utils_obj.switch_online(se_obj.id) + end + return true +end +function cmd.antifreeze(me,txt,owner,p) + local wrd = check_and_set_help(me,txt,owner,"[npc [