--[[ 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, 65534 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() local time_xml = CScriptXmlInit() time_xml:ParseFile("actor_menu.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) -- xcvb boost time self.slot[i].xcvb_time = time_xml:InitTextWnd("quick_slot3_text", self) local dialog_pos = self.dialog:GetWndPos() -------------------- for k, ele in pairs(self.slot[i]) do ele:SetWndPos( vector2():set( x , 0 ) ) utils_xml.correct_ratio(ele) end -- xcvb boost time local icon_pos = self.slot[i].icon_f:GetWndPos() local text_x_pos = dialog_pos.x + icon_pos.x local text_y_pos = dialog_pos.y + icon_pos.y - 15 self.slot[i].xcvb_time:SetWndPos(vector2():set( text_x_pos, text_y_pos )) local icon_width = self.slot[i].icon_f:GetWidth() local self_height = self.slot[i].xcvb_time:GetHeight() self.slot[i].xcvb_time:SetWndSize(vector2():set(icon_width, self_height)) self.slot[i].xcvb_time:SetText("") --------------------- 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) -- xcvb boost time local show_time db.actor:cast_Actor():conditions():BoosterForEach( function(boost_typ, boost_time, boost_val) if name == boost_name[boost_typ] then show_time = math.ceil(boost_time) end end) if self.slot[i].xcvb_time then self.slot[i].xcvb_time:Show(show_time and true or false) self.slot[i].xcvb_time:SetText(show_time or "") end ----------------------- 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