Files
Bubberstation/code/controllers/subsystem/atoms.dm
T
Ghom d4cdd6b63e Replaces lava and chasm's "safeties" and ignoring turf slowdown on catwalks with traits and a new element. (#76376)
## About The Pull Request
This adds a new element for movables that grants turfs they're in
traits, changes lava and the chasm component to check for traits
instead, ditto for turf slowdown. It also implements another trait that
prevents wet floor from slipping people, as well as some other changes
(feel free to opine on them really):
- Tables and conveyor belts now stop turf slowdown, much like catwalks,
as I imagine people walking on them are not really touching the floor.
(I'd include protection against lava too... until they melt, but that'd
mean finding a way to have these objects burn in the first place, and
lava code is still stupid despite a years old refactor I did)
- Tables also stop slippery turfs from slipping (bananas, soaps etc.
still apply). I wish there were a way to make some objects slippery by
coating them in water vapor or splashing water/lube, but that's outside
the scope of this PR.
- Fixed an edge case in which a mob standing on a lava turf would be
left permanently visually on fire if the lava is changed to another kind
of turf.
- Removed unused code from stone tiles.

I'm going to include these traits in that global list for admin-added
traits... tomorrow perhaps. 💤

## Why It's Good For The Game
Replacing some hard-coded mechanics with easier to use traits and an
element, which I also need for the submerge element PR.

## Changelog

🆑
refactor: Replaced hardcoded "safeties" for lava, chasms and ignoring
turf slowdowns on catwalks with traits.
balance: much like catwalks, tables and conveyors also disable turf
slowdowns.
balance: slippery turfs won't slip you when walking on a table.
fix: Fixed an edge case in which a mob standing on a lava turf would be
left visually but permanently on fire if the lava is changed to another
kind of turf.
/🆑
2023-07-07 10:04:33 +01:00

257 lines
7.8 KiB
Plaintext

