Merge pull request #7405 from VOREStation/vplk-static-machinery-power

Static Machinery Power
This commit is contained in:
Aronai Sieyes
2020-04-24 21:06:52 -04:00
committed by GitHub
16 changed files with 312 additions and 145 deletions

View File

@@ -17,6 +17,7 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called
#define USE_POWER_ACTIVE 2 // Machine is using power at its active power level
// Channel numbers for power.
#define CURRENT_CHANNEL -1 // Passed as an argument this means "use whatever current channel is"
#define EQUIP 1
#define LIGHT 2
#define ENVIRON 3

View File

@@ -121,10 +121,8 @@ SUBSYSTEM_DEF(machines)
while(current_run.len)
var/obj/machinery/M = current_run[current_run.len]
current_run.len--
if(istype(M) && !QDELETED(M) && !(M.process(wait) == PROCESS_KILL))
if(M.use_power)
M.auto_use_power()
else
if(!istype(M) || QDELETED(M) || (M.process(wait) == PROCESS_KILL))
global.processing_machines.Remove(M)
if(!QDELETED(M))
DISABLE_BITFIELD(M.datum_flags, DF_ISPROCESSING)

View File

@@ -21,14 +21,22 @@
var/requires_power = 1
var/always_unpowered = 0 //this gets overriden to 1 for space in area/New()
var/power_equip = 1
var/power_light = 1
var/power_environ = 1
var/music = null
var/used_equip = 0
var/used_light = 0
var/used_environ = 0
// Power channel status - Is it currently energized?
var/power_equip = TRUE
var/power_light = TRUE
var/power_environ = TRUE
// Oneoff power usage - Used once and cleared each power cycle
var/oneoff_equip = 0
var/oneoff_light = 0
var/oneoff_environ = 0
// Continuous "static" power usage - Do not update these directly!
var/static_equip = 0
var/static_light = 0
var/static_environ = 0
var/music = null
var/has_gravity = 1
var/secret_name = FALSE // This tells certain things that display areas' names that they shouldn't display this area's name.
var/obj/machinery/power/apc/apc = null
@@ -81,7 +89,7 @@
for(var/atom/movable/AM in T)
A.Entered(AM, old_area)
for(var/obj/machinery/M in T)
M.power_change()
M.area_changed(old_area, A)
/area/proc/get_contents()
return contents
@@ -251,33 +259,96 @@
if (fire || eject || party)
updateicon()
/area/proc/usage(var/chan)
/area/proc/usage(var/chan, var/include_static = TRUE)
var/used = 0
switch(chan)
if(LIGHT)
used += used_light
used += oneoff_light + (include_static * static_light)
if(EQUIP)
used += used_equip
used += oneoff_equip + (include_static * static_equip)
if(ENVIRON)
used += used_environ
used += oneoff_environ + (include_static * static_environ)
if(TOTAL)
used += used_light + used_equip + used_environ
used += oneoff_light + (include_static * static_light)
used += oneoff_equip + (include_static * static_equip)
used += oneoff_environ + (include_static * static_environ)
return used
// Helper for APCs; will generally be called every tick.
/area/proc/clear_usage()
used_equip = 0
used_light = 0
used_environ = 0
oneoff_equip = 0
oneoff_light = 0
oneoff_environ = 0
/area/proc/use_power(var/amount, var/chan)
// Use this for a one-time power draw from the area, typically for non-machines.
/area/proc/use_power_oneoff(var/amount, var/chan)
switch(chan)
if(EQUIP)
used_equip += amount
oneoff_equip += amount
if(LIGHT)
used_light += amount
oneoff_light += amount
if(ENVIRON)
used_environ += amount
oneoff_environ += amount
return amount
// This is used by machines to properly update the area of power changes.
/area/proc/power_use_change(old_amount, new_amount, chan)
use_power_static(new_amount - old_amount, chan) // Simultaneously subtract old_amount and add new_amount.
// Not a proc you want to use directly unless you know what you are doing; see use_power_oneoff above instead.
/area/proc/use_power_static(var/amount, var/chan)
switch(chan)
if(EQUIP)
static_equip += amount
if(LIGHT)
static_light += amount
if(ENVIRON)
static_environ += amount
// This recomputes the continued power usage; can be used for testing or error recovery, but is not called every tick.
/area/proc/retally_power()
static_equip = 0
static_light = 0
static_environ = 0
for(var/obj/machinery/M in src)
switch(M.power_channel)
if(EQUIP)
static_equip += M.get_power_usage()
if(LIGHT)
static_light += M.get_power_usage()
if(ENVIRON)
static_environ += M.get_power_usage()
//////////////////////////////////////////////////////////////////
/area/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("check_static_power", "Check Static Power")
/area/vv_do_topic(list/href_list)
. = ..()
IF_VV_OPTION("check_static_power")
if(!check_rights(R_DEBUG))
return
src.check_static_power(usr)
href_list["datumrefresh"] = "\ref[src]"
// Debugging proc to report if static power is correct or not.
/area/proc/check_static_power(var/user)
set name = "Check Static Power"
var/actual_static_equip = static_equip
var/actual_static_light = static_light
var/actual_static_environ = static_environ
retally_power()
if(user)
var/list/report = list("[src] ([type]) static power tally:")
report += "EQUIP: Actual: [actual_static_equip] Correct: [static_equip] Difference: [actual_static_equip - static_equip]"
report += "LIGHT: Actual: [actual_static_light] Correct: [static_light] Difference: [actual_static_light - static_light]"
report += "ENVIRON: Actual: [actual_static_environ] Correct: [static_environ] Difference: [actual_static_environ - static_environ]"
to_chat(user, report.Join("\n"))
return (actual_static_equip == static_equip && actual_static_light == static_light && actual_static_environ == static_environ)
//////////////////////////////////////////////////////////////////
var/list/mob/living/forced_ambiance_list = new

