-- map of exoskeletons to their power supplies --[[ key: id value: { current power supply: { name drain mult max } } ]] local exo_devices = {} -- cached properties of exo devices local cache = {} local warn = false local threshold = 25 local gc = game.translate_string function print_dbg(txt, ...) if exo_mcm.get_config("debug") then printf("arti_exo | %s | " .. txt, time_global(), ...) end end function init() local ini_exo_device = ini_file("items\\settings\\exo_devices.ltx") -- amper_factor = ini_device:r_float_ex("settings","amper_factor") or 1000 -- map psu props local n = ini_exo_device:line_count("dev_props") for i=0,n-1 do local result, sec, value = ini_exo_device:r_line_ex("dev_props",i,"","") local vals = str_explode(value, ",") cache[sec] = { ["name"] = sec, ["drain"] = tonumber(vals[1]) or 1, ["max"] = tonumber(vals[2]) or 100 } end n = ini_exo_device:line_count("spec_props") for i=0,n-1 do local result, sec, value = ini_exo_device:r_line_ex("spec_props",i,"","") cache[sec].ability = value end -- map exo drain base n = ini_exo_device:line_count("suit_drain") for i=0,n-1 do local result, sec, value = ini_exo_device:r_line_ex("suit_drain",i,"","") cache[sec] = tonumber(value) end end -- data management function get_data(id) if exo_devices[id] then return dup_table(exo_devices[id]) end end function set_data(id, data) -- validate the data first if not data then print_dbg("No data set for id %s, initializing", id) data = {} data.power = 0 end if data.supply then local name = data.supply.name if not cache[name] then data.supply = nil end end exo_devices[id] = data end function init_data(id) local data = {} data.power = 0 set_data(id, data) return data end -- helpers -- check if obj is exo outfit function is_exo(obj) local section = obj and obj:section() or false if section then return SYS_GetParam(0, section, "repair_type") == "outfit_exo" end return false end -- check if actor is wearing exo function is_wearing_exo() local outfit = db.actor:item_in_slot(7) return is_exo(outfit) end -- right-click options -- remove supply from exo function check_remove_psu(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return false end local id = obj:id() local data = get_data(id) if not data or is_empty(data) then return end if not data.supply then return end return "st_remove_psu" end function remove_psu(obj) local id = obj:id() local data = get_data(id) if not data or is_empty(data) then return end if not data.supply then return end if data.power > 100 then data.power = 100 end alife_create_item(data.supply.name, db.actor) data.supply = nil set_data(id, data) -- adjust_weight(obj) end function install_psu(suit, data, psu) if data.supply ~= nil then remove_psu(suit) end local sec_p = psu:section() if cache[sec_p] then data.supply = dup_table(cache[sec_p]) print_dbg("Installing PSU %s with drain %s, capacity %s", data.supply.name, data.supply.drain, data.supply.max) set_data(suit:id(), data) -- adjust_weight(suit) alife_release(psu) -- play some sound? xr_sound.set_sound_play(AC_ID,"inv_aam_open") end end -- Update the weight of an exoskeleton factoring in the weight of the PSU -- function adjust_weight(obj) -- print_dbg("begin adjust weight for %s", obj:section()) -- if not is_exo(obj) then return end -- local base_weight = SYS_GetParam(2, obj:section(), "inv_weight") -- local upgrades = utils_item.get_upgrades_installed(obj) -- local add_inv_weight = 0 -- for _, upgrade in pairs(upgrades) do -- local section = ini_sys:r_string_ex(upgrade, "section") -- local weight_adj = ini_sys:r_string_ex(section,"inv_weight") -- if weight_adj then -- local op = string.sub(weight_adj, 1, 1) -- local val = tonumber(string.sub(weight_adj, 2)) -- add_inv_weight = op == "+" and add_inv_weight + val or add_inv_weight - val -- end -- --printf(add_inv_weight) -- end -- local data = exo_devices[obj:id()] -- base_weight = base_weight + add_inv_weight -- if data and data.supply and data.supply.name then -- base_weight = base_weight + SYS_GetParam(2, data.supply.name, "inv_weight") -- end -- print_dbg("set weight of %s to %s", obj:section(), base_weight) -- obj:set_weight(base_weight) -- end -- charge exo with battery function menu_battery(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return false end local suit = db.actor:item_in_slot(7) if not is_exo(suit) then return end local id = suit:id() local data = get_data(id) if not data then data = init_data(id) end local max_power = data.supply and data.supply.max or 100 if data.power < max_power then return "st_charge_exo" end return false end function func_battery(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return end local suit = db.actor:item_in_slot(7) if not is_exo(suit) then return end local id = suit:id() local data = get_data(id) if not data then data = init_data(id) end charge_exo(obj, suit, data) end -- install psu function check_install_psu(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return end local suit = db.actor:item_in_slot(7) if not is_exo(suit) then return false end local id = suit:id() local data = get_data(id) if not data then data = init_data(id) end return "st_install_psu" end function func_install_psu(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return end local suit = db.actor:item_in_slot(7) if not is_exo(suit) then return end local data = get_data(suit:id()) if not data then data = init_data(suit:id()) end install_psu(suit, data, obj) end -- use the battery to charge the suit function charge_exo(battery, suit, data) print_dbg("begin charge exo") local max_restorable = (data.supply and data.supply.max or 100) - data.power local batt_power = math.ceil(battery:condition() * 100) local to_restore = math.min(max_restorable, batt_power) print_dbg("max_restorable is %s, battery power is %s. restoring %s", max_restorable, batt_power, to_restore) if to_restore > 0 then data.power = data.power + to_restore batt_power = batt_power - to_restore if batt_power == 0 then alife_release(battery) else battery:set_condition(batt_power/100) end set_data(suit:id(), data) actor_effects.play_item_fx("batteries_dead") utils_obj.play_sound("interface\\inv_batt") end end local current_outfit -- function on_move(obj) -- if is_exo(obj) then -- adjust_weight(obj) -- end -- end function actor_on_first_update() init() -- local suit = db.actor:item_in_slot(7) -- if suit then -- print_dbg("doing one time weight mgmt") -- actor_item_to_slot(suit) -- end end -- drag battery onto suit, or power device onto suit local function on_item_drag_dropped(obj_d, obj_o, slot_from, slot_to) -- Check capability if not (slot_from == EDDListType.iActorBag and (slot_to == EDDListType.iActorBag or slot_to == EDDListType.iActorSlot)) then return end if ui_inventory.GUI and ui_inventory.GUI.mode == "trade" then return end local sec_d = obj_d:section() -- battery or device local sec_o = obj_o:section() -- suit print_dbg("dragged %s on %s", sec_d, sec_o) if is_exo(obj_o) then local id = obj_o:id() local data = get_data(id) if not data then data = init_data(id) end if sec_d == "batteries_exo" then -- charge exo charge_exo(obj_d, obj_o, data) elseif SYS_GetParam(1, sec_d, "is_psu") then -- replace the psu install_psu(obj_o, data, obj_d) end end end -- player slowing management local slow_coef = 0.1 local is_player_slowed = false local function slow_player() if not is_player_slowed then print_dbg("slowing player") is_player_slowed = true speed.add_speed("exo_speed", slow_coef, false, true) end end local function reset_speed() if is_player_slowed then print_dbg("restoring speed") is_player_slowed = false speed.add_speed("exo_speed", 1, false, true) end end local updated = 0 local interval = 400 function actor_on_update() if time_global() < updated then return end updated = time_global() + interval -- update power if is_wearing_exo() then manage_drain() end -- update speed if (not is_wearing_exo()) then reset_speed() else if exo_charged() then reset_speed() else slow_player() end end end function exo_charged() local exosuit = db.actor:item_in_slot(7) if exosuit and is_exo(exosuit) then local id = exosuit:id() local data = get_data(id) if not data then data = init_data(id) end return data.power > 0 end return false end -- modify drain by local function modify_drain(base_drain, data) local drain_mult = exo_mcm.get_config("drain") or 1 if data.supply then base_drain = base_drain * data.supply.drain end if IsMoveState("mcSprint") then local fast_mult = exo_mcm.get_config("sprint_drain") return base_drain * fast_mult * drain_mult elseif IsMoveState("mcAnyMove") then return base_drain * drain_mult end if data.supply and data.supply.ability == "recharge" then return -0.05 else return 0 end end -- every tick, update the supply power accordingly function manage_drain() local exosuit = db.actor:item_in_slot(7) if exosuit and is_exo(exosuit) then local id = exosuit:id() local sec = exosuit:section() local data = get_data(id) if not data then data = init_data(id) end local base_drain = cache[sec] or 0.01 base_drain = modify_drain(base_drain, data) data.power = clamp(data.power - base_drain, 0, 400) set_data(id, data) if warn and data.power > threshold then warn = false end -- low power warn if not warn and data.power < threshold then warn = true news_manager.send_tip(db.actor, gc("st_low_power"), nil, "swiss_knife", 6000) end end end CanRepair = inventory_upgrades.can_repair_item function inventory_upgrades.can_repair_item( sec, cond, mechanic ) if sec == "batteries_exo" then return false else return CanRepair(sec, cond, mechanic) end end NameCustom = ui_inventory.UIInventory.Name_Custom -- hijack functions for exos function ui_inventory.UIInventory:Name_Custom(obj, bag, temp, i) obj = self:CheckItem(obj,"Name_Custom " .. i) if i == 3 and is_exo(obj) then return check_remove_psu(obj) else return NameCustom(self, obj, bag, temp, i) end end ActionCustom = ui_inventory.UIInventory.Action_Custom function ui_inventory.UIInventory:Action_Custom(obj, bag, temp, i) obj = self:CheckItem(obj,"Action_Custom " .. i) if i == 3 and is_exo(obj) then remove_psu(obj) else ActionCustom(self, obj, bag, temp, i) end end local clr_r = utils_xml.get_color("d_red") local clr_g = utils_xml.get_color("d_green") local clr_y = utils_xml.get_color("yellow") local clr_2 = utils_xml.get_color("ui_gray_1") original_build_desc_header = ui_item.build_desc_header function ui_item.build_desc_header(obj, sec, str) local _str = "" local _str2 = original_build_desc_header(obj, sec, str) -- display power + psu if obj and is_exo(obj) then local data = get_data(obj:id()) if not data then data = init_data(obj:id()) end local display_str = "" local power = string.format("%.2f", data.power) local max_power = data.supply and data.supply.max or 100 local clr = utils_xml.get_color_con((data.power/max_power) * 100) display_str = display_str .." " .. clr_y .. gc("st_dot") .. " " .. clr_2 .. gc("st_power") .. " ".. clr .. power .. "/" .. max_power .. "\\n" local psu = gc("st_equipped_none") clr = clr_r if data.supply then psu = ui_item.get_sec_name(data.supply.name) clr = clr_g end display_str = display_str .. " " .. clr_y .. gc("st_dot") .. " " .. clr_2 .. gc("st_equipped_supply") .. " " .. clr .. psu .. clr_2 .. "\\n \\n" _str = _str .. display_str end _str = _str2 .. _str return _str end local function se_device_on_unregister(se_obj, typ) local id = se_obj.id exo_devices[id] = nil end local function save_state(mdata) mdata.exo_devices = exo_devices end local function load_state(mdata) exo_devices = mdata.exo_devices or {} end function on_game_start() RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("ActorMenu_on_item_drag_drop",on_item_drag_dropped) RegisterScriptCallback("server_entity_on_unregister",se_device_on_unregister) RegisterScriptCallback("actor_on_update",actor_on_update) RegisterScriptCallback("actor_item_to_slot",on_move) RegisterScriptCallback("actor_item_to_ruck",on_move) RegisterScriptCallback("actor_on_first_update",actor_on_first_update) end