#define SUBSYSTEM_INIT_SOURCE "subsystem init"
SUBSYSTEM_DEF(atoms)
name = "Atoms"
init_order = INIT_ORDER_ATOMS
flags = SS_NO_FIRE
/// A stack of list(source, desired initialized state)
/// We read the source of init changes from the last entry, and assert that all changes will come with a reset
var/list/initialized_state = list()
var/base_initialized
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
///initAtom() adds the atom its creating to this list iff InitializeAtoms() has been given a list to populate as an argument
var/list/created_atoms
/// Atoms that will be deleted once the subsystem is initialized
var/list/queued_deletions = list()
var/init_start_time
#ifdef PROFILE_MAPLOAD_INIT_ATOM
var/list/mapload_init_times = list()
#endif
initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/Initialize()
init_start_time = world.time
setupGenetics() //to set the mutations' sequence
initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
initialized = INITIALIZATION_INNEW_REGULAR
return SS_INIT_SUCCESS
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return)
if(initialized == INITIALIZATION_INSSATOMS)
return
set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, SUBSYSTEM_INIT_SOURCE)
// This may look a bit odd, but if the actual atom creation runtimes for some reason, we absolutely need to set initialized BACK
CreateAtoms(atoms, atoms_to_return)
clear_tracked_initalize(SUBSYSTEM_INIT_SOURCE)
if(late_loaders.len)
for(var/I in 1 to late_loaders.len)
var/atom/A = late_loaders[I]
//I hate that we need this
if(QDELETED(A))
continue
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
if (created_atoms)
atoms_to_return += created_atoms
created_atoms = null
for (var/queued_deletion in queued_deletions)
qdel(queued_deletion)
testing("[queued_deletions.len] atoms were queued for deletion.")
queued_deletions.Cut()
#ifdef PROFILE_MAPLOAD_INIT_ATOM
rustg_file_write(json_encode(mapload_init_times), "[GLOB.log_directory]/init_times.json")
#endif
/// Actually creates the list of atoms. Exists soley so a runtime in the creation logic doesn't cause initalized to totally break
/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null)
if (atoms_to_return)
LAZYINITLIST(created_atoms)
#ifdef TESTING
var/count
#endif
var/list/mapload_arg = list(TRUE)
if(atoms)
#ifdef TESTING
count = atoms.len
#endif
for(var/I in 1 to atoms.len)
var/atom/A = atoms[I]
if(!(A.flags_1 & INITIALIZED_1))
CHECK_TICK
PROFILE_INIT_ATOM_BEGIN()
InitAtom(A, TRUE, mapload_arg)
PROFILE_INIT_ATOM_END(A)
else
#ifdef TESTING
count = 0
#endif
for(var/atom/A as anything in world)
if(!(A.flags_1 & INITIALIZED_1))
PROFILE_INIT_ATOM_BEGIN()
InitAtom(A, FALSE, mapload_arg)
PROFILE_INIT_ATOM_END(A)
#ifdef TESTING
++count
#endif
CHECK_TICK
testing("Initialized [count] atoms")
/// Init this specific atom
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, from_template = FALSE, list/arguments)
var/the_type = A.type
if(QDELING(A))
// Check init_start_time to not worry about atoms created before the atoms SS that are cleaned up before this
if (A.gc_destroyed > init_start_time)
BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
return TRUE
// This is handled and battle tested by dreamchecker. Limit to UNIT_TESTS just in case that ever fails.
#ifdef UNIT_TESTS
var/start_tick = world.time
#endif
var/result = A.Initialize(arglist(arguments))
#ifdef UNIT_TESTS
if(start_tick != world.time)
BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
#endif
var/qdeleted = FALSE
switch(result)
if (INITIALIZE_HINT_NORMAL)
// pass
if(INITIALIZE_HINT_LATELOAD)
if(arguments[1]) //mapload
late_loaders += A
else
A.LateInitialize()
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.flags_1 & INITIALIZED_1))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
else
SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
var/atom/movable/location = A.loc
if(location)
/// Sends a signal that the new atom `src`, has been created at `loc`
SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1])
if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables
created_atoms += A.get_all_contents()
return qdeleted || QDELING(A)
/datum/controller/subsystem/atoms/proc/map_loader_begin(source)
set_tracked_initalized(INITIALIZATION_INSSATOMS, source)
/datum/controller/subsystem/atoms/proc/map_loader_stop(source)
clear_tracked_initalize(source)
/// Use this to set initialized to prevent error states where the old initialized is overriden, and we end up losing all context
/// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentially
/datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source)
if(!length(initialized_state))
base_initialized = initialized
initialized_state += list(list(source, state))
initialized = state
/datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source)
if(!length(initialized_state))
return
for(var/i in length(initialized_state) to 1 step -1)
if(initialized_state[i][1] == source)
initialized_state.Cut(i, i+1)
break
if(!length(initialized_state))
initialized = base_initialized
base_initialized = INITIALIZATION_INNEW_REGULAR
return
initialized = initialized_state[length(initialized_state)][2]
/// Returns TRUE if anything is currently being initialized
/datum/controller/subsystem/atoms/proc/initializing_something()
return length(initialized_state) > 1
/datum/controller/subsystem/atoms/Recover()
initialized = SSatoms.initialized
if(initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
initialized_state = SSatoms.initialized_state
BadInitializeCalls = SSatoms.BadInitializeCalls
/datum/controller/subsystem/atoms/proc/setupGenetics()
var/list/mutations = subtypesof(/datum/mutation/human)
shuffle_inplace(mutations)
for(var/A in subtypesof(/datum/generecipe))
var/datum/generecipe/GR = A
GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result)
for(var/i in 1 to LAZYLEN(mutations))
var/path = mutations[i] //byond gets pissy when we do it in one line
var/datum/mutation/human/B = new path ()
B.alias = "Mutation [i]"
GLOB.all_mutations[B.type] = B
GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks)
GLOB.alias_mutations[B.alias] = B.type
if(B.locked)
continue
if(B.quality == POSITIVE)
GLOB.good_mutations |= B
else if(B.quality == NEGATIVE)
GLOB.bad_mutations |= B
else if(B.quality == MINOR_NEGATIVE)
GLOB.not_good_mutations |= B
CHECK_TICK
/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(mapload)\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"
/// Prepares an atom to be deleted once the atoms SS is initialized.
/datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target)
if (initialized == INITIALIZATION_INNEW_REGULAR)
// Atoms SS has already completed, just kill it now.
qdel(target)
else
queued_deletions += WEAKREF(target)
/datum/controller/subsystem/atoms/Shutdown()
var/initlog = InitLog()
if(initlog)
text2file(initlog, "[GLOB.log_directory]/initialize.log")
#undef SUBSYSTEM_INIT_SOURCE