View File

@@ -5,12 +5,16 @@ Overview:
of 'Del' removes reference to src machine in global 'machines list'.
Class Variables:
power_init_complete (boolean)
Indicates that we have have registered our static power usage with the area.
use_power (num)
current state of auto power use.
Possible Values:
0 -- no auto power use
1 -- machine is using power at its idle power level
2 -- machine is using power at its active power level
USE_POWER_OFF:0 -- no auto power use
USE_POWER_IDLE:1 -- machine is using power at its idle power level
USE_POWER_ACTIVE:2 -- machine is using power at its active power level
active_power_usage (num)
Value for the amount of power to use when in active power mode
@@ -51,28 +55,18 @@ Class Procs:
Destroy() 'game/machinery/machine.dm'
auto_use_power() 'game/machinery/machine.dm'
This proc determines how power mode power is deducted by the machine.
'auto_use_power()' is called by the 'master_controller' game_controller every
tick.
get_power_usage() 'game/machinery/machinery_power.dm'
Returns the amount of power this machine uses every SSmachines cycle.
Default definition uses 'use_power', 'active_power_usage', 'idle_power_usage'
Return Value:
return:1 -- if object is powered
return:0 -- if object is not powered.
Default definition uses 'use_power', 'power_channel', 'active_power_usage',
'idle_power_usage', 'powered()', and 'use_power()' implement behavior.
powered(chan = EQUIP) 'modules/power/power.dm'
powered(chan = CURRENT_CHANNEL) 'game/machinery/machinery_power.dm'
Checks to see if area that contains the object has power available for power
channel given in 'chan'.
use_power(amount, chan=EQUIP, autocalled) 'modules/power/power.dm'
use_power_oneoff(amount, chan=CURRENT_CHANNEL) 'game/machinery/machinery_power.dm'
Deducts 'amount' from the power channel 'chan' of the area that contains the object.
If it's autocalled then everything is normal, if something else calls use_power we are going to
need to recalculate the power two ticks in a row.
power_change() 'modules/power/power.dm'
power_change() 'game/machinery/machinery_power.dm'
Called by the area that contains the object when ever that area under goes a
power state change (area runs out of power, or area channel is turned off).
@@ -108,6 +102,7 @@ Class Procs:
var/idle_power_usage = 0
var/active_power_usage = 0
var/power_channel = EQUIP //EQUIP, ENVIRON or LIGHT
var/power_init_complete = FALSE
var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames.
var/uid
var/panel_open = 0
@@ -145,7 +140,9 @@ Class Procs:
if(A.loc == src) // If the components are inside the machine, delete them.
qdel(A)
else // Otherwise we assume they were dropped to the ground during deconstruction, and were not removed from the component_parts list by deconstruction code.
component_parts -= A
warning("[A] was still in [src]'s component_parts when it was Destroy()'d")
component_parts.Cut()
component_parts = null
if(contents) // The same for contents.
for(var/atom/A in contents)
if(ishuman(A))
@@ -157,9 +154,8 @@ Class Procs:
qdel(A)
return ..()
/obj/machinery/process()//If you dont use process or power why are you here
if(!(use_power || idle_power_usage || active_power_usage))
return PROCESS_KILL
/obj/machinery/process() // Steady power usage is handled separately. If you dont use process why are you here?
return PROCESS_KILL
/obj/machinery/emp_act(severity)
if(use_power && stat == 0)
@@ -192,31 +188,6 @@ Class Procs:
else
return
//sets the use_power var and then forces an area power update
/obj/machinery/proc/update_use_power(var/new_use_power)
use_power = new_use_power
// Sets the power_channel var
/obj/machinery/proc/update_power_channel(var/new_channel)
power_channel = new_channel
// Sets the idle_power_usage var
/obj/machinery/proc/update_idle_power_usage(var/new_power_usage)
idle_power_usage = new_power_usage
// Sets the active_power_usage var
/obj/machinery/proc/update_active_power_usage(var/new_power_usage)
active_power_usage = new_power_usage
/obj/machinery/proc/auto_use_power()
if(!powered(power_channel))
return 0
if(use_power == USE_POWER_IDLE)
use_power(idle_power_usage, power_channel, 1)
else if(use_power >= USE_POWER_ACTIVE)
use_power(active_power_usage, power_channel, 1)
return 1
/obj/machinery/proc/operable(var/additional_flags = 0)
return !inoperable(additional_flags)

