--[[ Tronex 2019/12/7 Device binder This script manage devices power consumption, by binding online devices to the device_binder below. Providing consistant check/management Engine handles turning devices functionality off when their power go below a certain point (power_critical), while some are script conrtrolled See "items\settings\devices.ltx" for configurations --]] local enable_debug = false -------------------------------------------------------------------------------- -- Controls -------------------------------------------------------------------------------- dev_consumption = {} -- [sec] = {...} | Power consumption of device per stage dev_consumption_act = {} -- [sec] = {...} | Power consumption of device per action (stage 2) dev_consumption_tot = {} -- [sec] = num | Total power consumption of a device dev_critical = {} -- [sec] = num | Power limit, devices go off when their power go below this value local dev_condition = {} -- [id] = con | Table to store/reload condition of online devices local dev_event_loss = {} -- [id] = num | Table to trace power lost on device event activity local dev_inv = {} -- [id] = bool | Table to trace devices in inventory local dev_slot = {} -- [id] = bool | Table to trace devices in slots local dev_type = { ["D_PDA"] = "PDA", ["DET_SIMP"] = "DET", ["DET_ADVA"] = "DET", ["DET_ELIT"] = "DET", ["DET_SCIE"] = "DET", ["D_DSMETR"] = "DET", ["D_CUSTOM"] = "DET", ["D_FLALIT"] = "FLASH", ["TORCH_S"] = "TORCH", ["II_ATTCH"] = "OTHR", } -- device sections devices = {} device_battery = nil device_geiger = nil device_npc_pda = {} local amper_factor -- Condition States local c_limit local c_zero local c_full local temp_con = {} -- PDA local pda_active = false -- Torch local torch_active = false -- Night-visions nv_state = false -- make nv_state a global variable so we can access it from beefs_nvgs nv_discharged = false local KEYBIND_NV_UP local KEYBIND_NV_DOWN local w_multi = 1024/(device().width) local h_multi = 768/(device().height) local nv_eff = {} local nv_hud = { x = 0, y = 0, height = (device().height * h_multi), weight = (device().width * w_multi) } -- HUD dimensions/position local ppe_effects = { -- PPE effects used for each NV. lum and id used for their ppe effects ["nightvision_1"] = { id = 3301, lum = 0.2 , ui = "wpn\\hud_nvg_gen1" }, ["nightvision_2"] = { id = 3302, lum = 0.5 , ui = "wpn\\hud_nvg_gen2" }, ["nightvision_3"] = { id = 3303, lum = 1.0 , ui = "wpn\\hud_nvg_gen3" }, --["nightvision_bluer"] = { id = 3304, lum = 1.0 , ui = "wpn\\hud_nvg_gen1_test" }, } -- Dosimeter local rad_unit = game.translate_string("st_msv") local holding_shift = false dosimeter_env_rads_mode = false local rad_zones = { ["zone_field_radioactive"] = true, ["zone_field_radioactive_weak"] = true, ["zone_field_radioactive_average"] = true, ["zone_field_radioactive_strong"] = true, ["zone_radioactive"] = true, ["zone_radioactive_weak"] = true, ["zone_radioactive_average"] = true, ["zone_radioactive_strong"] = true, } function initialize() local ini_device = ini_file("items\\settings\\devices.ltx") device_battery = ini_device:r_sec_ex("settings","battery","batteries_dead") device_geiger = ini_device:r_sec_ex("settings","geiger","detector_geiger") -- We clear battery from devices list, and put it in its own category _ITM["battery"] = {} _ITM["battery"][device_battery] = true if _ITM["device"][device_battery] then _ITM["device"][device_battery] = nil end c_limit = ini_device:r_float_ex("settings","condition_limit") or 0.05 c_zero = ini_device:r_float_ex("settings","condition_zero") or 0.0001 c_full = ini_device:r_float_ex("settings","condition_full") or 0.9999 amper_factor = ini_device:r_float_ex("settings","amper_factor") or 1000 local n = ini_device:line_count("power_consumption") for i=0,n-1 do local result, sec, value = ini_device:r_line_ex("power_consumption",i,"","") if ini_sys:section_exist(sec) and value then dev_critical[sec] = ini_sys:r_float_ex(sec,"power_critical") or c_limit dev_consumption[sec] = {} local t = str_explode(value,",") for j=1,#t do local cons = tonumber(t[j]) or 0 if (t[j] == "p") or (cons > 0) then dev_consumption[sec][j] = cons dev_consumption_tot[sec] = dev_consumption_tot[sec] and (dev_consumption_tot[sec] + cons) or cons print_dbg("dev_consumption [%s][%s] = %s", sec, j, cons) end end dev_consumption_act[sec] = {} local value_e = ini_device:r_string_ex("power_consumption_event",sec) if value_e then t = str_explode(value_e,",") for j=1,#t do local cons = tonumber(t[j]) or 0 if cons > 0 then dev_consumption_act[sec][j] = cons dev_consumption_tot[sec] = dev_consumption_tot[sec] and (dev_consumption_tot[sec] + cons) or cons print_dbg("dev_consumption_act [%s][%s] = %s", sec, j, cons) end end end else printe("!ERROR item_device | reading from items\\settings\\devices.ltx | section [%s] or power consumption values don't exist", sec) end end n = ini_device:line_count("npc_pda") for i=0,n-1 do local result, sec, value = ini_device:r_line_ex("npc_pda",i,"","") if ini_sys:section_exist(sec) then device_npc_pda[sec] = true end end end function print_dbg(txt, ...) if enable_debug then printf("item_device | %s | " .. txt, time_global(), ...) end end -------------------------------------------------------------------------------- -- Callbacks -------------------------------------------------------------------------------- local function process_pda_info(npc,info_id) -- add this function to bind_stalker info_callback function -- battery.process_pda_infoportion(info_id) local actor = db.actor if actor then if info_id == "ui_pda" then -- PDA opened -- set flag to indicate PDA is open pda_active = true hide_hud_inventory() --news_manager.send_tip(actor, "Opening PDA", nil, nil, 5000) actor:disable_info_portion("ui_pda_hide") elseif info_id == "ui_pda_hide" then -- PDA closed -- remove flag to indicate PDA is closed pda_active = false --news_manager.send_tip(actor, "Closing PDA", nil, nil, 5000) actor:disable_info_portion("ui_pda") end end end local function on_item_drag_dropped(obj_b, obj_d, 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 local sec_b = obj_b:section() -- battery local sec_d = obj_d:section() -- device if (sec_b == device_battery) and dev_consumption[sec_d] then local con_b = obj_b:condition() local con_d = obj_d:condition() if (con_d > con_b) then actor_menu.set_msg(1, game.translate_string("ui_st_battery_more_power")) return end if (con_d > dev_critical[sec_d]) then alife_create_item(device_battery, db.actor, {cond = con_d}) end alife_release(obj_b) obj_d:set_condition(con_b) actor_effects.play_item_fx(device_battery) utils_obj.play_sound("interface\\inv_batt") end end local function on_key_hold(key) if (key == DIK_keys["DIK_LSHIFT"]) then holding_shift = true end end local function on_key_release(key) if (key == DIK_keys["DIK_LSHIFT"]) then holding_shift = false return end local bind = dik_to_bind(key) -- Night-Vision if (bind == key_bindings.kNIGHT_VISION) then local torch = db.actor:item_in_slot(10) set_nightvision(torch and torch:section(), not nv_state) elseif (key == KEYBIND_NV_UP) then z_beefs_nvgs.brightness_up() elseif (key == KEYBIND_NV_DOWN) then z_beefs_nvgs.brightness_down() -- Dosimeter keybind elseif (bind == key_bindings.kCUSTOM14) then local obj_geiger = db.actor:object(device_geiger) if obj_geiger and drain_device_on_event(obj_geiger, device_geiger, 2) then if (holding_shift) then dosimeter_env_rads_mode = not dosimeter_env_rads_mode dosimeter_mode_string = dosimeter_env_rads_mode and game.translate_string("st_environment_rads") or game.translate_string("st_actor_rads") actor_menu.set_msg(1, game.translate_string("st_dosimeter_mode")..": "..dosimeter_mode_string , 3) return end local rads = dosimeter_env_rads_mode and math.floor(level.get_env_rads()*2500) or math.floor(db.actor.radiation*10000*0.387) actor_menu.set_msg(1, tostring(rads) .. " " .. rad_unit , 2 ) utils_obj.play_sound("detectors\\geiger_1") end end end local function on_anomaly_touch(obj, flags) if obj then -- Don't play gieger sound if player don't have a geiger counter if rad_zones[obj:section()] then if game_difficulties.get_game_factor("notify_geiger") then local obj_geiger = device_geiger and db.actor:object(device_geiger) if not (obj_geiger and drain_device_on_event(obj_geiger, device_geiger, 1)) then flags.ret_value = false return end end -- Anomaly detector elseif (not game_difficulties.get_game_factor("notify_anomaly")) then flags.ret_value = false return end end flags.ret_value = true end local function on_trade_opened() local function search(temp , item) if IsItem("device",item:section()) then temp_con[item:id()] = item:condition() item:set_condition(c_full) end end db.actor:iterate_inventory(search,nil) local pda = db.actor:item_in_slot(8) if pda and (not temp_con[pda:id()]) then temp_con[pda:id()] = pda:condition() pda:set_condition(c_full) end local det = db.actor:item_in_slot(9) if det and (not temp_con[det:id()]) then temp_con[det:id()] = det:condition() det:set_condition(c_full) end local torch = db.actor:item_in_slot(10) if torch and (not temp_con[torch:id()]) then temp_con[torch:id()] = torch:condition() torch:set_condition(c_full) end end local function on_trade_closed() for k,v in pairs(temp_con) do local obj = level.object_by_id(k) if obj then local p = obj:parent() if (p and p:id() == AC_ID) then alife_process_item(obj:section(),k, {cond = v}) end end end temp_con = empty_table(temp_con) end local function item_rack(obj) local sec = obj:section() if dev_consumption[sec] then local id = obj:id() dev_inv[id] = true dev_slot[id] = nil print_dbg("[%s] moved to inv", obj:name()) end end local function item_slot(obj) local sec = obj:section() if dev_consumption[sec] then local id = obj:id() dev_inv[id] = nil dev_slot[id] = true print_dbg("[%s] moved to slot", obj:name()) end end local function item_out(obj) local sec = obj:section() if dev_consumption[sec] then local id = obj:id() -- Hack to turn off dropped NV device. TODO: adjust the device process function to overcome this hack if dev_slot[id] and nv_state then local cls = ini_sys:r_string_ex(sec,"class") if (dev_type[cls] == "TORCH") then set_nightvision(sec,false) end end dev_inv[id] = nil dev_slot[id] = nil print_dbg("[%s] moved out", obj:name()) end end local function actor_on_first_update() for i=1,13 do local obj = db.actor:item_in_slot(i) if obj and IsItem("device",obj:section()) then item_slot(obj) end end -- Restore torch state local torch = db.actor:item_in_slot(10) if torch and torch:torch_enabled() then torch_active = torch:id() end end local function se_device_on_unregister(se_obj, typ) --local sec = se_obj:section_name() --if ini_sys:r_string_ex(sec,"script_binding") == "item_device.bind" then local id = se_obj.id dev_condition[id] = nil dev_inv[id] = nil dev_slot[id] = nil --end end local function save_state(mdata) mdata.device_condition = dev_condition end local function load_state(mdata) dev_condition = mdata.device_condition or {} end local function on_option_change() KEYBIND_NV_UP = z_beefs_nvgs_mcm.get_config("keybind_nv_up") KEYBIND_NV_DOWN = z_beefs_nvgs_mcm.get_config("keybind_nv_down") end function on_game_start() if (USE_MARSHAL) then RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) end RegisterScriptCallback("server_entity_on_unregister",se_device_on_unregister) RegisterScriptCallback("actor_on_feeling_anomaly",on_anomaly_touch) RegisterScriptCallback("on_key_press",on_key_press) RegisterScriptCallback("on_key_release",on_key_release) RegisterScriptCallback("on_key_hold",on_key_hold) RegisterScriptCallback("ActorMenu_on_item_drag_drop",on_item_drag_dropped) RegisterScriptCallback("actor_on_info_callback",process_pda_info) RegisterScriptCallback("ActorMenu_on_trade_started",on_trade_opened) RegisterScriptCallback("ActorMenu_on_trade_closed",on_trade_closed) RegisterScriptCallback("actor_on_first_update",actor_on_first_update) RegisterScriptCallback("actor_on_item_take",item_rack) RegisterScriptCallback("actor_item_to_ruck",item_rack) RegisterScriptCallback("actor_on_item_drop",item_out) RegisterScriptCallback("actor_item_to_slot",item_slot) RegisterScriptCallback("on_option_change", on_option_change) on_option_change() initialize() end -------------------------------------------------------------------------------- -- Item property -------------------------------------------------------------------------------- function menu_battery(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return end local sec = obj:section() local con = obj:condition() if (sec ~= device_battery) and dev_critical[sec] and (con > dev_critical[sec]) then return game.translate_string("st_item_unpack_battery") end end function func_battery(obj) local p = obj:parent() if not (p and p:id() == AC_ID) then return end local sec = obj:section() local con = obj:condition() if ini_sys:section_exist(device_battery) then alife_create_item(device_battery, db.actor, {cond = con}) alife_process_item(sec, obj:id(), {cond = c_zero}) -- Play effect actor_effects.play_item_fx(device_battery) utils_obj.play_sound("interface\\inv_batt") end end -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- function drain_device_on_event(obj, sec, idx, custom_loss) if (not obj) then --callstack() --printe("! bind device.drain_device_on_event | no object pass") return end sec = sec or obj:section() if (not dev_critical[sec]) then return end -- Device goes off on critical power, no draining local cond = obj:condition() if cond < dev_critical[sec] then return false end local loss = dev_consumption_act[sec] and dev_consumption_act[sec][idx] or 0 if custom_loss then loss = custom_loss end local id = obj:id() if dev_event_loss[id] then dev_event_loss[id] = dev_event_loss[id] + loss else dev_event_loss[id] = loss end return true -- if drain happened end function drain_device(obj, sec, loss, loss_add) if not (obj and loss and loss ~= 0) then return end sec = sec or obj:section() -- Device goes off on critical power, no draining local cond = obj:condition() if cond < dev_critical[sec] then --print_dbg("[%s] power is too low: %s", obj:name(), cond) return false end if loss_add then loss = loss + loss_add end -- Set up new power local factor = game_difficulties.get_eco_factor("battery_consumption") loss = loss * factor loss = loss / amper_factor local cond_n = cond - loss cond_n = clamp(cond_n, c_zero, c_full) obj:set_condition(cond_n) --print_dbg("[%s] | power loss: %s | remaining power: %s", obj:name(), loss, cond_n) return true -- if drain happened end function can_toggle_torch() return db.actor:item_in_slot(10) and true or false end function toggle_torch() local torch = db.actor:item_in_slot(10) local id = torch and torch:id() if id then if torch_active == id then torch_active = false else torch_active = id end else torch_active = false end end function is_torch_active() return torch_active end function set_nightvision_HUD(bShow) local hud = get_hud() local drk = hud:GetCustomStatic("nv_tunnel") local wnd if (bShow == false) or (db.actor:is_talking()) then if (drk ~= nil) then hud:RemoveCustomStatic("nv_tunnel") drk = nil end return end if (drk == nil) then hud:AddCustomStatic("nv_tunnel",true) drk = hud:GetCustomStatic("nv_tunnel") wnd = drk:wnd() if (wnd ~= nil) then wnd:SetWndPos(vector2():set(nv_hud.x , nv_hud.y)) wnd:SetWndSize(vector2():set(nv_hud.weight , nv_hud.height)) wnd:SetAutoDelete(true) end end if (drk ~= nil) then wnd = drk:wnd() local torch = db.actor:item_in_slot(10) local torch_sec = torch and torch:section() if torch_sec then nv_eff[torch_sec] = nv_eff[torch_sec] or ini_sys:r_string_ex(torch_sec,"nv_effect") local ppe = ppe_effects[nv_eff[torch_sec]] if ppe then --wnd:InitTexture(ppe.ui) end end end end function set_nightvision(section,state) if (not section) then return end nv_eff[section] = nv_eff[section] or ini_sys:r_string_ex(section,"nv_effect") if ppe_effects[nv_eff[section]] then if state and (not nv_state) then local ppe = ppe_effects[nv_eff[section]] level.add_pp_effector(nv_eff[section] .. ".ppe", ppe.id, true) utils_obj.play_sound("interface\\inv_nv_start") nv_state = true game.set_nv_lumfactor(ppe.lum) elseif (not state) and nv_state then local ppe = ppe_effects[nv_eff[section]] level.remove_pp_effector(ppe.id) utils_obj.play_sound("interface\\inv_nv_off") nv_state = false game.set_nv_lumfactor(0) end set_nightvision_HUD(nv_state) end end function is_nv_active() return nv_state end function set_pda_glitch(obj, val) if (not obj) then return end local curr_val = obj:psy_factor() obj:set_psy_factor( clamp( (curr_val + val), 0, 1 ) ) end function is_pda_active() return pda_active end function is_pda_charged(actor_only) local pda = db.actor:item_in_slot(8) if (not pda) then return false end if actor_only and device_npc_pda[pda:section()] then return false end return is_device_charged( pda ) end function is_device_charged(obj, sec, cond) sec = obj and obj:section() or sec cond = obj and obj:condition() or cond return sec and cond and dev_critical[sec] and cond > dev_critical[sec] end function get_power_critical(sec) return dev_critical[sec] end function get_power_consumption(sec) return dev_consumption[sec] and dev_consumption[sec][1] end function pda_warning() --Return true if PDA is equipped and works if (is_pda_charged() or is_pda_active()) then return true end --Show not equipped or battery empty message local obj = db.actor:item_in_slot(8) if (obj) then actor_menu.set_msg(1, game.translate_string("st_pda_no_power") , 3 ) else actor_menu.set_msg(1, game.translate_string("st_pda_no_active") , 3 ) end return false end -------------------------------------------------------------------------------- -- Class "device_binder" -------------------------------------------------------------------------------- function bind(obj) obj:bind_object(device_binder(obj)) end class "device_binder" (object_binder) function device_binder:__init(obj) super(obj) self.first_update = nil end local nv_glitch_state = false function device_binder:update(delta) object_binder.update(self, delta) local obj = self.object local id = obj:id() local sec = obj:section() local parent = obj:parent() if not self.first_update then self.first_update = true -- Process devices belongs to others if parent and (parent:id() ~= AC_ID) then -- Process devices belongs to stalkers if IsStalker(parent) then -- Set up profiles of spawned PDA if device_npc_pda[sec] and (not se_load_var(id, obj:name(), "info")) then ui_pda_npc_tab.register_pda(parent, sec, id) end --[[ if stalker is not a trader, set custom power if utils_obj.is_trader(parent) then local cond_set = (math.random(30,70)/100) alife_process_item( sec , id , {cond = cond_set} ) end --]] end end -- set up stored power for device on first update if dev_condition[id] then obj:set_condition(dev_condition[id]) --print_dbg("[%s] power restored from save: %s", obj:name(), dev_condition[id]) end return end -- store updated device power local cond = obj:condition() dev_condition[id] = cond -- only process device power if it belongs to actor if (parent and (parent:id() == AC_ID) and (self.type ~= false)) then -- set type if (self.type == nil) then local cls = ini_sys:r_string_ex(sec,"class") if dev_type[cls] then self.type = dev_type[cls] --print_dbg("[%s] type detected: %s", obj:name(), dev_type[cls]) else self.type = false --print_dbg("[%s] type not found", obj:name()) end end -- night-vision/light management if (self.type == "TORCH") then self:process_torch(id, sec, cond) end if dev_consumption[sec] and (cond > dev_critical[sec]) then -- No need to process unequipped devices if dev_slot[id] then if (self.N_V) then -- Emissions: the closer the wave, the higher the psi influnces if GetEvent("surge", "state") then local surge_time = GetEvent("surge", "time") or 0 local val_glitch = (surge_time > 168) and normalize(surge_time, 220, 168) or normalize(surge_time, 20, 168) z_beefs_nvgs.nvg_glitch( clamp(val_glitch,0.0,0.9) ) nv_glitch_state = true -- Psi-storms: huge spike when a vortex hits the ground elseif GetEvent("psi_storm", "state") then if GetEvent("psi_storm", "vortex") then z_beefs_nvgs.nvg_glitch(0.9) nv_glitch_state = true else z_beefs_nvgs.nvg_glitch(0.2) nv_glitch_state = true end -- NVG doesn't get affected in normal cases else if nv_glitch_state then z_beefs_nvgs.nvg_glitch(0.0) nv_glitch_state = false end end end -- Light flicker on event for flashlights if (self.type == "FLASH") then self:process_flicker() end -- Glitch effect on event for PDA if (self.type == "PDA") then self:process_glitch(id, sec, cond) end end -- drain device power if its active self:process_power(id, sec, cond) end end end function device_binder:process_power(id, section, condition) local power_loss = 0 local in_hand = false for num, consumption in pairs(dev_consumption[section]) do -- Device active in hand if num == 1 then local obj = db.actor:active_detector() or db.actor:active_item() if obj and obj:id() == id then power_loss = power_loss + consumption in_hand = true end -- Device active in slot elseif num == 2 and dev_slot[id] and (not in_hand) then power_loss = power_loss + consumption -- Device is emitting light if dev_consumption_act[section] and (self.type == "TORCH") and self.object:torch_enabled() then power_loss = power_loss + (dev_consumption_act[section][1] or 0) end -- Device has night vision active if dev_consumption_act[section] and self.N_V and nv_state then power_loss = power_loss + (dev_consumption_act[section][2] or 0) end -- Device active in inventory elseif num == 3 and dev_inv[id] and (not in_hand) then power_loss = power_loss + consumption end end -- Power loss on event if dev_event_loss[id] and (dev_event_loss[id] > 0) then power_loss = power_loss + dev_event_loss[id] dev_event_loss[id] = 0 end -- Consume power drain_device(self.object, section, power_loss) end function device_binder:process_torch(id, section, condition) -- Night-Vision state management if nv_state then if dev_slot[id] then self.N_V = true -- indicator for device having active NV else -- if NV device moved out of slot, turn NV off if self.N_V then set_nightvision(section,false) end self.N_V = nil end -- if NV device ran out of power, turn NV off if (self.N_V and (condition <= dev_critical[section])) then nv_discharged = true set_nightvision(section,false) self.N_V = nil end -- disable NV info if NV is off elseif self.N_V and (not nv_state) then set_nightvision(section,false) self.N_V = nil end -- Torch state management if torch_active == id then if dev_slot[id] and (condition > dev_critical[section]) then if (not self.object:torch_enabled()) then self.object:enable_torch(true) end else if (self.object:torch_enabled()) then self.object:enable_torch(false) end torch_active = false --actor_menu.set_notification(nil, "ui_inGame2_notify_low_battery", 10) end else if (self.object:torch_enabled()) then self.object:enable_torch(false) end end -- Torch light flicker on event if self.object:torch_enabled() then self:process_flicker() end end function device_binder:process_glitch(id, section, condition) -- Emissions: the closer the wave, the higher the psi influnces if GetEvent("surge", "state") then local surge_time = GetEvent("surge", "time") or 0 local val_glitch = (surge_time > 168) and normalize(surge_time, 220, 168) or normalize(surge_time, 20, 168) self.object:set_psy_factor( clamp(val_glitch,0,1) ) -- Psi-storms: huge spike when a vortex hits the ground elseif GetEvent("psi_storm", "state") then if GetEvent("psi_storm", "vortex") then self.object:set_psy_factor(1) else self.object:set_psy_factor(0.2) end -- PDA doesn't get affected in normal cases else self.object:set_psy_factor(0) end end function device_binder:process_flicker(force) local flicker = level_environment.get_light_flicker() if force then flicker = force end if (self.flicker == nil) then self.flicker = false end if (self.flicker ~= flicker) then --print_dbg("[%s] flicker: %s", self.object:name(), flicker) if flicker then local ca = "light_flicker" local on_off_chance = math.random(25,50) local on_off_time = math.random(0.3,0.9) local fps = math.random(20,30) self.object:set_color_animator(ca,true,on_off_chance,on_off_time,fps) --printf("~ Torch set anim") else --printf("~ Torch reset anim") self.object:reset_color_animator() end end self.flicker = flicker end function device_binder:reload(section) object_binder.reload(self, section) end function device_binder:reinit() object_binder.reinit(self) end function device_binder:net_spawn(se_abstract) if not(object_binder.net_spawn(self, se_abstract)) then return false end return true end function device_binder:net_destroy() -- if NV device disappear, turn off NV. duh? if self.N_V then self.N_V = nil set_nightvision(self.object:section(),false) end -- if working torch device disappear, clear id if torch_active == self.object:id() then torch_active = false end -- if light source is flickering, turn it off if self.flicker then self:process_flicker(false) end --print_dbg("[%s] destroyed: %s", self.object:name()) object_binder.net_destroy(self) end function device_binder:save(stpk) end function device_binder:load(stpk) end