Merge branch 'master' of https://github.com/PolarisSS13/Polaris into teshhair2
@@ -53,6 +53,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
|
||||
// Subsystem init_order, from highest priority to lowest priority
|
||||
// Subsystems shutdown in the reverse of the order they initialize in
|
||||
// The numbers just define the ordering, they are meaningless otherwise.
|
||||
#define INIT_ORDER_MISC_EARLY 60
|
||||
#define INIT_ORDER_WEBHOOKS 50
|
||||
#define INIT_ORDER_SQLITE 40
|
||||
#define INIT_ORDER_INPUT 37
|
||||
|
||||
@@ -253,29 +253,50 @@
|
||||
/proc/key_name_admin(var/whom, var/include_name = 1)
|
||||
return key_name(whom, 1, include_name)
|
||||
|
||||
// Helper procs for building detailed log lines
|
||||
/datum/proc/log_info_line()
|
||||
return "[src] ([type])"
|
||||
|
||||
/atom/log_info_line()
|
||||
var/turf/t = get_turf(src)
|
||||
if(istype(t))
|
||||
return "([t]) ([t.x],[t.y],[t.z]) ([t.type])"
|
||||
else if(loc)
|
||||
return "([loc]) (0,0,0) ([loc.type])"
|
||||
else
|
||||
return "(NULL) (0,0,0) (NULL)"
|
||||
|
||||
/mob/log_info_line()
|
||||
return "[..()] ([ckey])"
|
||||
|
||||
/proc/log_info_line(var/datum/d)
|
||||
if(!istype(d))
|
||||
return
|
||||
return d.log_info_line()
|
||||
|
||||
/mob/proc/simple_info_line()
|
||||
return "[key_name(src)] ([x],[y],[z])"
|
||||
|
||||
|
||||
/client/proc/simple_info_line()
|
||||
return "[key_name(src)] ([mob.x],[mob.y],[mob.z])"
|
||||
|
||||
|
||||
/proc/log_info_line(datum/thing)
|
||||
if (isnull(thing))
|
||||
return "*null*"
|
||||
if (islist(thing))
|
||||
var/list/result = list()
|
||||
var/list/thing_list = thing
|
||||
for (var/key in thing_list)
|
||||
var/value = isnum(key) ? null : thing[key]
|
||||
result += "[log_info_line(key)][value ? " - [log_info_line(value)]" : ""]"
|
||||
return "\[[jointext(result, ", ")]\]"
|
||||
if (!istype(thing))
|
||||
return json_encode(thing)
|
||||
return thing.get_log_info_line()
|
||||
|
||||
|
||||
/datum/proc/get_log_info_line()
|
||||
return "[src] ([type])"
|
||||
|
||||
|
||||
/weakref/get_log_info_line()
|
||||
return "[ref_name] ([ref_type]) ([ref]) (WEAKREF)"
|
||||
|
||||
|
||||
/area/get_log_info_line()
|
||||
return "[..()] ([isnum(z) ? "[x],[y],[z]" : "0,0,0"])"
|
||||
|
||||
|
||||
/turf/get_log_info_line()
|
||||
return "[..()] ([x],[y],[z]) ([loc ? loc.type : "NULL"])"
|
||||
|
||||
|
||||
/atom/movable/get_log_info_line()
|
||||
var/turf/turf = get_turf(src)
|
||||
return "[..()] ([turf ? turf : "NULL"]) ([turf ? "[turf.x],[turf.y],[turf.z]" : "0,0,0"]) ([turf ? turf.type : "NULL"])"
|
||||
|
||||
|
||||
/mob/get_log_info_line()
|
||||
return ckey ? "[..()] ([ckey])" : ..()
|
||||
|
||||
@@ -27,11 +27,6 @@ SUBSYSTEM_DEF(antags)
|
||||
var/datum/antagonist/A = new antag_type
|
||||
antag_datums[A.id] = A
|
||||
|
||||
/datum/controller/subsystem/antags/Shutdown()
|
||||
for(var/thing in antag_datums)
|
||||
qdel(thing)
|
||||
. = ..()
|
||||
|
||||
|
||||
/datum/controller/subsystem/antags/proc/get_antag_id_from_name(var/role_text)
|
||||
for(var/datum/antagonist/A as anything in antag_datums)
|
||||
|
||||
@@ -1,159 +1,119 @@
|
||||
#define BAD_INIT_QDEL_BEFORE 1
|
||||
#define BAD_INIT_DIDNT_INIT 2
|
||||
#define BAD_INIT_SLEPT 4
|
||||
#define BAD_INIT_NO_HINT 8
|
||||
|
||||
SUBSYSTEM_DEF(atoms)
|
||||
name = "Atoms"
|
||||
init_order = INIT_ORDER_ATOMS
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
// override and GetArguments() exists for mod-override/downstream hook functionality.
|
||||
// Useful for total-overhaul type modifications.
|
||||
var/adjust_init_arguments = FALSE
|
||||
// Bad initialization types.
|
||||
var/const/QDEL_BEFORE_INITIALIZE = 1
|
||||
var/const/DID_NOT_SET_INITIALIZED = 2
|
||||
var/const/SLEPT_IN_INITIALIZE = 4
|
||||
var/const/DID_NOT_RETURN_HINT = 8
|
||||
|
||||
var/atom_init_stage = INITIALIZATION_INSSATOMS
|
||||
var/old_init_stage
|
||||
var/static/atom_init_stage = INITIALIZATION_INSSATOMS
|
||||
var/static/old_init_stage
|
||||
var/static/list/late_loaders = list()
|
||||
var/static/list/created_atoms = list()
|
||||
var/static/list/bad_init_calls = list()
|
||||
|
||||
var/list/late_loaders
|
||||
var/list/BadInitializeCalls = list()
|
||||
|
||||
/datum/controller/subsystem/atoms/Initialize(timeofday)
|
||||
if(!plant_controller) // Initialize seed repo for /obj/item/seed and /obj/item/grown
|
||||
plant_controller = new
|
||||
setupgenetics() //to set the mutations' place in structural enzymes, so initializers know where to put mutations.
|
||||
/datum/controller/subsystem/atoms/Initialize(start_uptime)
|
||||
atom_init_stage = INITIALIZATION_INNEW_MAPLOAD
|
||||
InitializeAtoms()
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitializeAtoms(var/list/supplied_atoms)
|
||||
|
||||
if(atom_init_stage <= INITIALIZATION_INSSATOMS_LATE)
|
||||
return
|
||||
|
||||
atom_init_stage = INITIALIZATION_INNEW_MAPLOAD
|
||||
|
||||
LAZYINITLIST(late_loaders)
|
||||
|
||||
var/list/mapload_arg = list(TRUE)
|
||||
var/count = LAZYLEN(supplied_atoms)
|
||||
if(count)
|
||||
while(supplied_atoms.len)
|
||||
var/atom/A = supplied_atoms[supplied_atoms.len]
|
||||
supplied_atoms.len--
|
||||
if(!A.initialized)
|
||||
InitAtom(A, GetArguments(A, mapload_arg))
|
||||
CHECK_TICK
|
||||
else if(!subsystem_initialized)
|
||||
// If wondering why not just store all atoms in a list and use the block above: that turns out unbearably expensive.
|
||||
// Instead, atoms without extra arguments in New created on server start are fished out of world directly.
|
||||
// We do this exactly once.
|
||||
|
||||
for(var/atom/A in world)
|
||||
if(!A.initialized)
|
||||
InitAtom(A, GetArguments(A, mapload_arg, FALSE))
|
||||
++count
|
||||
CHECK_TICK
|
||||
|
||||
report_progress("Initialized [count] atom\s")
|
||||
|
||||
atom_init_stage = INITIALIZATION_INNEW_REGULAR
|
||||
|
||||
if(late_loaders.len)
|
||||
for(var/I in late_loaders)
|
||||
var/atom/A = I
|
||||
A.LateInitialize(arglist(late_loaders[A]))
|
||||
report_progress("Late initialized [late_loaders.len] atom\s")
|
||||
late_loaders.Cut()
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
|
||||
LAZYREMOVE(global.pre_init_created_atoms, A)
|
||||
var/the_type = A.type
|
||||
if(QDELING(A))
|
||||
BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
|
||||
return TRUE
|
||||
|
||||
var/start_tick = world.time
|
||||
|
||||
var/result = A.Initialize(arglist(arguments))
|
||||
|
||||
if(start_tick != world.time)
|
||||
BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
|
||||
|
||||
var/qdeleted = FALSE
|
||||
|
||||
if(result != INITIALIZE_HINT_NORMAL)
|
||||
switch(result)
|
||||
if(INITIALIZE_HINT_LATELOAD)
|
||||
if(arguments[1]) //mapload
|
||||
late_loaders[A] = arguments
|
||||
else
|
||||
A.LateInitialize(arglist(arguments))
|
||||
if(INITIALIZE_HINT_QDEL)
|
||||
qdel(A)
|
||||
qdeleted = TRUE
|
||||
else
|
||||
BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
|
||||
|
||||
if(!A) //possible harddel
|
||||
qdeleted = TRUE
|
||||
else if(!A.initialized)
|
||||
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
|
||||
|
||||
return qdeleted || QDELING(A)
|
||||
|
||||
// override and GetArguments() exists for mod-override/downstream hook functionality.
|
||||
// Useful for total-overhaul type modifications.
|
||||
/atom/proc/AdjustInitializeArguments(list/arguments)
|
||||
// Lists are passed by reference so can simply modify the arguments list without returning it
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/GetArguments(atom/A, list/mapload_arg, created=TRUE)
|
||||
if(!created && !adjust_init_arguments)
|
||||
return mapload_arg // Performance optimization. Nothing to do.
|
||||
var/list/arguments = mapload_arg.Copy()
|
||||
var/extra_args = LAZYACCESS(global.pre_init_created_atoms, A)
|
||||
if(created && extra_args)
|
||||
arguments += extra_args
|
||||
if(adjust_init_arguments)
|
||||
A.AdjustInitializeArguments(arguments)
|
||||
return arguments
|
||||
|
||||
/datum/controller/subsystem/atoms/stat_entry(msg)
|
||||
..("Bad Initialize Calls:[BadInitializeCalls.len]")
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/map_loader_begin()
|
||||
old_init_stage = atom_init_stage
|
||||
atom_init_stage = INITIALIZATION_INSSATOMS_LATE
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/map_loader_stop()
|
||||
atom_init_stage = old_init_stage
|
||||
|
||||
/datum/controller/subsystem/atoms/Recover()
|
||||
atom_init_stage = SSatoms.atom_init_stage
|
||||
if(atom_init_stage == INITIALIZATION_INNEW_MAPLOAD)
|
||||
created_atoms.Cut()
|
||||
late_loaders.Cut()
|
||||
if (atom_init_stage == INITIALIZATION_INNEW_MAPLOAD)
|
||||
InitializeAtoms()
|
||||
old_init_stage = SSatoms.old_init_stage
|
||||
BadInitializeCalls = SSatoms.BadInitializeCalls
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitLog()
|
||||
. = ""
|
||||
for(var/path in BadInitializeCalls)
|
||||
. += "Path : [path] \n"
|
||||
var/fails = BadInitializeCalls[path]
|
||||
if(fails & BAD_INIT_DIDNT_INIT)
|
||||
. += "- Didn't call atom/Initialize()\n"
|
||||
if(fails & BAD_INIT_NO_HINT)
|
||||
. += "- Didn't return an Initialize hint\n"
|
||||
if(fails & BAD_INIT_QDEL_BEFORE)
|
||||
. += "- Qdel'd in New()\n"
|
||||
if(fails & BAD_INIT_SLEPT)
|
||||
. += "- Slept during Initialize()\n"
|
||||
|
||||
/datum/controller/subsystem/atoms/Shutdown()
|
||||
var/initlog = InitLog()
|
||||
if(initlog)
|
||||
text2file(initlog, "[log_path]-initialize.log")
|
||||
if (!initlog)
|
||||
return
|
||||
text2file(initlog, "[log_path]/initialize.log")
|
||||
|
||||
#undef BAD_INIT_QDEL_BEFORE
|
||||
#undef BAD_INIT_DIDNT_INIT
|
||||
#undef BAD_INIT_SLEPT
|
||||
#undef BAD_INIT_NO_HINT
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitializeAtoms()
|
||||
if (atom_init_stage <= INITIALIZATION_INSSATOMS_LATE)
|
||||
return
|
||||
atom_init_stage = INITIALIZATION_INNEW_MAPLOAD
|
||||
var/list/mapload_arg = list(TRUE)
|
||||
var/count = 0
|
||||
var/atom/created
|
||||
var/list/arguments
|
||||
for (var/i = 1 to length(created_atoms))
|
||||
created = created_atoms[i]
|
||||
if (!created.initialized)
|
||||
arguments = created_atoms[created] ? mapload_arg + created_atoms[created] : mapload_arg
|
||||
InitAtom(created, arguments)
|
||||
CHECK_TICK
|
||||
created_atoms.Cut()
|
||||
if (!subsystem_initialized)
|
||||
for (var/atom/atom in world)
|
||||
if (!atom.initialized)
|
||||
InitAtom(atom, mapload_arg)
|
||||
++count
|
||||
CHECK_TICK
|
||||
report_progress("Initialized [count] atom\s")
|
||||
atom_init_stage = INITIALIZATION_INNEW_REGULAR
|
||||
if (!length(late_loaders))
|
||||
return
|
||||
for (var/atom/atom as anything in late_loaders)
|
||||
atom.LateInitialize(arglist(late_loaders[atom]))
|
||||
report_progress("Late initialized [length(late_loaders)] atom\s")
|
||||
late_loaders.Cut()
|
||||
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitAtom(atom/atom, list/arguments)
|
||||
var/atom_type = atom?.type
|
||||
if (QDELING(atom))
|
||||
bad_init_calls[atom_type] |= QDEL_BEFORE_INITIALIZE
|
||||
return TRUE
|
||||
var/start_tick = world.time
|
||||
var/result = atom.Initialize(arglist(arguments))
|
||||
if (start_tick != world.time)
|
||||
bad_init_calls[atom_type] |= SLEPT_IN_INITIALIZE
|
||||
var/qdeleted = FALSE
|
||||
if (result != INITIALIZE_HINT_NORMAL)
|
||||
switch (result)
|
||||
if (INITIALIZE_HINT_LATELOAD)
|
||||
if (arguments[1]) //mapload
|
||||
late_loaders[atom] = arguments
|
||||
else
|
||||
atom.LateInitialize(arglist(arguments))
|
||||
if (INITIALIZE_HINT_QDEL)
|
||||
qdel(atom)
|
||||
qdeleted = TRUE
|
||||
else
|
||||
bad_init_calls[atom_type] |= DID_NOT_RETURN_HINT
|
||||
if (!atom)
|
||||
qdeleted = TRUE
|
||||
else if (!atom.initialized)
|
||||
bad_init_calls[atom_type] |= DID_NOT_SET_INITIALIZED
|
||||
return qdeleted || QDELING(atom)
|
||||
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/BeginMapLoad()
|
||||
old_init_stage = atom_init_stage
|
||||
atom_init_stage = INITIALIZATION_INSSATOMS_LATE
|
||||
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/FinishMapLoad()
|
||||
atom_init_stage = old_init_stage
|
||||
|
||||
|
||||
/datum/controller/subsystem/atoms/proc/InitLog()
|
||||
. = ""
|
||||
for (var/path in bad_init_calls)
|
||||
. += "Path : [path] \n"
|
||||
var/fails = bad_init_calls[path]
|
||||
if (fails & DID_NOT_SET_INITIALIZED)
|
||||
. += "- Didn't call atom/Initialize()\n"
|
||||
if (fails & DID_NOT_RETURN_HINT)
|
||||
. += "- Didn't return an Initialize hint\n"
|
||||
if (fails & QDEL_BEFORE_INITIALIZE)
|
||||
. += "- Qdel'd in New()\n"
|
||||
if (fails & SLEPT_IN_INITIALIZE)
|
||||
. += "- Slept during Initialize()\n"
|
||||
|
||||
@@ -273,71 +273,73 @@ SUBSYSTEM_DEF(garbage)
|
||||
|
||||
// Should be treated as a replacement for the 'del' keyword.
|
||||
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
|
||||
/proc/qdel(datum/D, force=FALSE)
|
||||
if(!istype(D))
|
||||
del(D)
|
||||
/proc/qdel(datum/thing, force)
|
||||
if (!thing)
|
||||
return
|
||||
var/datum/qdel_item/I = SSgarbage.items[D.type]
|
||||
if (!I)
|
||||
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
|
||||
I.qdels++
|
||||
|
||||
|
||||
if(isnull(D.gc_destroyed))
|
||||
if(SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
|
||||
if (!istype(thing))
|
||||
crash_with("qdel() can only handle /datum (sub)types, was passed: [log_info_line(thing)]")
|
||||
del(thing)
|
||||
return
|
||||
var/datum/qdel_item/qdel_item = SSgarbage.items[thing.type]
|
||||
if (!qdel_item)
|
||||
qdel_item = new (thing.type)
|
||||
SSgarbage.items[thing.type] = qdel_item
|
||||
qdel_item.qdels++
|
||||
if (isnull(thing.gc_destroyed))
|
||||
if (SEND_SIGNAL(thing, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
|
||||
return
|
||||
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
|
||||
thing.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
|
||||
var/start_time = world.time
|
||||
var/start_tick = world.tick_usage
|
||||
SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
|
||||
var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up.
|
||||
if(world.time != start_time)
|
||||
I.slept_destroy++
|
||||
SEND_SIGNAL(thing, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
|
||||
var/hint = thing.Destroy(force) // Let our friend know they're about to get fucked up.
|
||||
if (world.time != start_time)
|
||||
qdel_item.slept_destroy++
|
||||
else
|
||||
I.destroy_time += TICK_USAGE_TO_MS(start_tick)
|
||||
if(!D)
|
||||
qdel_item.destroy_time += TICK_USAGE_TO_MS(start_tick)
|
||||
if (!thing)
|
||||
return
|
||||
switch(hint)
|
||||
switch (hint)
|
||||
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
|
||||
SSgarbage.PreQueue(D)
|
||||
SSgarbage.PreQueue(thing)
|
||||
if (QDEL_HINT_IWILLGC)
|
||||
D.gc_destroyed = world.time
|
||||
thing.gc_destroyed = world.time
|
||||
return
|
||||
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
|
||||
if(!force)
|
||||
D.gc_destroyed = null //clear the gc variable (important!)
|
||||
thing.gc_destroyed = null //clear the gc variable (important!)
|
||||
return
|
||||
// Returning LETMELIVE after being told to force destroy
|
||||
// indicates the objects Destroy() does not respect force
|
||||
#ifdef TESTING
|
||||
if(!I.no_respect_force)
|
||||
crash_with("[D.type] has been force deleted, but is \
|
||||
#ifdef TESTING
|
||||
if(!qdel_item.no_respect_force)
|
||||
crash_with("[thing.type] has been force deleted, but is \
|
||||
returning an immortal QDEL_HINT, indicating it does \
|
||||
not respect the force flag for qdel(). It has been \
|
||||
placed in the queue, further instances of this type \
|
||||
will also be queued.")
|
||||
#endif
|
||||
I.no_respect_force++
|
||||
|
||||
SSgarbage.PreQueue(D)
|
||||
#endif
|
||||
qdel_item.no_respect_force++
|
||||
SSgarbage.PreQueue(thing)
|
||||
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
|
||||
SSgarbage.HardQueue(D)
|
||||
SSgarbage.HardQueue(thing)
|
||||
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
|
||||
SSgarbage.HardDelete(D)
|
||||
SSgarbage.HardDelete(thing)
|
||||
if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
|
||||
SSgarbage.PreQueue(D)
|
||||
SSgarbage.PreQueue(thing)
|
||||
#ifdef TESTING
|
||||
D.find_references()
|
||||
thing.find_references()
|
||||
#endif
|
||||
else
|
||||
#ifdef TESTING
|
||||
if(!I.no_hint)
|
||||
crash_with("[D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
|
||||
if (!qdel_item.no_hint)
|
||||
crash_with("[thing.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
|
||||
#endif
|
||||
I.no_hint++
|
||||
SSgarbage.PreQueue(D)
|
||||
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
|
||||
qdel_item.no_hint++
|
||||
SSgarbage.PreQueue(thing)
|
||||
else if (thing.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
|
||||
CRASH("[thing.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
|
||||
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
SUBSYSTEM_DEF(init_misc_early)
|
||||
name = "Early Misc Initialization"
|
||||
init_order = INIT_ORDER_MISC_EARLY
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
|
||||
/datum/controller/subsystem/init_misc_early/Initialize(timeofday)
|
||||
plant_controller = new
|
||||
setupgenetics()
|
||||
@@ -1,26 +1,30 @@
|
||||
//obtain a weak reference to a datum
|
||||
/proc/weakref(datum/D)
|
||||
if(!istype(D))
|
||||
return
|
||||
if(QDELETED(D))
|
||||
return
|
||||
if(!D.weakref)
|
||||
D.weakref = new/weakref(D)
|
||||
return D.weakref
|
||||
|
||||
/weakref
|
||||
var/ref
|
||||
var/ref_name
|
||||
var/ref_type
|
||||
|
||||
/weakref/New(datum/D)
|
||||
ref = "\ref[D]"
|
||||
|
||||
/weakref/Destroy()
|
||||
// A weakref datum should not be manually destroyed as it is a shared resource,
|
||||
// rather it should be automatically collected by the BYOND GC when all references are gone.
|
||||
return QDEL_HINT_LETMELIVE
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
return QDEL_HINT_IWILLGC
|
||||
|
||||
|
||||
/weakref/New(datum/thing)
|
||||
ref = "\ref[thing]"
|
||||
ref_name = "[thing]"
|
||||
ref_type = thing.type
|
||||
|
||||
|
||||
/weakref/proc/resolve()
|
||||
var/datum/D = locate(ref)
|
||||
if(D && D.weakref == src)
|
||||
return D
|
||||
return null
|
||||
var/datum/thing = locate(ref)
|
||||
if (thing && thing.weakref == src)
|
||||
return thing
|
||||
return null
|
||||
|
||||
|
||||
/proc/weakref(datum/thing)
|
||||
if (!istype(thing) || QDELING(thing))
|
||||
return
|
||||
if (!thing.weakref)
|
||||
thing.weakref = new /weakref (thing)
|
||||
return thing.weakref
|
||||
|
||||
@@ -133,15 +133,19 @@
|
||||
var/stasis_level = 3 //Every 'this' life ticks are applied to the mob (when life_ticks%stasis_level == 1)
|
||||
var/obj/item/reagent_containers/syringe/syringe
|
||||
|
||||
/obj/structure/closet/body_bag/cryobag/Initialize()
|
||||
tank = new tank_type(null) //It's in nullspace to prevent ejection when the bag is opened.
|
||||
..()
|
||||
|
||||
/obj/structure/closet/body_bag/cryobag/Destroy()
|
||||
QDEL_NULL(syringe)
|
||||
QDEL_NULL(tank)
|
||||
return ..()
|
||||
|
||||
|
||||
/obj/structure/closet/body_bag/cryobag/Initialize()
|
||||
. = ..()
|
||||
if (ispath(tank_type, /obj/item/tank))
|
||||
tank = new tank_type // no loc to prevent tank being dropped when opened
|
||||
|
||||
|
||||
/obj/structure/closet/body_bag/cryobag/attack_hand(mob/living/user)
|
||||
if(used)
|
||||
var/confirm = alert(user, "Are you sure you want to open \the [src]? \
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
//Let's get some REAL contraband stuff in here. Because come on, getting brigged for LIPSTICK is no fun.
|
||||
//
|
||||
// Includes drug powder.
|
||||
//
|
||||
//Illicit drugs~
|
||||
/obj/item/storage/pill_bottle/happy
|
||||
name = "bottle of Happy pills"
|
||||
desc = "A recreational drug. When you want to see the rainbow. Probably not work-approved..."
|
||||
wrapper_color = COLOR_PINK
|
||||
starts_with = list(/obj/item/reagent_containers/pill/happy = 7)
|
||||
|
||||
|
||||
/obj/item/storage/pill_bottle/zoom
|
||||
name = "bottle of Zoom pills"
|
||||
desc = "Probably illegal. Trade brain for speed."
|
||||
wrapper_color = COLOR_BLUE
|
||||
starts_with = list(/obj/item/reagent_containers/pill/zoom = 7)
|
||||
|
||||
|
||||
/obj/item/reagent_containers/glass/beaker/vial/random
|
||||
flags = 0
|
||||
var/list/random_reagent_list = list(list("water" = 15) = 1, list("cleaner" = 15) = 1)
|
||||
|
||||
|
||||
/obj/item/reagent_containers/glass/beaker/vial/random/toxin
|
||||
random_reagent_list = list(
|
||||
list("mindbreaker" = 10, "bliss" = 20) = 3,
|
||||
@@ -26,26 +24,21 @@
|
||||
list("impedrezene" = 15) = 2,
|
||||
list("zombiepowder" = 10) = 1)
|
||||
|
||||
|
||||
/obj/item/reagent_containers/glass/beaker/vial/random/Initialize()
|
||||
. = ..()
|
||||
if(is_open_container())
|
||||
flags ^= OPENCONTAINER
|
||||
|
||||
var/list/picked_reagents = pickweight(random_reagent_list)
|
||||
for(var/reagent in picked_reagents)
|
||||
reagents.add_reagent(reagent, picked_reagents[reagent])
|
||||
|
||||
var/list/names = new
|
||||
for(var/datum/reagent/R in reagents.reagent_list)
|
||||
names += R.name
|
||||
|
||||
desc = "Contains [english_list(names)]."
|
||||
update_icon()
|
||||
|
||||
/*/////////////////////////////////////
|
||||
// DRUG POWDER //
|
||||
//////////////////////////////////////
|
||||
*/
|
||||
|
||||
/obj/item/reagent_containers/powder
|
||||
name = "powder"
|
||||
desc = "A powdered form of... something."
|
||||
@@ -57,46 +50,47 @@
|
||||
w_class = ITEMSIZE_TINY
|
||||
volume = 50
|
||||
|
||||
/// The name of the reagent with the most volume in this powder.
|
||||
var/main_reagent_name
|
||||
|
||||
|
||||
/obj/item/reagent_containers/powder/Initialize(mapload, datum/reagents/initial_reagents)
|
||||
. = ..()
|
||||
if (istype(initial_reagents))
|
||||
initial_reagents.trans_to_holder(reagents, volume)
|
||||
else if (islist(initial_reagents))
|
||||
var/list/initial_reagents_list = initial_reagents
|
||||
for (var/reagent_type in initial_reagents_list)
|
||||
reagents.add_reagent(reagent_type, initial_reagents[reagent_type])
|
||||
if (!reagents.total_volume)
|
||||
return INITIALIZE_HINT_QDEL
|
||||
var/datum/reagent/main_reagent = reagents.get_master_reagent()
|
||||
main_reagent_name = lowertext(main_reagent.name)
|
||||
color = reagents.get_color()
|
||||
|
||||
|
||||
/obj/item/reagent_containers/powder/examine(mob/user)
|
||||
if(reagents)
|
||||
var/datum/reagent/R = reagents.get_master_reagent()
|
||||
desc = "A powdered form of what appears to be [R.name]. There's about [reagents.total_volume] units here."
|
||||
return ..()
|
||||
. = ..()
|
||||
if (isliving(user) && get_dist(user, src) > 2)
|
||||
return
|
||||
. += "It seems to be about [reagents.total_volume] units of [main_reagent_name]."
|
||||
|
||||
/obj/item/reagent_containers/powder/Initialize()
|
||||
..()
|
||||
get_appearance()
|
||||
|
||||
/obj/item/reagent_containers/powder/proc/get_appearance()
|
||||
/// Names and colors based on dominant reagent.
|
||||
if (reagents.reagent_list.len > 0)
|
||||
color = reagents.get_color()
|
||||
var/datum/reagent/R = reagents.get_master_reagent()
|
||||
var/new_name = lowertext(R)
|
||||
name = "powdered [new_name]"
|
||||
|
||||
/// Snorting.
|
||||
|
||||
/obj/item/reagent_containers/powder/attackby(var/obj/item/W, var/mob/living/user)
|
||||
|
||||
if(!ishuman(user)) /// You gotta be fleshy to snort the naughty drugs.
|
||||
/obj/item/reagent_containers/powder/attackby(obj/item/item, mob/living/user)
|
||||
if (!ishuman(user))
|
||||
return ..()
|
||||
|
||||
if(!istype(W, /obj/item/glass_extra/straw) && !istype(W, /obj/item/reagent_containers/rollingpaper))
|
||||
if (!istype(item, /obj/item/glass_extra/straw) && !istype(item, /obj/item/reagent_containers/rollingpaper))
|
||||
return ..()
|
||||
|
||||
user.visible_message("<span class='warning'>[user] snorts [src] with [W]!</span>")
|
||||
reagents.trans_to_mob(user, amount_per_transfer_from_this, CHEM_BLOOD)
|
||||
var/used_up = !reagents.total_volume
|
||||
user.visible_message(
|
||||
SPAN_ITALIC("\The [user] snorts some [name] with \a [item]."),
|
||||
SPAN_ITALIC("You snort [used_up ? "the last" : "some"] of the [main_reagent_name] with \the [item].")
|
||||
)
|
||||
playsound(loc, 'sound/effects/snort.ogg', 50, 1)
|
||||
|
||||
if(reagents)
|
||||
reagents.trans_to_mob(user, amount_per_transfer_from_this, CHEM_BLOOD)
|
||||
|
||||
if(!reagents.total_volume) /// Did we use all of it?
|
||||
if (used_up)
|
||||
qdel(src)
|
||||
|
||||
////// End powder. ///////////
|
||||
//////////////////////////////
|
||||
///// Drugs for loadout///////
|
||||
|
||||
/obj/item/storage/pill_bottle/bliss
|
||||
name = "unlabeled pill bottle"
|
||||
@@ -126,4 +120,4 @@
|
||||
/obj/item/storage/pill_bottle/schnappi
|
||||
name = "unlabeled pill bottle"
|
||||
desc = "A pill bottle with its label suspiciously scratched out."
|
||||
starts_with = list(/obj/item/reagent_containers/pill/unidentified/schnappi = 7)
|
||||
starts_with = list(/obj/item/reagent_containers/pill/unidentified/schnappi = 7)
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
var/drop_get_turf = TRUE
|
||||
var/start_anomalous = FALSE
|
||||
|
||||
// creates a new object and deletes itself
|
||||
|
||||
/obj/random/Initialize()
|
||||
. = INITIALIZE_HINT_QDEL
|
||||
..()
|
||||
if(!prob(spawn_nothing_percentage))
|
||||
try_spawn_item()
|
||||
return INITIALIZE_HINT_QDEL
|
||||
if (prob(spawn_nothing_percentage))
|
||||
return
|
||||
try_spawn_item()
|
||||
|
||||
|
||||
/obj/random/proc/try_spawn_item()
|
||||
var/atom/result = spawn_item()
|
||||
|
||||
@@ -404,13 +404,13 @@
|
||||
// As this is only done at runtime, we have to create all the vending machines in existence and force them
|
||||
// to register their products when this asset initializes.
|
||||
/datum/asset/spritesheet/vending/proc/populate_vending_products()
|
||||
SSatoms.map_loader_begin()
|
||||
SSatoms.BeginMapLoad()
|
||||
for(var/path in subtypesof(/obj/machinery/vending))
|
||||
var/obj/machinery/vending/x = new path(null)
|
||||
// force an inventory build; with map_loader_begin active, init isn't called
|
||||
// force an inventory build; with BeginMapLoad active, init isn't called
|
||||
x.build_inventory()
|
||||
qdel(x)
|
||||
SSatoms.map_loader_stop()
|
||||
SSatoms.FinishMapLoad()
|
||||
|
||||
// /datum/asset/simple/genetics
|
||||
// assets = list(
|
||||
|
||||
@@ -730,6 +730,24 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
||||
pref.h_style = new_h_style
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if (href_list["hair_style_left"])
|
||||
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||
var/index = valid_hairstyles.Find(href_list["hair_style_left"])
|
||||
if (!index || index == 1)
|
||||
pref.h_style = valid_hairstyles[length(valid_hairstyles)]
|
||||
else
|
||||
pref.h_style = valid_hairstyles[index - 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if (href_list["hair_style_right"])
|
||||
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||
var/index = valid_hairstyles.Find(href_list["hair_style_right"])
|
||||
if (!index || index == length(valid_hairstyles))
|
||||
pref.h_style = valid_hairstyles[1]
|
||||
else
|
||||
pref.h_style = valid_hairstyles[index + 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["grad_style"])
|
||||
var/list/valid_gradients = GLOB.hair_gradients
|
||||
|
||||
@@ -738,26 +756,22 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
||||
pref.grad_style = new_grad_style
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["hair_style_left"])
|
||||
var/H = href_list["hair_style_left"]
|
||||
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||
var/start = valid_hairstyles.Find(H)
|
||||
|
||||
if(start != 1) //If we're not the beginning of the list, become the previous element.
|
||||
pref.h_style = valid_hairstyles[start-1]
|
||||
else //But if we ARE, become the final element.
|
||||
pref.h_style = valid_hairstyles[valid_hairstyles.len]
|
||||
else if (href_list["grad_style_left"])
|
||||
var/list/valid_hair_gradients = GLOB.hair_gradients
|
||||
var/index = valid_hair_gradients.Find(href_list["grad_style_left"])
|
||||
if (!index || index == 1)
|
||||
pref.grad_style = valid_hair_gradients[length(valid_hair_gradients)]
|
||||
else
|
||||
pref.grad_style = valid_hair_gradients[index - 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["hair_style_right"])
|
||||
var/H = href_list["hair_style_right"]
|
||||
var/list/valid_hairstyles = pref.get_valid_hairstyles()
|
||||
var/start = valid_hairstyles.Find(H)
|
||||
|
||||
if(start != valid_hairstyles.len) //If we're not the end of the list, become the next element.
|
||||
pref.h_style = valid_hairstyles[start+1]
|
||||
else //But if we ARE, become the first element.
|
||||
pref.h_style = valid_hairstyles[1]
|
||||
else if (href_list["grad_style_right"])
|
||||
var/list/valid_hair_gradients = GLOB.hair_gradients
|
||||
var/index = valid_hair_gradients.Find(href_list["grad_style_right"])
|
||||
if (!index || index == length(valid_hair_gradients))
|
||||
pref.grad_style = valid_hair_gradients[1]
|
||||
else
|
||||
pref.grad_style = valid_hair_gradients[index + 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["facial_color"])
|
||||
@@ -806,26 +820,22 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
|
||||
pref.f_style = new_f_style
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["facial_style_left"])
|
||||
var/F = href_list["facial_style_left"]
|
||||
else if (href_list["facial_style_left"])
|
||||
var/list/valid_facialhairstyles = pref.get_valid_facialhairstyles()
|
||||
var/start = valid_facialhairstyles.Find(F)
|
||||
|
||||
if(start != 1) //If we're not the beginning of the list, become the previous element.
|
||||
pref.f_style = valid_facialhairstyles[start-1]
|
||||
else //But if we ARE, become the final element.
|
||||
pref.f_style = valid_facialhairstyles[valid_facialhairstyles.len]
|
||||
var/index = valid_facialhairstyles.Find(href_list["facial_style_left"])
|
||||
if (!index || index == 1)
|
||||
pref.f_style = valid_facialhairstyles[length(valid_facialhairstyles)]
|
||||
else
|
||||
pref.f_style = valid_facialhairstyles[index - 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["facial_style_right"])
|
||||
var/F = href_list["facial_style_right"]
|
||||
else if (href_list["facial_style_right"])
|
||||
var/list/valid_facialhairstyles = pref.get_valid_facialhairstyles()
|
||||
var/start = valid_facialhairstyles.Find(F)
|
||||
|
||||
if(start != valid_facialhairstyles.len) //If we're not the end of the list, become the next element.
|
||||
pref.f_style = valid_facialhairstyles[start+1]
|
||||
else //But if we ARE, become the first element.
|
||||
var/index = valid_facialhairstyles.Find(href_list["facial_style_right"])
|
||||
if (!index || index == length(valid_facialhairstyles))
|
||||
pref.f_style = valid_facialhairstyles[1]
|
||||
else
|
||||
pref.f_style = valid_facialhairstyles[index + 1]
|
||||
return TOPIC_REFRESH_UPDATE_PREVIEW
|
||||
|
||||
else if(href_list["marking_style"])
|
||||
|
||||
@@ -153,6 +153,36 @@
|
||||
desc = "A starry winter hood."
|
||||
icon_state = "winterhood_cosmic"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_red
|
||||
name = "red parka hood"
|
||||
desc = "A red fur-lined hood."
|
||||
icon_state = "redpark_hood"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_green
|
||||
name = "green parka hood"
|
||||
desc = "A green fur-lined hood."
|
||||
icon_state = "greenpark_hood"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_blue
|
||||
name = "blue parka hood"
|
||||
desc = "A blue fur-lined hood."
|
||||
icon_state = "bluepark_hood"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_yellow
|
||||
name = "yellow parka hood"
|
||||
desc = "A yellow fur-lined hood."
|
||||
icon_state = "yellowpark_hood"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_purple
|
||||
name = "purple parka hood"
|
||||
desc = "A purple fur-lined hood."
|
||||
icon_state = "purplepark_hood"
|
||||
|
||||
/obj/item/clothing/head/hood/winter/parka_vintage
|
||||
name = "vintage parka hood"
|
||||
desc = "An old-fashioned fur-lined hood."
|
||||
icon_state = "vintagepark_hood"
|
||||
|
||||
// Explorer gear
|
||||
/obj/item/clothing/head/hood/explorer
|
||||
name = "explorer hood"
|
||||
|
||||
@@ -335,6 +335,45 @@
|
||||
light_power = 1.8
|
||||
light_range = 1.2
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka
|
||||
name = "red parka"
|
||||
desc = "A heavy fur-lined jacket designed to keep you extra warm in sub-zero conditions."
|
||||
icon_state = "redpark"
|
||||
item_state_slots = list(slot_r_hand_str = "coatwinter", slot_l_hand_str = "coatwinter")
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_red
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/green
|
||||
name = "green parka"
|
||||
icon_state = "greenpark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_green
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/blue
|
||||
name = "blue parka"
|
||||
icon_state = "bluepark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_blue
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/yellow
|
||||
name = "yellow parka"
|
||||
icon_state = "yellowpark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_yellow
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/purple
|
||||
name = "purple parka"
|
||||
icon_state = "purplepark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_purple
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen
|
||||
name = "corporate blue parka"
|
||||
desc = "A NanoTrasen branded fur-lined jacket made to keep you nice and toasty on cold winter days. Or at least alive"
|
||||
icon_state = "corppark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_blue //No point having a unique hood when it's just blue anyway!
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/vintage
|
||||
name = "vintage parka"
|
||||
desc = "An old-fashioned fur-lined jacket made to keep you nice and toasty on cold winter days. Or at least alive."
|
||||
icon_state = "vintagepark"
|
||||
hoodtype = /obj/item/clothing/head/hood/winter/parka_vintage
|
||||
|
||||
// winter coats end here
|
||||
|
||||
/obj/item/clothing/suit/storage/hooded/explorer
|
||||
|
||||
@@ -366,7 +366,7 @@ var/global/use_preloader = FALSE
|
||||
first_turf_index++
|
||||
|
||||
//turn off base new Initialization until the whole thing is loaded
|
||||
SSatoms.map_loader_begin()
|
||||
SSatoms.BeginMapLoad()
|
||||
//instanciate the first /turf
|
||||
var/turf/T
|
||||
if(members[first_turf_index] != /turf/template_noop)
|
||||
@@ -385,7 +385,7 @@ var/global/use_preloader = FALSE
|
||||
for(index in 1 to first_turf_index-1)
|
||||
instance_atom(members[index],members_attributes[index],crds,no_changeturf,orientation)
|
||||
//Restore initialization to the previous value
|
||||
SSatoms.map_loader_stop()
|
||||
SSatoms.FinishMapLoad()
|
||||
|
||||
////////////////
|
||||
//Helpers procs
|
||||
@@ -406,9 +406,9 @@ var/global/use_preloader = FALSE
|
||||
|
||||
//custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize
|
||||
if(TICK_CHECK)
|
||||
SSatoms.map_loader_stop()
|
||||
SSatoms.FinishMapLoad()
|
||||
stoplag()
|
||||
SSatoms.map_loader_begin()
|
||||
SSatoms.BeginMapLoad()
|
||||
|
||||
// Rotate the atom now that it exists, rather than changing its orientation beforehand through the fields["dir"]
|
||||
if(orientation != 0) // 0 means no rotation
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
if (istype(loc, /turf/space))
|
||||
return ..() - 1
|
||||
|
||||
if(species.slowdown)
|
||||
. += species.slowdown
|
||||
. += species.get_slowdown(src)
|
||||
|
||||
if(force_max_speed)
|
||||
return ..() + HUMAN_LOWEST_SLOWDOWN
|
||||
@@ -263,4 +262,4 @@
|
||||
/mob/living/carbon/human/set_dir(var/new_dir)
|
||||
. = ..()
|
||||
if(. && species.tail)
|
||||
update_tail_showing()
|
||||
update_tail_showing()
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
handle_shock()
|
||||
|
||||
handle_pain()
|
||||
|
||||
|
||||
handle_allergens()
|
||||
|
||||
handle_medical_side_effects()
|
||||
@@ -167,11 +167,12 @@
|
||||
var/pressure_difference
|
||||
|
||||
// First get the absolute pressure difference.
|
||||
if(pressure < species.safe_pressure) // We are in an underpressure.
|
||||
pressure_difference = species.safe_pressure - pressure
|
||||
var/species_safe_pressure = species.get_safe_pressure(src)
|
||||
if(pressure < species_safe_pressure) // We are in an underpressure.
|
||||
pressure_difference = species_safe_pressure - pressure
|
||||
|
||||
else //We are in an overpressure or standard atmosphere.
|
||||
pressure_difference = pressure - species.safe_pressure
|
||||
pressure_difference = pressure - species_safe_pressure
|
||||
|
||||
if(pressure_difference < 5) // If the difference is small, don't bother calculating the fraction.
|
||||
pressure_difference = 0
|
||||
@@ -184,10 +185,10 @@
|
||||
// The difference is always positive to avoid extra calculations.
|
||||
// Apply the relative difference on a standard atmosphere to get the final result.
|
||||
// The return value will be the adjusted_pressure of the human that is the basis of pressure warnings and damage.
|
||||
if(pressure < species.safe_pressure)
|
||||
return species.safe_pressure - pressure_difference
|
||||
if(pressure < species_safe_pressure)
|
||||
return species_safe_pressure - pressure_difference
|
||||
else
|
||||
return species.safe_pressure + pressure_difference
|
||||
return species_safe_pressure + pressure_difference
|
||||
|
||||
/mob/living/carbon/human/handle_disabilities()
|
||||
..()
|
||||
@@ -565,7 +566,7 @@
|
||||
else
|
||||
failed_last_breath = 0
|
||||
adjustOxyLoss(-5)
|
||||
|
||||
|
||||
if(!does_not_breathe && client) // If we breathe, and have an active client, check if we have synthetic lungs.
|
||||
var/obj/item/organ/internal/lungs/L = internal_organs_by_name[O_LUNGS]
|
||||
var/turf = get_turf(src)
|
||||
@@ -588,7 +589,7 @@
|
||||
to_chat(src, "<span class='danger'>You feel your face burning and a searing heat in your lungs!</span>")
|
||||
|
||||
if(breath.temperature >= species.heat_discomfort_level)
|
||||
|
||||
|
||||
if(breath.temperature >= species.breath_heat_level_3)
|
||||
apply_damage(HEAT_GAS_DAMAGE_LEVEL_3, BURN, BP_HEAD, used_weapon = "Excessive Heat")
|
||||
throw_alert("temp", /obj/screen/alert/hot, HOT_ALERT_SEVERITY_MAX)
|
||||
@@ -641,25 +642,25 @@
|
||||
|
||||
breath.update_values()
|
||||
return 1
|
||||
|
||||
|
||||
/mob/living/carbon/human/proc/play_inhale(var/mob/living/M, var/exhale)
|
||||
var/suit_inhale_sound
|
||||
if(species.suit_inhale_sound)
|
||||
suit_inhale_sound = species.suit_inhale_sound
|
||||
else // Failsafe
|
||||
suit_inhale_sound = 'sound/effects/mob_effects/suit_breathe_in.ogg'
|
||||
|
||||
|
||||
playsound_local(get_turf(src), suit_inhale_sound, 100, pressure_affected = FALSE, volume_channel = VOLUME_CHANNEL_AMBIENCE)
|
||||
if(!exhale) // Did we fail exhale? If no, play it after inhale finishes.
|
||||
addtimer(CALLBACK(src, .proc/play_exhale, M), 5 SECONDS)
|
||||
|
||||
|
||||
/mob/living/carbon/human/proc/play_exhale(var/mob/living/M)
|
||||
var/suit_exhale_sound
|
||||
if(species.suit_exhale_sound)
|
||||
suit_exhale_sound = species.suit_exhale_sound
|
||||
else // Failsafe
|
||||
suit_exhale_sound = 'sound/effects/mob_effects/suit_breathe_out.ogg'
|
||||
|
||||
|
||||
playsound_local(get_turf(src), suit_exhale_sound, 100, pressure_affected = FALSE, volume_channel = VOLUME_CHANNEL_AMBIENCE)
|
||||
|
||||
/mob/living/carbon/human/proc/handle_allergens()
|
||||
@@ -729,7 +730,7 @@
|
||||
else
|
||||
loc_temp = environment.temperature
|
||||
|
||||
if(adjusted_pressure < species.warning_high_pressure && adjusted_pressure > species.warning_low_pressure && abs(loc_temp - bodytemperature) < 20 && bodytemperature < species.heat_level_1 && bodytemperature > species.cold_level_1)
|
||||
if(adjusted_pressure < species.get_warning_high_pressure(src) && adjusted_pressure > species.get_warning_low_pressure(src) && abs(loc_temp - bodytemperature) < 20 && bodytemperature < species.heat_level_1 && bodytemperature > species.cold_level_1)
|
||||
clear_alert("pressure")
|
||||
return // Temperatures are within normal ranges, fuck all this processing. ~Ccomp
|
||||
|
||||
@@ -798,15 +799,16 @@
|
||||
if(status_flags & GODMODE)
|
||||
return 1 //godmode
|
||||
|
||||
if(adjusted_pressure >= species.hazard_high_pressure)
|
||||
var/pressure_damage = min( ( (adjusted_pressure / species.hazard_high_pressure) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE)
|
||||
var/species_hazard_high_pressure = species.get_hazard_high_pressure(src)
|
||||
if(adjusted_pressure >= species_hazard_high_pressure)
|
||||
var/pressure_damage = min( ( (adjusted_pressure / species_hazard_high_pressure) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE)
|
||||
take_overall_damage(brute=pressure_damage, used_weapon = "High Pressure")
|
||||
throw_alert("pressure", /obj/screen/alert/highpressure, 2)
|
||||
else if(adjusted_pressure >= species.warning_high_pressure)
|
||||
else if(adjusted_pressure >= species.get_warning_high_pressure(src))
|
||||
throw_alert("pressure", /obj/screen/alert/highpressure, 1)
|
||||
else if(adjusted_pressure >= species.warning_low_pressure)
|
||||
else if(adjusted_pressure >= species.get_warning_low_pressure(src))
|
||||
clear_alert("pressure")
|
||||
else if(adjusted_pressure >= species.hazard_low_pressure)
|
||||
else if(adjusted_pressure >= species.get_hazard_low_pressure(src))
|
||||
throw_alert("pressure", /obj/screen/alert/lowpressure, 1)
|
||||
else
|
||||
if( !(COLD_RESISTANCE in mutations))
|
||||
@@ -814,7 +816,7 @@
|
||||
if(getOxyLoss() < 55) // 12 OxyLoss per 4 ticks when wearing internals; unconsciousness in 16 ticks, roughly half a minute
|
||||
var/pressure_dam = 3 // 16 OxyLoss per 4 ticks when no internals present; unconsciousness in 13 ticks, roughly twenty seconds
|
||||
// (Extra 1 oxyloss from failed breath)
|
||||
// Being in higher pressure decreases the damage taken, down to a minimum of (species.hazard_low_pressure / ONE_ATMOSPHERE) at species.hazard_low_pressure
|
||||
// Being in higher pressure decreases the damage taken, down to a minimum of (species.get_hazard_low_pressure(src) / ONE_ATMOSPHERE) at species.get_hazard_low_pressure(src)
|
||||
pressure_dam *= (ONE_ATMOSPHERE - adjusted_pressure) / ONE_ATMOSPHERE
|
||||
|
||||
if(wear_suit && wear_suit.min_pressure_protection && head && head.min_pressure_protection)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
/datum/unarmed_attack/claws/strong,
|
||||
/datum/unarmed_attack/bite/strong
|
||||
)
|
||||
|
||||
rarity_value = 5
|
||||
blurb = "The Vox are the broken remnants of a once-proud race, now reduced to little more than \
|
||||
scavenging vermin who prey on isolated stations, ships or planets to keep their own ancient arkships \
|
||||
@@ -40,9 +41,6 @@
|
||||
male_sneeze_sound = 'sound/voice/shrieksneeze.ogg'
|
||||
female_sneeze_sound = 'sound/voice/shrieksneeze.ogg'
|
||||
|
||||
warning_low_pressure = 50
|
||||
hazard_low_pressure = 0
|
||||
|
||||
cold_level_1 = 210 //Default 260
|
||||
cold_level_2 = 150 //Default 200
|
||||
cold_level_3 = 90 //Default 120
|
||||
@@ -98,6 +96,8 @@
|
||||
default_emotes = list(
|
||||
/decl/emote/audible/vox_shriek
|
||||
)
|
||||
inherent_verbs = list(/mob/living/carbon/human/proc/toggle_vox_pressure_seal)
|
||||
var/list/current_pressure_toggle = list()
|
||||
|
||||
/datum/species/vox/get_random_name(var/gender)
|
||||
var/datum/language/species_language = GLOB.all_languages[default_language]
|
||||
@@ -106,3 +106,57 @@
|
||||
/datum/species/vox/equip_survival_gear(var/mob/living/carbon/human/H, var/extendedtank = 0,var/comprehensive = 0)
|
||||
. = ..()
|
||||
H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/vox(H), slot_wear_mask)
|
||||
|
||||
/datum/species/vox/get_slowdown(var/mob/living/carbon/human/H)
|
||||
if(current_pressure_toggle["\ref[H]"])
|
||||
return 1.5
|
||||
return ..()
|
||||
|
||||
/datum/species/vox/get_warning_low_pressure(var/mob/living/carbon/human/H)
|
||||
if(current_pressure_toggle["\ref[H]"])
|
||||
return 50
|
||||
return ..()
|
||||
|
||||
/datum/species/vox/get_hazard_low_pressure(var/mob/living/carbon/human/H)
|
||||
if(current_pressure_toggle["\ref[H]"])
|
||||
return 0
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/proc/toggle_vox_pressure_seal()
|
||||
set name = "Toggle Vox Pressure Seal"
|
||||
set category = "Abilities"
|
||||
set src = usr
|
||||
|
||||
if(!istype(species, /datum/species/vox))
|
||||
verbs -= /mob/living/carbon/human/proc/toggle_vox_pressure_seal
|
||||
return
|
||||
|
||||
if(incapacitated(INCAPACITATION_KNOCKOUT))
|
||||
to_chat(src, SPAN_WARNING("You are in no state to do that."))
|
||||
return
|
||||
|
||||
var/datum/gender/G = gender_datums[get_visible_gender()]
|
||||
visible_message(SPAN_NOTICE("\The [src] begins flexing and realigning [G.his] scaling..."))
|
||||
if(!do_after(src, 2 SECONDS, src, FALSE))
|
||||
visible_message(
|
||||
SPAN_NOTICE("\The [src] ceases adjusting [G.his] scaling."),
|
||||
self_message = SPAN_WARNING("You must remain still to seal or unseal your scaling."))
|
||||
return
|
||||
|
||||
if(incapacitated(INCAPACITATION_KNOCKOUT))
|
||||
to_chat(src, SPAN_WARNING("You are in no state to do that."))
|
||||
return
|
||||
|
||||
// TODO: maybe add cold and heat thresholds to this.
|
||||
var/my_ref = "\ref[src]"
|
||||
var/datum/species/vox/kikiki = species
|
||||
if((kikiki.current_pressure_toggle[my_ref] = !kikiki.current_pressure_toggle[my_ref]))
|
||||
visible_message(
|
||||
SPAN_NOTICE("\The [src]'s scaling flattens and smooths out."),
|
||||
self_message = SPAN_NOTICE("You flatten your scaling and inflate internal bladders, protecting yourself against low pressure at the cost of dexterity.")
|
||||
)
|
||||
else
|
||||
visible_message(
|
||||
SPAN_NOTICE("\The [src]'s scaling bristles roughly."),
|
||||
self_message = SPAN_NOTICE("You bristle your scaling and deflate your internal bladders, restoring mobility but leaving yourself vulnerable to low pressure.")
|
||||
)
|
||||
|
||||
@@ -113,3 +113,21 @@
|
||||
|
||||
/datum/species/proc/get_vision_flags(var/mob/living/carbon/human/H)
|
||||
return vision_flags
|
||||
|
||||
/datum/species/proc/get_hazard_high_pressure(var/mob/living/carbon/human/H)
|
||||
return hazard_high_pressure
|
||||
|
||||
/datum/species/proc/get_warning_high_pressure(var/mob/living/carbon/human/H)
|
||||
return warning_high_pressure
|
||||
|
||||
/datum/species/proc/get_warning_low_pressure(var/mob/living/carbon/human/H)
|
||||
return warning_low_pressure
|
||||
|
||||
/datum/species/proc/get_hazard_low_pressure(var/mob/living/carbon/human/H)
|
||||
return hazard_low_pressure
|
||||
|
||||
/datum/species/proc/get_safe_pressure(var/mob/living/carbon/human/H)
|
||||
return safe_pressure
|
||||
|
||||
/datum/species/proc/get_slowdown(var/mob/living/carbon/human/H)
|
||||
return slowdown
|
||||
|
||||
@@ -225,7 +225,7 @@ var/global/datum/species/shapeshifter/promethean/prometheans
|
||||
var/datum/gas_mixture/environment = T.return_air()
|
||||
var/pressure = environment.return_pressure()
|
||||
var/affecting_pressure = H.calculate_affecting_pressure(pressure)
|
||||
if(affecting_pressure <= hazard_low_pressure) // Dangerous low pressure stops the regeneration of physical wounds. Body is focusing on keeping them intact rather than sealing.
|
||||
if(affecting_pressure <= get_hazard_low_pressure(H)) // Dangerous low pressure stops the regeneration of physical wounds. Body is focusing on keeping them intact rather than sealing.
|
||||
regen_brute = FALSE
|
||||
regen_burn = FALSE
|
||||
|
||||
|
||||
@@ -1721,22 +1721,49 @@ shaved
|
||||
//Skrell 'hairstyles'
|
||||
|
||||
/datum/sprite_accessory/hair/skr
|
||||
name = "Skrell Average Tentacles"
|
||||
icon_state = "skrell_hair_average"
|
||||
name = "Tentacles, Average"
|
||||
icon_state = "skrell_short"
|
||||
species_allowed = list(SPECIES_SKRELL, SPECIES_EVENT1, SPECIES_EVENT2, SPECIES_EVENT3)
|
||||
|
||||
/datum/sprite_accessory/hair/skr/tentacle_veryshort
|
||||
name = "Skrell Short Tentacles"
|
||||
icon_state = "skrell_hair_short"
|
||||
gender = MALE
|
||||
/datum/sprite_accessory/hair/skr/pullback
|
||||
name = "Tentacles, Average, Pullback"
|
||||
icon_state = "skrell_short_pullback"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/tentacle_average
|
||||
name = "Skrell Long Tentacles"
|
||||
icon_state = "skrell_hair_long"
|
||||
/datum/sprite_accessory/hair/skr/very_short
|
||||
name = "Tentacles, Short"
|
||||
icon_state = "skrell_very_short"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/tentacle_verylong
|
||||
name = "Skrell Very Long Tentacles"
|
||||
icon_state = "skrell_hair_verylong"
|
||||
/datum/sprite_accessory/hair/skr/long
|
||||
name = "Tentacles, Long"
|
||||
icon_state = "skrell_long"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/long/pullback
|
||||
name = "Tentacles, Long, Pullback"
|
||||
icon_state = "skrell_long_pullback"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/long/scarf
|
||||
name = "Tentacles, Long, Scarf"
|
||||
icon_state = "skrell_long_scarf"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/long/wavy
|
||||
name = "Tentacles, Long, Wavy"
|
||||
icon_state = "skrell_long_wavy"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/very_long
|
||||
name = "Tentacles, Very Long"
|
||||
icon_state = "skrell_very_long"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/very_long/pullback
|
||||
name = "Tentacles, Very Long, Pullback"
|
||||
icon_state = "skrell_very_long_pullback"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/very_long/scarf
|
||||
name = "Tentacles, Very Long, Scarf"
|
||||
icon_state = "skrell_very_long_scarf"
|
||||
|
||||
/datum/sprite_accessory/hair/skr/very_long/wavy
|
||||
name = "Tentacles, Very Long, Wavy"
|
||||
icon_state = "skrell_very_long_wavy"
|
||||
|
||||
//Tajaran hairstyles
|
||||
/datum/sprite_accessory/hair/taj
|
||||
|
||||
@@ -91,27 +91,16 @@
|
||||
|
||||
return
|
||||
|
||||
/obj/item/reagent_containers/pill/attackby(obj/item/W as obj, mob/user as mob)
|
||||
if(is_sharp(W))
|
||||
var/obj/item/reagent_containers/powder/J = new /obj/item/reagent_containers/powder(src.loc)
|
||||
user.visible_message("<span class='warning'>[user] gently cuts up [src] with [W]!</span>")
|
||||
playsound(src.loc, 'sound/effects/chop.ogg', 50, 1)
|
||||
|
||||
if(reagents)
|
||||
reagents.trans_to_obj(J, reagents.total_volume)
|
||||
J.get_appearance()
|
||||
/obj/item/reagent_containers/pill/attackby(obj/item/item, mob/living/user)
|
||||
if (is_sharp(item) || istype(item, /obj/item/card))
|
||||
user.visible_message(
|
||||
SPAN_ITALIC("\The [user] cuts up \a [src] with \a [item]."),
|
||||
SPAN_ITALIC("You cut up \the [src] with \the [item].")
|
||||
)
|
||||
playsound(loc, 'sound/effects/chop.ogg', 50, 1)
|
||||
new /obj/item/reagent_containers/powder (loc, reagents)
|
||||
qdel(src)
|
||||
|
||||
if(istype(W, /obj/item/card/id))
|
||||
var/obj/item/reagent_containers/powder/J = new /obj/item/reagent_containers/powder(src.loc)
|
||||
user.visible_message("<span class='warning'>[user] clumsily chops up [src] with [W]!</span>")
|
||||
playsound(src.loc, 'sound/effects/chop.ogg', 50, 1)
|
||||
|
||||
if(reagents)
|
||||
reagents.trans_to_obj(J, reagents.total_volume)
|
||||
J.get_appearance()
|
||||
qdel(src)
|
||||
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
name = "SUBSYSTEM - ATOMS: Shall have no bad init calls"
|
||||
|
||||
/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls/start_test()
|
||||
if(SSatoms.BadInitializeCalls.len)
|
||||
if(SSatoms.bad_init_calls.len)
|
||||
log_bad(jointext(SSatoms.InitLog(), null))
|
||||
fail("[SSatoms] had bad initialization calls.")
|
||||
else
|
||||
@@ -35,7 +35,7 @@
|
||||
var/fail = FALSE
|
||||
for(var/atom/atom in world)
|
||||
if(!atom.initialized && !QDELETED(atom)) // Not ideal to skip over qdeleted atoms, but a lot of current code uses pre-init qdels
|
||||
log_bad("Uninitialized atom: [atom.type] - [atom.log_info_line()]")
|
||||
log_bad("Uninitialized atom: [atom.type] - [atom.get_log_info_line()]")
|
||||
fail = TRUE
|
||||
if(fail)
|
||||
fail("There were uninitialized atoms.")
|
||||
|
||||
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
@@ -1147,8 +1147,6 @@
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/effect/floor_decal/steeldecal/steel_decals_central7{
|
||||
dir = 8
|
||||
},
|
||||
@@ -1168,6 +1166,8 @@
|
||||
},
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/turf/simulated/floor/tiled,
|
||||
/area/surface/station/arrivals/cynosure)
|
||||
"aGP" = (
|
||||
@@ -2610,12 +2610,12 @@
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/melee/umbrella/random{
|
||||
pixel_y = -4
|
||||
},
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/turf/simulated/floor/tiled,
|
||||
/area/surface/station/arrivals/cynosure)
|
||||
"bkP" = (
|
||||
@@ -27785,6 +27785,12 @@
|
||||
name = "Biohazard Shutter"
|
||||
},
|
||||
/obj/structure/closet/emcloset,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/melee/umbrella/random,
|
||||
/turf/simulated/floor/tiled/techfloor,
|
||||
/area/surface/station/rnd/hallway/gnd)
|
||||
"mHN" = (
|
||||
@@ -35441,12 +35447,12 @@
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/melee/umbrella/random{
|
||||
pixel_y = -4
|
||||
},
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/turf/simulated/floor/tiled,
|
||||
/area/surface/station/arrivals/cynosure)
|
||||
"pVb" = (
|
||||
@@ -42243,9 +42249,8 @@
|
||||
/obj/item/melee/umbrella/random{
|
||||
pixel_y = -4
|
||||
},
|
||||
/obj/effect/floor_decal/arrows,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/turf/simulated/floor/plating,
|
||||
/area/surface/station/hallway/primary/groundfloor/south)
|
||||
"sWe" = (
|
||||
@@ -50374,14 +50379,14 @@
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/shoes/boots/winter,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat,
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/item/melee/umbrella/random,
|
||||
/obj/machinery/camera/network/ground_floor{
|
||||
c_tag = "Ground Floor - Arrivals North";
|
||||
dir = 8
|
||||
},
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/obj/item/clothing/suit/storage/hooded/wintercoat/parka/nanotrasen,
|
||||
/turf/simulated/floor/tiled,
|
||||
/area/surface/station/arrivals/cynosure)
|
||||
"wNJ" = (
|
||||
|
||||