View File

@@ -0,0 +1,178 @@
//
// /obj/machinery POWER USAGE CODE HERE! GO GO GADGET STATIC POWER!
// Note: You can find /obj/machinery/power power usage code in power.dm
//
// The following four machinery variables determine the "static" amount of power used every power cycle:
// - use_power, idle_power_usage, active_power_usage, power_channel
//
// Please never change any of these variables! Use the procs that update them instead!
//
// Note that we update the area even if the area is unpowered.
#define REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power)\
if(old_power != new_power){\
var/area/A = get_area(src);\
if(A) A.power_use_change(old_power, new_power, power_channel)}
// Current power consumption right now.
#define POWER_CONSUMPTION (use_power == USE_POWER_IDLE ? idle_power_usage : (use_power >= USE_POWER_ACTIVE ? active_power_usage : 0))
// returns true if the area has power on given channel (or doesn't require power).
// defaults to power_channel
/obj/machinery/proc/powered(var/chan = CURRENT_CHANNEL) // defaults to power_channel
//Don't do this. It allows machines that set use_power to 0 when off (many machines) to
//be turned on again and used after a power failure because they never gain the NOPOWER flag.
//if(!use_power)
// return 1
var/area/A = get_area(src) // make sure it's in an area
if(!A)
return 0 // if not, then not powered
if(chan == CURRENT_CHANNEL)
chan = power_channel
return A.powered(chan) // return power status of the area
// called whenever the power settings of the containing area change
// by default, check equipment channel & set/clear NOPOWER flag
// Returns TRUE if NOPOWER stat flag changed.
// can override if needed
/obj/machinery/proc/power_change()
var/oldstat = stat
if(powered(power_channel))
stat &= ~NOPOWER
else
stat |= NOPOWER
. = (stat != oldstat)
return
// Get the amount of power this machine will consume each cycle. Override by experts only!
/obj/machinery/proc/get_power_usage()
return POWER_CONSUMPTION
// DEPRECATED! - USE use_power_oneoff() instead!
/obj/machinery/proc/use_power(var/amount, var/chan = -1) // defaults to power_channel
return src.use_power_oneoff(amount, chan);
// This will have this machine have its area eat this much power next tick, and not afterwards. Do not use for continued power draw.
// Returns actual amount drawn (In theory this could be less than the amount asked for. In pratice it won't be FOR NOW)
/obj/machinery/proc/use_power_oneoff(var/amount, var/chan = CURRENT_CHANNEL)
var/area/A = get_area(src) // make sure it's in an area
if(!A)
return
if(chan == CURRENT_CHANNEL)
chan = power_channel
return A.use_power_oneoff(amount, chan)
// Check if we CAN use a given amount of extra power as a one off. Returns amount we could use without actually using it.
// For backwards compatibilty this returns true if the channel is powered. This is consistant with pre-static-power
// behavior of APC powerd machines, but at some point we might want to make this a bit cooler.
/obj/machinery/proc/can_use_power_oneoff(var/amount, var/chan = CURRENT_CHANNEL)
if(powered(chan))
return amount // If channel is powered then you can do it.
return 0
// Do not do power stuff in New/Initialize until after ..()
/obj/machinery/Initialize()
. = ..()
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(0, power)
power_init_complete = TRUE
// Or in Destroy at all, but especially after the ..().
/obj/machinery/Destroy()
if(ismovable(loc))
GLOB.moved_event.unregister(loc, src, .proc/update_power_on_move) // Unregister just in case
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(power, 0)
. = ..()
// Registering moved_event observers for all machines is too expensive. Instead we do it ourselves.
// 99% of machines are always on a turf anyway, very few need recursive move handling.
/obj/machinery/Move()
var/old_loc = loc
if((. = ..()))
update_power_on_move(src, old_loc, loc)
if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
GLOB.moved_event.register(loc, src, .proc/update_power_on_move)
if(ismovable(old_loc)) // Unregister recursive movement.
GLOB.moved_event.unregister(old_loc, src, .proc/update_power_on_move)
/obj/machinery/forceMove(atom/destination)
var/old_loc = loc
if((. = ..()))
update_power_on_move(src, old_loc, loc)
if(ismovable(loc)) // Register for recursive movement (if the thing we're inside moves)
GLOB.moved_event.register(loc, src, .proc/update_power_on_move)
if(ismovable(old_loc)) // Unregister recursive movement.
GLOB.moved_event.unregister(old_loc, src, .proc/update_power_on_move)
/obj/machinery/proc/update_power_on_move(atom/movable/mover, atom/old_loc, atom/new_loc)
var/area/old_area = get_area(old_loc)
var/area/new_area = get_area(new_loc)
if(old_area != new_area)
area_changed(old_area, new_area)
/obj/machinery/proc/area_changed(area/old_area, area/new_area)
if(old_area == new_area || !power_init_complete)
return
var/power = POWER_CONSUMPTION
if(!power)
return // This is the most likely case anyway.
if(old_area)
old_area.power_use_change(power, 0, power_channel) // Remove our usage from old area
if(new_area)
new_area.power_use_change(0, power, power_channel) // Add our usage to new area
power_change() // Force check in case the old area was powered and the new one isn't or vice versa.
//
// Usage Update Procs - These procs are the only allowed way to modify these four variables:
// - use_power, idle_power_usage, active_power_usage, power_channel
//
// Sets the use_power var and then forces an area power update
/obj/machinery/proc/update_use_power(var/new_use_power)
if(use_power == new_use_power)
return
if(!power_init_complete)
use_power = new_use_power
return TRUE // We'll be retallying anyway.
var/old_power = POWER_CONSUMPTION
use_power = new_use_power
var/new_power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power)
return TRUE
// Sets the power_channel var and then forces an area power update.
/obj/machinery/proc/update_power_channel(var/new_channel)
if(power_channel == new_channel)
return
if(!power_init_complete)
power_channel = new_channel
return TRUE // We'll be retallying anyway.
var/power = POWER_CONSUMPTION
REPORT_POWER_CONSUMPTION_CHANGE(power, 0) // Subtract from old channel
power_channel = new_channel
REPORT_POWER_CONSUMPTION_CHANGE(0, power) // Add to new channel
return TRUE
// Sets the idle_power_usage var and then forces an area power update if use_power was USE_POWER_IDLE
/obj/machinery/proc/update_idle_power_usage(var/new_power_usage)
if(idle_power_usage == new_power_usage)
return
var/old_power = idle_power_usage
idle_power_usage = new_power_usage
if(power_init_complete && use_power == USE_POWER_IDLE) // If this is the channel in use
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power_usage)
// Sets the active_power_usage var and then forces an area power update if use_power was USE_POWER_ACTIVE
/obj/machinery/proc/update_active_power_usage(var/new_power_usage)
if(active_power_usage == new_power_usage)
return
var/old_power = active_power_usage
active_power_usage = new_power_usage
if(power_init_complete && use_power == USE_POWER_ACTIVE) // If this is the channel in use
REPORT_POWER_CONSUMPTION_CHANGE(old_power, new_power_usage)
#undef REPORT_POWER_CONSUMPTION_CHANGE
#undef POWER_CONSUMPTION

View File

@@ -62,6 +62,9 @@
recharge_amount = cell.give(recharge_amount)
use_power(recharge_amount / CELLRATE)
else
// Since external power is offline, draw operating current from the internal cell
cell.use(get_power_usage() * CELLRATE)
if(icon_update_tick >= 10)
icon_update_tick = 0
@@ -71,19 +74,6 @@
if(occupant || recharge_amount)
update_icon()
//since the recharge station can still be on even with NOPOWER. Instead it draws from the internal cell.
/obj/machinery/recharge_station/auto_use_power()
if(!(stat & NOPOWER))
return ..()
if(!has_cell_power())
return 0
if(use_power == USE_POWER_IDLE)
cell.use(idle_power_usage * CELLRATE)
else if(use_power >= USE_POWER_ACTIVE)
cell.use(active_power_usage * CELLRATE)
return 1
//Processes the occupant, drawing from the internal power cell if needed.
/obj/machinery/recharge_station/proc/process_occupant()
if(isrobot(occupant))

View File

@@ -111,5 +111,5 @@
if(pow_chan)
var/delta = min(12, ER.chassis.cell.maxcharge-cur_charge)
ER.chassis.give_power(delta)
A.use_power(delta*ER.coeff, pow_chan)
A.use_power_oneoff(delta*ER.coeff, pow_chan)
return

View File

@@ -56,7 +56,7 @@
return
if(!A.powered(EQUIP))
return
A.use_power(EQUIP, 5000)
A.use_power_oneoff(5000, EQUIP)
var/light = A.power_light
A.updateicon()

View File

@@ -170,7 +170,7 @@ var/const/enterloopsanity = 100
if(isliving(M))
var/mob/living/L = M
L.handle_footstep(src)
..()
var/objects = 0
if(A && (A.flags & PROXMOVE))
for(var/atom/movable/thing in range(1))

View File

@@ -160,7 +160,7 @@
var/area/A = get_area(src)
if(A)
if(A.powered(EQUIP) && assembly.give_power(power_amount))
A.use_power(power_amount, EQUIP)
A.use_power_oneoff(power_amount, EQUIP)
// give_power() handles CELLRATE on its own.
// Interacts with the powernet.

View File

@@ -38,7 +38,7 @@
power_usage += tesla_link.passive_charging_rate
battery_module.battery.give(tesla_link.passive_charging_rate * CELLRATE)
A.use_power(power_usage, EQUIP)
A.use_power_oneoff(power_usage, EQUIP)
return TRUE
// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged

View File

@@ -12,10 +12,6 @@
// Power
//
// This will have this machine have its area eat this much power next tick, and not afterwards. Do not use for continued power draw.
/obj/machinery/proc/use_power_oneoff(var/amount, var/chan = -1)
return use_power(amount, chan)
// Change one of the power consumption vars
/obj/machinery/proc/change_power_consumption(new_power_consumption, use_power_mode = USE_POWER_IDLE)
switch(use_power_mode)
@@ -95,7 +91,7 @@
/obj/machinery/computer/ship/attack_ghost(mob/user)
interface_interact(user)
// If you don't call parent in this proc, you must make all appropriate checks yourself.
// If you don't call parent in this proc, you must make all appropriate checks yourself.
// If you do, you must respect the return value.
/obj/machinery/computer/ship/attack_hand(mob/user)
if((. = ..()))

View File

@@ -34,10 +34,10 @@
// controls power to devices in that area
// may be opened to change power cell
// three different channels (lighting/equipment/environ) - may each be set to on, off, or auto
#define POWERCHAN_OFF 0
#define POWERCHAN_OFF_AUTO 1
#define POWERCHAN_ON 2
#define POWERCHAN_ON_AUTO 3
#define POWERCHAN_OFF 0 // Power channel is off and will stay that way dammit
#define POWERCHAN_OFF_AUTO 1 // Power channel is off until power rises above a threshold
#define POWERCHAN_ON 2 // Power channel is on until there is no power
#define POWERCHAN_ON_AUTO 3 // Power channel is on until power drops below a threshold
//NOTE: STUFF STOLEN FROM AIRLOCK.DM thx
@@ -1037,10 +1037,9 @@
return 0
/obj/machinery/power/apc/process()
if(stat & (BROKEN|MAINT))
return
if(!area.requires_power)
return PROCESS_KILL
if(stat & (BROKEN|MAINT))
return
if(failure_timer)
update()
@@ -1049,9 +1048,9 @@
force_update = 1
return
lastused_light = area.usage(LIGHT)
lastused_equip = area.usage(EQUIP)
lastused_environ = area.usage(ENVIRON)
lastused_light = area.usage(LIGHT, lighting >= POWERCHAN_ON)
lastused_equip = area.usage(EQUIP, equipment >= POWERCHAN_ON)
lastused_environ = area.usage(ENVIRON, environ >= POWERCHAN_ON)
area.clear_usage()
lastused_total = lastused_light + lastused_equip + lastused_environ

View File

@@ -64,44 +64,6 @@
/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl.
return
// returns true if the area has power on given channel (or doesn't require power).
// defaults to power_channel
/obj/machinery/proc/powered(var/chan = -1) // defaults to power_channel
if(!src.loc)
return 0
//Don't do this. It allows machines that set use_power to 0 when off (many machines) to
//be turned on again and used after a power failure because they never gain the NOPOWER flag.
//if(!use_power)
// return 1
var/area/A = src.loc.loc // make sure it's in an area
if(!A || !isarea(A))
return 0 // if not, then not powered
if(chan == -1)
chan = power_channel
return A.powered(chan) // return power status of the area
// increment the power usage stats for an area
/obj/machinery/proc/use_power(var/amount, var/chan = -1) // defaults to power_channel
var/area/A = get_area(src) // make sure it's in an area
if(!A || !isarea(A))
return
if(chan == -1)
chan = power_channel
A.use_power(amount, chan)
/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change
// by default, check equipment channel & set flag
// can override if needed
if(powered(power_channel))
stat &= ~NOPOWER
else
stat |= NOPOWER
return
// connect the machine to a powernet if a node cable is present on the turf
/obj/machinery/power/proc/connect_to_network()
var/turf/T = src.loc
@@ -372,7 +334,7 @@
var/drained_energy = drained_hp*20
if (source_area)
source_area.use_power(drained_energy/CELLRATE)
source_area.use_power_oneoff(drained_energy/CELLRATE, EQUIP)
else if (istype(power_source,/datum/powernet))
var/drained_power = drained_energy/CELLRATE
drained_power = PN.draw_power(drained_power)

View File

@@ -267,7 +267,7 @@
return
if(chan == -1)
chan = power_channel
A.use_power(amount, chan)
A.use_power_oneoff(amount, chan)
/obj/machinery/portable_atmospherics/powered/reagent_distillery/process()
..()

View File

@@ -772,6 +772,7 @@
#include "code\game\machinery\jukebox.dm"
#include "code\game\machinery\lightswitch.dm"
#include "code\game\machinery\machinery.dm"
#include "code\game\machinery\machinery_power.dm"
#include "code\game\machinery\magnet.dm"
#include "code\game\machinery\mass_driver.dm"
#include "code\game\machinery\navbeacon.dm"