[MIRROR] Speeds up the preference menu, significantly. Adds object pooling, other stuff too [MDB IGNORE] (#9962)

* Speeds up the preference menu, significantly. Adds object pooling, other stuff too

* First fixes

* Feex

* Quick fix for the unit test to shut up

* Fixing the runtime with randomly-colored jumpsuits

* Fixes that one hard del

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
Co-authored-by: GoldenAlpharex <jerego1234@hotmail.com>
This commit is contained in:
SkyratBot
2021-12-10 02:22:41 +00:00
committed by GitHub
co-authored by LemonInTheDark GoldenAlpharex GoldenAlpharex
parent f7a4750b59
commit b7164873d4
51 changed files with 859 additions and 123 deletions
+20 -1
View File
@@ -260,7 +260,7 @@
#define SSAIR_SUPERCONDUCTIVITY 7
#define SSAIR_PROCESS_ATOMS 8
//Pipeline rebuild helper defines, these suck but it'll do for now
//Pipeline rebuild helper defines, these suck but it'll do for now //Fools you actually merged it
#define SSAIR_REBUILD_PIPELINE 1
#define SSAIR_REBUILD_QUEUE 2
@@ -269,6 +269,25 @@
#define SSEXPLOSIONS_TURFS 2
#define SSEXPLOSIONS_THROWS 3
// Wardrobe subsystem tasks
#define SSWARDROBE_STOCK 1
#define SSWARDROBE_INSPECT 2
//Wardrobe cache metadata indexes
#define WARDROBE_CACHE_COUNT 1
#define WARDROBE_CACHE_LAST_INSPECT 2
#define WARDROBE_CACHE_CALL_INSERT 3
#define WARDROBE_CACHE_CALL_REMOVAL 4
//Wardrobe preloaded stock indexes
#define WARDROBE_STOCK_CONTENTS 1
#define WARDROBE_STOCK_CALL_INSERT 2
#define WARDROBE_STOCK_CALL_REMOVAL 3
//Wardrobe callback master list indexes
#define WARDROBE_CALLBACK_INSERT 1
#define WARDROBE_CALLBACK_REMOVE 2
// Subsystem delta times or tickrates, in seconds. I.e, how many seconds in between each process() call for objects being processed by that subsystem.
// Only use these defines if you want to access some other objects processing delta_time, otherwise use the delta_time that is sent as a parameter to process()
#define SSFLUIDS_DT (SSfluids.wait/10)
+358
View File
@@ -0,0 +1,358 @@
/// This subsystem strives to make loading large amounts of select objects as smooth at execution as possible
/// It preloads a set of types to store, and caches them until requested
/// Doesn't catch everything mind, this is intentional. There's many types that expect to either
/// A: Not sit in a list for 2 hours, or B: have extra context passed into them, or for their parent to be their location
/// You should absolutely not spam this system, it will break things in new and wonderful ways
/// S close enough for government work though.
/// Fuck you goonstation
SUBSYSTEM_DEF(wardrobe)
name = "Wardrobe"
wait = 10 // This is more like a queue then anything else
flags = SS_BACKGROUND
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT // We're going to fill up our cache while players sit in the lobby
/// How much to cache outfit items
/// Multiplier, 2 would mean cache enough items to stock 1 of each preloaded order twice, etc
var/cache_intensity = 2
/// How many more then the template of a type are we allowed to have before we delete applicants?
var/overflow_lienency = 2
/// List of type -> list(insertion callback, removal callback) callbacks for insertion/removal to use.
/// Set in setup_callbacks, used in canonization.
var/list/initial_callbacks = list()
/// Canonical list of types required to fill all preloaded stocks once.
/// Type -> list(count, last inspection timestamp, call on insert, call on removal)
var/list/canon_minimum = list()
/// List of types to load. Type -> count //(I'd do a list of lists but this needs to be refillable)
var/list/order_list = list()
/// List of lists. Contains our preloaded atoms. Type -> list(last inspect time, list(instances))
var/list/preloaded_stock = list()
/// The last time we inspected our stock
var/last_inspect_time = 0
/// How often to inspect our stock, in deciseconds
var/inspect_delay = 30 SECONDS
/// What we're currently doing
var/current_task = SSWARDROBE_STOCK
/// How many times we've had to generate a stock item on request
var/stock_miss = 0
/// How many times we've successfully returned a cached item
var/stock_hit = 0
/// How many items would we make just by loading the master list once?
var/one_go_master = 0
/datum/controller/subsystem/wardrobe/Initialize(start_timeofday)
. = ..()
setup_callbacks()
load_outfits()
load_species()
load_pda_nicknacks()
load_storage_contents()
hard_refresh_queue()
stock_hit = 0
stock_miss = 0
/// Resets the load queue to the master template, accounting for the existing stock
/datum/controller/subsystem/wardrobe/proc/hard_refresh_queue()
for(var/datum/type_to_queue as anything in canon_minimum)
var/list/master_info = canon_minimum[type_to_queue]
var/amount_to_load = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/list/stock_info = preloaded_stock[type_to_queue]
if(stock_info) // If we already have stuff, reduce the amount we load
amount_to_load -= length(stock_info[WARDROBE_STOCK_CONTENTS])
set_queue_item(type_to_queue, amount_to_load)
/datum/controller/subsystem/wardrobe/stat_entry(msg)
var/total_provided = max(stock_hit + stock_miss, 1)
var/current_max_store = (one_go_master * cache_intensity) + (overflow_lienency * length(canon_minimum))
msg += " P:[length(canon_minimum)] Q:[length(order_list)] S:[length(preloaded_stock)] I:[cache_intensity] O:[overflow_lienency]"
msg += " H:[stock_hit] M:[stock_miss] T:[total_provided] H/T:[PERCENT(stock_hit / total_provided)]% M/T:[PERCENT(stock_miss / total_provided)]%"
msg += " MAX:[current_max_store]"
msg += " ID:[inspect_delay] NI:[last_inspect_time + inspect_delay]"
return ..()
/datum/controller/subsystem/wardrobe/fire(resumed=FALSE)
if(current_task != SSWARDROBE_INSPECT && world.time - last_inspect_time >= inspect_delay)
current_task = SSWARDROBE_INSPECT
switch(current_task)
if(SSWARDROBE_STOCK)
stock_wardrobe()
if(SSWARDROBE_INSPECT)
run_inspection()
if(state != SS_RUNNING)
return
current_task = SSWARDROBE_STOCK
last_inspect_time = world.time
/// Turns the order list into actual loaded items, this is where most work is done
/datum/controller/subsystem/wardrobe/proc/stock_wardrobe()
for(var/atom/movable/type_to_stock as anything in order_list)
var/amount_to_stock = order_list[type_to_stock]
for(var/i in 1 to amount_to_stock)
if(MC_TICK_CHECK)
order_list[type_to_stock] = (amount_to_stock - (i - 1)) // Account for types we've already created
return
var/atom/movable/new_member = new type_to_stock()
stash_object(new_member)
order_list -= type_to_stock
if(MC_TICK_CHECK)
return
/// Once every medium while, go through the current stock and make sure we don't have too much of one thing
/// Or that we're not too low on some other stock
/// This exists as a failsafe, so the wardrobe doesn't just end up generating too many items or accidentially running out somehow
/datum/controller/subsystem/wardrobe/proc/run_inspection()
for(var/datum/loaded_type as anything in canon_minimum)
var/list/master_info = canon_minimum[loaded_type]
var/last_looked_at = master_info[WARDROBE_CACHE_LAST_INSPECT]
if(last_looked_at == last_inspect_time)
continue
var/list/stock_info = preloaded_stock[loaded_type]
var/amount_held = 0
if(stock_info)
var/list/held_objects = stock_info[WARDROBE_STOCK_CONTENTS]
amount_held = length(held_objects)
var/target_stock = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/target_delta = amount_held - target_stock
// If we've got too much
if(target_delta > overflow_lienency)
unload_stock(loaded_type, target_delta - overflow_lienency)
if(state != SS_RUNNING)
return
// If we have more then we target, just don't you feel me?
target_delta = min(target_delta, 0) //I only want negative numbers to matter here
// If we don't have enough, queue enough to make up the remainder
// If we have too much in the queue, cull to 0. We do this so time isn't wasted creating and destroying entries
set_queue_item(loaded_type, abs(target_delta))
master_info[WARDROBE_CACHE_LAST_INSPECT] = last_inspect_time
if(MC_TICK_CHECK)
return
/// Takes a path to get the callback owner for
/// Returns the deepest path in our callback store that matches the input
/// The hope is this will prevent dumb conflicts, since the furthest down is always going to be the most relevant
/datum/controller/subsystem/wardrobe/proc/get_callback_type(datum/to_check)
var/longest_path
var/longest_path_length = 0
for(var/datum/path as anything in initial_callbacks)
if(ispath(to_check, path))
var/stringpath = "[path]"
var/pathlength = length(splittext(stringpath, "/")) // We get the "depth" of the path
if(pathlength < longest_path_length)
continue
longest_path = path
longest_path_length = pathlength
return longest_path
/**
* Canonizes the type, which means it's now managed by the subsystem, and will be created deleted and passed out to comsumers
*
* Arguments:
* * type to stock - What type exactly do you want us to remember?
*
*/
/datum/controller/subsystem/wardrobe/proc/canonize_type(type_to_stock)
if(!type_to_stock)
return
if(!ispath(type_to_stock))
stack_trace("Non path [type_to_stock] attempted to canonize itself. Something's fucky")
var/list/master_info = canon_minimum[type_to_stock]
if(!master_info)
master_info = new /list(WARDROBE_CACHE_CALL_REMOVAL)
master_info[WARDROBE_CACHE_COUNT] = 0
//Decide on the appropriate callbacks to use
var/callback_type = get_callback_type(type_to_stock)
var/list/callback_info = initial_callbacks[callback_type]
if(callback_info)
master_info[WARDROBE_CACHE_CALL_INSERT] = callback_info[WARDROBE_CALLBACK_INSERT]
master_info[WARDROBE_CACHE_CALL_REMOVAL] = callback_info[WARDROBE_CALLBACK_REMOVE]
canon_minimum[type_to_stock] = master_info
master_info[WARDROBE_CACHE_COUNT] += 1
one_go_master++
/datum/controller/subsystem/wardrobe/proc/add_queue_item(queued_type, amount)
var/amount_held = order_list[queued_type] || 0
set_queue_item(queued_type, amount_held + amount)
/datum/controller/subsystem/wardrobe/proc/remove_queue_item(queued_type, amount)
var/amount_held = order_list[queued_type]
if(!amount_held)
return
set_queue_item(queued_type, amount_held - amount)
/datum/controller/subsystem/wardrobe/proc/set_queue_item(queued_type, amount)
var/list/master_info = canon_minimum[queued_type]
if(!master_info)
stack_trace("We just tried to queue a type \[[queued_type]\] that's not stored in the master canon")
return
var/target_amount = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/list/stock_info = preloaded_stock[queued_type]
if(stock_info)
target_amount -= length(stock_info[WARDROBE_STOCK_CONTENTS])
amount = min(amount, target_amount) // If we're trying to set more then we need, don't!
if(amount <= 0) // If we already have all we need, end it
order_list -= queued_type
return
order_list[queued_type] = amount
/// Take an existing object, and insert it into our storage
/// If we can't or won't take it, it's deleted. You do not own this object after passing it in
/datum/controller/subsystem/wardrobe/proc/stash_object(atom/movable/object)
var/object_type = object.type
var/list/master_info = canon_minimum[object_type]
// I will not permit objects you didn't reserve ahead of time
if(!master_info)
qdel(object)
return
var/stock_target = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/amount_held = 0
var/list/stock_info = preloaded_stock[object_type]
if(stock_info)
amount_held = length(stock_info[WARDROBE_STOCK_CONTENTS])
// Doublely so for things we already have too much of
if(amount_held - stock_target >= overflow_lienency)
qdel(object)
return
// Fuck off
if(QDELETED(object))
stack_trace("We tried to stash a qdeleted object, what did you do")
return
if(!stock_info)
stock_info = new /list(WARDROBE_STOCK_CALL_REMOVAL)
stock_info[WARDROBE_STOCK_CONTENTS] = list()
stock_info[WARDROBE_STOCK_CALL_INSERT] = master_info[WARDROBE_CACHE_CALL_INSERT]
stock_info[WARDROBE_STOCK_CALL_REMOVAL] = master_info[WARDROBE_CACHE_CALL_REMOVAL]
preloaded_stock[object_type] = stock_info
var/datum/callback/do_on_insert = stock_info[WARDROBE_STOCK_CALL_INSERT]
if(do_on_insert)
do_on_insert.object = object
do_on_insert.Invoke()
do_on_insert.object = null
object.moveToNullspace()
stock_info[WARDROBE_STOCK_CONTENTS] += object
/datum/controller/subsystem/wardrobe/proc/provide_type(datum/requested_type, atom/movable/location)
var/atom/movable/requested_object
var/list/stock_info = preloaded_stock[requested_type]
if(!stock_info)
stock_miss++
requested_object = new requested_type(location)
return requested_object
var/list/contents = stock_info[WARDROBE_STOCK_CONTENTS]
var/contents_length = length(contents)
requested_object = contents[contents_length]
contents.len--
if(QDELETED(requested_object))
stack_trace("We somehow ended up with a qdeleted or null object in SSwardrobe's stock. Something's weird, likely to do with reinsertion. Typepath of [requested_type]")
stock_miss++
requested_object = new requested_type(location)
return requested_object
if(location)
requested_object.forceMove(location)
var/datum/callback/do_on_removal = stock_info[WARDROBE_STOCK_CALL_REMOVAL]
if(do_on_removal)
do_on_removal.object = requested_object
do_on_removal.Invoke()
do_on_removal.object = null
stock_hit++
add_queue_item(requested_type, 1) // Requeue the item, under the assumption we'll never see it again
if(!(contents_length - 1))
preloaded_stock -= requested_type
return requested_object
/// Unloads an amount of some type we have in stock
/// Private function, for internal use only
/datum/controller/subsystem/wardrobe/proc/unload_stock(datum/unload_type, amount, force = FALSE)
var/list/stock_info = preloaded_stock[unload_type]
if(!stock_info)
return
var/list/unload_from = stock_info[WARDROBE_STOCK_CONTENTS]
for(var/i in 1 to min(amount, length(unload_from)))
var/datum/nuke = unload_from[unload_from.len]
unload_from.len--
qdel(nuke)
if(!force && MC_TICK_CHECK && length(unload_from))
return
if(!length(stock_info[WARDROBE_STOCK_CONTENTS]))
preloaded_stock -= unload_type
/// Sets up insertion and removal callbacks by typepath
/// We will always use the deepest path. So /obj/item/blade/knife superceeds the entries of /obj/item and /obj/item/blade
/// Mind this
/datum/controller/subsystem/wardrobe/proc/setup_callbacks()
var/list/play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Turns out there's a global list of pdas. Let's work around that yeah?
play_with[WARDROBE_CALLBACK_INSERT] = CALLBACK(null, /obj/item/pda/proc/display_pda)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/pda/proc/cloak_pda)
initial_callbacks[/obj/item/pda] = play_with
play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Don't want organs rotting on the job
play_with[WARDROBE_CALLBACK_INSERT] = CALLBACK(null, /obj/item/organ/proc/enter_wardrobe)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/organ/proc/exit_wardrobe)
initial_callbacks[/obj/item/organ] = play_with
play_with = new /list(WARDROBE_CALLBACK_REMOVE)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/storage/box/survival/proc/wardrobe_removal)
initial_callbacks[/obj/item/storage/box/survival] = play_with
/datum/controller/subsystem/wardrobe/proc/load_outfits()
for(var/datum/outfit/to_stock as anything in subtypesof(/datum/outfit))
if(!initial(to_stock.preload)) // Clearly not interested
continue
var/datum/outfit/hang_up = new to_stock()
for(var/datum/outfit_item as anything in hang_up.get_types_to_preload())
canonize_type(outfit_item)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_species()
for(var/datum/species/to_record as anything in subtypesof(/datum/species))
if(!initial(to_record.preload))
continue
var/datum/species/fossil_record = new to_record()
for(var/obj/item/species_request as anything in fossil_record.get_types_to_preload())
for(var/i in 1 to 5) // Store 5 of each species, since that seems on par with 1 of each outfit
canonize_type(species_request)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_pda_nicknacks()
for(var/obj/item/pda/pager as anything in typesof(/obj/item/pda))
var/obj/item/pda/flip_phone = new pager()
for(var/datum/outfit_item_type as anything in flip_phone.get_types_to_preload())
canonize_type(outfit_item_type)
qdel(flip_phone)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_storage_contents()
for(var/obj/item/storage/crate as anything in subtypesof(/obj/item/storage))
if(!initial(crate.preload))
continue
var/obj/item/pda/another_crate = new crate()
//Unlike other uses, I really don't want people being lazy with this one.
var/list/somehow_more_boxes = another_crate.get_types_to_preload()
if(!length(somehow_more_boxes))
stack_trace("You appear to have set preload to true on [crate] without defining get_types_to_preload. Please be more strict about your scope, this stuff is spooky")
for(var/datum/a_really_small_box as anything in somehow_more_boxes)
canonize_type(a_really_small_box)
qdel(another_crate)
+24 -5
View File
@@ -115,14 +115,33 @@
/datum/component/storage/PreTransfer()
update_actions()
/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list)
can_hold_description = generate_hold_desc(can_hold_list)
/// Almost 100% of the time the lists passed into set_holdable are reused for each instance of the component
/// Just fucking cache it 4head
/// Yes I could generalize this, but I don't want anyone else using it. in fact, DO NOT COPY THIS
/// If you find yourself needing this pattern, you're likely better off using static typecaches
/// I'm not because I do not trust implementers of the storage component to use them, BUT
/// IF I FIND YOU USING THIS PATTERN IN YOUR CODE I WILL BREAK YOU ACROSS MY KNEES
/// ~Lemon
GLOBAL_LIST_EMPTY(cached_storage_typecaches)
if (can_hold_list != null)
can_hold = string_list(typecacheof(can_hold_list))
/datum/component/storage/proc/set_holdable(list/can_hold_list, list/cant_hold_list)
if(!islist(can_hold_list))
can_hold_list = list(can_hold_list)
if(!islist(cant_hold_list))
cant_hold_list = list(cant_hold_list)
can_hold_description = generate_hold_desc(can_hold_list)
if (can_hold_list)
var/unique_key = can_hold_list.Join("-")
if(!GLOB.cached_storage_typecaches[unique_key])
GLOB.cached_storage_typecaches[unique_key] = typecacheof(can_hold_list)
can_hold = GLOB.cached_storage_typecaches[unique_key]
if (cant_hold_list != null)
cant_hold = string_list(typecacheof(cant_hold_list))
var/unique_key = cant_hold_list.Join("-")
if(!GLOB.cached_storage_typecaches[unique_key])
GLOB.cached_storage_typecaches[unique_key] = typecacheof(cant_hold_list)
cant_hold = GLOB.cached_storage_typecaches[unique_key]
/datum/component/storage/proc/generate_hold_desc(can_hold_list)
var/list/desc = list()
+60 -21
View File
@@ -117,6 +117,9 @@
/// Should the toggle helmet proc be called on the helmet during equip
var/toggle_helmet = TRUE
///Should we preload some of this job's items?
var/preload = FALSE
/// Any undershirt. While on humans it is a string, here we use paths to stay consistent with the rest of the equips.
var/datum/sprite_accessory/undershirt = null
@@ -163,35 +166,35 @@
//Start with uniform,suit,backpack for additional slots
if(uniform)
H.equip_to_slot_or_del(new uniform(H),ITEM_SLOT_ICLOTHING, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(uniform, H), ITEM_SLOT_ICLOTHING, TRUE)
if(suit)
H.equip_to_slot_or_del(new suit(H),ITEM_SLOT_OCLOTHING, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(suit, H), ITEM_SLOT_OCLOTHING, TRUE)
if(back)
H.equip_to_slot_or_del(new back(H),ITEM_SLOT_BACK, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(back, H), ITEM_SLOT_BACK, TRUE)
if(belt)
H.equip_to_slot_or_del(new belt(H),ITEM_SLOT_BELT, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(belt, H), ITEM_SLOT_BELT, TRUE)
if(gloves)
H.equip_to_slot_or_del(new gloves(H),ITEM_SLOT_GLOVES, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(gloves, H), ITEM_SLOT_GLOVES, TRUE)
if(shoes)
H.equip_to_slot_or_del(new shoes(H),ITEM_SLOT_FEET, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(shoes, H), ITEM_SLOT_FEET, TRUE)
if(head)
H.equip_to_slot_or_del(new head(H),ITEM_SLOT_HEAD, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(head, H), ITEM_SLOT_HEAD, TRUE)
if(mask)
H.equip_to_slot_or_del(new mask(H),ITEM_SLOT_MASK, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(mask, H), ITEM_SLOT_MASK, TRUE)
if(neck)
H.equip_to_slot_or_del(new neck(H),ITEM_SLOT_NECK, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(neck, H), ITEM_SLOT_NECK, TRUE)
if(ears)
H.equip_to_slot_or_del(new ears(H),ITEM_SLOT_EARS, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(ears, H), ITEM_SLOT_EARS, TRUE)
if(glasses)
H.equip_to_slot_or_del(new glasses(H),ITEM_SLOT_EYES, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(glasses, H), ITEM_SLOT_EYES, TRUE)
if(id)
H.equip_to_slot_or_del(new id(H),ITEM_SLOT_ID, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(id, H), ITEM_SLOT_ID, TRUE) //We don't provide ids (Fix this?)
if(!visualsOnly && id_trim && H.wear_id)
var/obj/item/card/id/id_card = H.wear_id
if(istype(id_card) && !SSid_access.apply_trim_to_card(id_card, id_trim))
WARNING("Unable to apply trim [id_trim] to [id_card] in outfit [name].")
if(suit_store)
H.equip_to_slot_or_del(new suit_store(H),ITEM_SLOT_SUITSTORE, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(suit_store, H), ITEM_SLOT_SUITSTORE, TRUE)
if(undershirt)
H.undershirt = initial(undershirt.name)
@@ -199,20 +202,20 @@
if(accessory)
var/obj/item/clothing/under/U = H.w_uniform
if(U)
U.attach_accessory(new accessory(H))
U.attach_accessory(SSwardrobe.provide_type(accessory, H))
else
WARNING("Unable to equip accessory [accessory] in outfit [name]. No uniform present!")
if(l_hand)
H.put_in_l_hand(new l_hand(H))
H.put_in_l_hand(SSwardrobe.provide_type(l_hand, H))
if(r_hand)
H.put_in_r_hand(new r_hand(H))
H.put_in_r_hand(SSwardrobe.provide_type(r_hand, H))
if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon.
if(l_pocket)
H.equip_to_slot_or_del(new l_pocket(H),ITEM_SLOT_LPOCKET, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(l_pocket, H), ITEM_SLOT_LPOCKET, TRUE)
if(r_pocket)
H.equip_to_slot_or_del(new r_pocket(H),ITEM_SLOT_RPOCKET, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(r_pocket, H), ITEM_SLOT_RPOCKET, TRUE)
if(box)
if(!backpack_contents)
@@ -226,7 +229,7 @@
if(!isnum(number))//Default to 1
number = 1
for(var/i in 1 to number)
H.equip_to_slot_or_del(new path(H),ITEM_SLOT_BACKPACK, TRUE)
H.equip_to_slot_or_del(SSwardrobe.provide_type(path, H), ITEM_SLOT_BACKPACK, TRUE)
if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit))
var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
@@ -241,13 +244,13 @@
H.update_action_buttons_icon()
if(implants)
for(var/implant_type in implants)
var/obj/item/implant/I = new implant_type(H)
var/obj/item/implant/I = SSwardrobe.provide_type(implant_type, H)
I.implant(H, null, TRUE)
// Insert the skillchips associated with this outfit into the target.
if(skillchips)
for(var/skillchip_path in skillchips)
var/obj/item/skillchip/skillchip_instance = new skillchip_path()
var/obj/item/skillchip/skillchip_instance = SSwardrobe.provide_type(skillchip_path)
var/implant_msg = H.implant_skillchip(skillchip_instance)
if(implant_msg)
stack_trace("Failed to implant [H] with [skillchip_instance], on job [src]. Failure message: [implant_msg]")
@@ -352,6 +355,42 @@
list_clear_nulls(types)
return types
/// Return a list of types to pregenerate for later equipping
/// This should not be things that do unique stuff in Initialize() based off their location, since we'll be storing them for a while
/datum/outfit/proc/get_types_to_preload()
var/list/preload = list()
preload += id
preload += uniform
preload += suit
preload += suit_store
preload += back
//Load in backpack gear and shit
for(var/datum/type_to_load in backpack_contents)
for(var/i in 1 to backpack_contents[type_to_load])
preload += type_to_load
preload += belt
preload += ears
preload += glasses
preload += gloves
preload += head
preload += mask
preload += neck
preload += shoes
preload += l_pocket
preload += r_pocket
preload += l_hand
preload += r_hand
preload += accessory
preload += box
for(var/implant_type in implants)
preload += implant_type
for(var/skillpath in skillchips)
preload += skillpath
preload -= typesof(/obj/item/clothing/under/color/random) // SKYRAT EDIT - Don't preload random jumpsuit spawners that delete themselves
return preload
/// Return a json list of this outfit
/datum/outfit/proc/get_json_data()
. = list()
+1
View File
@@ -2,6 +2,7 @@
name = "body egg"
desc = "All slimy and yuck."
icon_state = "innards"
visual = TRUE
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_PARASITE_EGG
+48 -25
View File
@@ -36,7 +36,7 @@ GLOBAL_LIST_EMPTY(PDAs)
/// String name of owner
var/owner = null
/// Access level defined by cartridge
/// Typepath of the default cartridge to use
var/default_cartridge = 0
/// Current cartridge
var/obj/item/cartridge/cartridge = null
@@ -97,8 +97,10 @@ GLOBAL_LIST_EMPTY(PDAs)
var/datum/picture/picture //Scanned photo
var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette)
var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above.
//This is the typepath to load "into" the pda
var/obj/item/insert_type = /obj/item/pen
//This is the currently inserted item
var/obj/item/inserted_item
var/underline_flag = TRUE //flag for underline
/obj/item/pda/suicide_act(mob/living/carbon/user)
@@ -128,15 +130,26 @@ GLOBAL_LIST_EMPTY(PDAs)
GLOB.PDAs += src
if(default_cartridge)
cartridge = new default_cartridge(src)
if(inserted_item)
inserted_item = new inserted_item(src)
else
inserted_item = new /obj/item/pen(src)
cartridge = SSwardrobe.provide_type(default_cartridge, src)
cartridge.host_pda = src
if(insert_type)
inserted_item = SSwardrobe.provide_type(insert_type, src)
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater)
update_appearance()
/obj/item/pda/Destroy()
GLOB.PDAs -= src
if(istype(id))
QDEL_NULL(id)
if(istype(cartridge))
QDEL_NULL(cartridge)
if(istype(pai))
QDEL_NULL(pai)
if(istype(inserted_item))
QDEL_NULL(inserted_item)
return ..()
/obj/item/pda/equipped(mob/user, slot)
. = ..()
if(!equipped)
@@ -163,6 +176,14 @@ GLOBAL_LIST_EMPTY(PDAs)
font_mode = FONT_MONO
equipped = TRUE
/obj/item/pda/Exited(atom/movable/gone, direction)
. = ..()
if(gone == cartridge)
cartridge.host_pda = null
cartridge = null
if(gone == inserted_item)
inserted_item = null
/obj/item/pda/proc/update_label()
name = "PDA-[owner] ([ownjob])" //Name generalisation
@@ -976,9 +997,8 @@ GLOBAL_LIST_EMPTY(PDAs)
return
if(inserted_item)
user.put_in_hands(inserted_item)
to_chat(user, span_notice("You remove [inserted_item] from [src]."))
inserted_item = null
user.put_in_hands(inserted_item) //Don't need to manage the pen ref, handled on Exited()
update_appearance()
playsound(src, 'sound/machines/pda_button2.ogg', 50, TRUE)
else
@@ -988,11 +1008,9 @@ GLOBAL_LIST_EMPTY(PDAs)
if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand
return
if (!isnull(cartridge))
user.put_in_hands(cartridge)
to_chat(user, span_notice("You eject [cartridge] from [src]."))
user.put_in_hands(cartridge) //We don't manage reference clearing here, dealt with in Exited()
scanmode = PDA_SCANNER_NONE
cartridge.host_pda = null
cartridge = null
updateSelfDialog()
update_appearance()
@@ -1191,18 +1209,6 @@ GLOBAL_LIST_EMPTY(PDAs)
explosion(src, devastation_range = -1, heavy_impact_range = -1, light_impact_range = 2, flash_range = 3)
qdel(src)
/obj/item/pda/Destroy()
GLOB.PDAs -= src
if(istype(id))
QDEL_NULL(id)
if(istype(cartridge))
QDEL_NULL(cartridge)
if(istype(pai))
QDEL_NULL(pai)
if(istype(inserted_item))
QDEL_NULL(inserted_item)
return ..()
//AI verb and proc for sending PDA messages.
/obj/item/pda/ai/verb/cmd_toggle_pda_receiver()
@@ -1306,6 +1312,23 @@ GLOBAL_LIST_EMPTY(PDAs)
SIGNAL_HANDLER
return COMPONENT_PDA_NO_DETONATE
/// Return a list of types you want to pregenerate and use later
/// Do not pass in things that care about their init location, or expect extra input
/// Also as a curtiousy to me, don't pass in any bombs
/obj/item/pda/proc/get_types_to_preload()
var/list/preload = list()
preload += default_cartridge
preload += insert_type
return preload
/// Callbacks for preloading pdas
/obj/item/pda/proc/display_pda()
GLOB.PDAs += src
/// See above, we don't want jerry from accounting to try and message nullspace his new bike
/obj/item/pda/proc/cloak_pda()
GLOB.PDAs -= src
#undef PDA_SCANNER_NONE
#undef PDA_SCANNER_MEDICAL
#undef PDA_SCANNER_FORENSICS
@@ -2,7 +2,7 @@
/obj/item/pda/clown
name = "clown PDA"
default_cartridge = /obj/item/cartridge/virus/clown
inserted_item = /obj/item/toy/crayon/rainbow
insert_type = /obj/item/toy/crayon/rainbow
icon_state = "pda-clown"
greyscale_config = null
greyscale_config = null
@@ -28,7 +28,7 @@
/obj/item/pda/mime
name = "mime PDA"
default_cartridge = /obj/item/cartridge/virus/mime
inserted_item = /obj/item/toy/crayon/mime
insert_type = /obj/item/toy/crayon/mime
greyscale_config = /datum/greyscale_config/pda/mime
greyscale_colors = "#e2e2e2#cc4242"
desc = "A portable microcomputer by Thinktronic Systems, LTD. The hardware has been modified for compliance with the vows of silence."
@@ -147,14 +147,14 @@
/obj/item/pda/heads/rd
name = "research director PDA"
default_cartridge = /obj/item/cartridge/rd
inserted_item = /obj/item/pen/fountain
insert_type = /obj/item/pen/fountain
greyscale_config = /datum/greyscale_config/pda/stripe_thick/head
greyscale_colors = "#e2e2e2#000099#9F5CA5"
/obj/item/pda/captain
name = "captain PDA"
default_cartridge = /obj/item/cartridge/captain
inserted_item = /obj/item/pen/fountain/captain
insert_type = /obj/item/pen/fountain/captain
greyscale_config = /datum/greyscale_config/pda/captain
greyscale_colors = "#2C7CB2#FF0000#FFFFFF#F5D67B"
@@ -170,7 +170,7 @@
/obj/item/pda/quartermaster
name = "quartermaster PDA"
default_cartridge = /obj/item/cartridge/quartermaster
inserted_item = /obj/item/pen/survival
insert_type = /obj/item/pen/survival
greyscale_config = /datum/greyscale_config/pda/stripe_thick
greyscale_colors = "#D6B328#6506ca#927444"
@@ -195,7 +195,7 @@
/obj/item/pda/lawyer
name = "lawyer PDA"
default_cartridge = /obj/item/cartridge/lawyer
inserted_item = /obj/item/pen/fountain
insert_type = /obj/item/pen/fountain
greyscale_colors = "#5B74A5#f7e062"
ttone = "objection"
@@ -219,7 +219,7 @@
icon_pai = "pai_overlay_library"
icon_inactive_pai = "pai_off_overlay_library"
default_cartridge = /obj/item/cartridge/curator
inserted_item = /obj/item/pen/fountain
insert_type = /obj/item/pen/fountain
desc = "A portable microcomputer by Thinktronic Systems, LTD. This model is a WGW-11 series e-reader."
note = "Congratulations, your station has chosen the Thinktronic 5290 WGW-11 Series E-reader and Personal Data Assistant!"
silent = TRUE //Quiet in the library!
@@ -239,7 +239,7 @@
/obj/item/pda/bar
name = "bartender PDA"
greyscale_colors = "#333333#c7c7c7"
inserted_item = /obj/item/pen/fountain
insert_type = /obj/item/pen/fountain
/obj/item/pda/atmos
name = "atmospherics PDA"
@@ -34,12 +34,6 @@
var/mob/living/simple_animal/bot/active_bot
var/list/botlist = list()
/obj/item/cartridge/Initialize(mapload)
. = ..()
var/obj/item/pda/pda = loc
if(istype(pda))
host_pda = pda
/obj/item/cartridge/engineering
name = "\improper Power-ON cartridge"
icon_state = "cart-e"
@@ -137,8 +131,6 @@
. = ..()
radio = new(src)
/obj/item/cartridge/quartermaster
name = "space parts & space vendors cartridge"
desc = "Perfect for the Quartermaster on the go!"
@@ -6,21 +6,25 @@
var/lifespan_postmortem = 6000
///will people implanted with this act as teleporter beacons?
var/allow_teleport = TRUE
///The id of the timer that's qdeleting us
var/timerid
/obj/item/implant/tracking/c38
name = "TRAC implant"
desc = "A smaller tracking implant that supplies power for only a few minutes."
var/lifespan = 3000 //how many deciseconds does the implant last?
///The id of the timer that's qdeleting us
var/timerid
allow_teleport = FALSE
/obj/item/implant/tracking/c38/Initialize(mapload)
/obj/item/implant/tracking/c38/implant(mob/living/target, mob/user, silent, force)
. = ..()
timerid = QDEL_IN(src, lifespan)
/obj/item/implant/tracking/c38/Destroy()
/obj/item/implant/tracking/c38/removed(mob/living/source, silent, special)
. = ..()
deltimer(timerid)
timerid = null
/obj/item/implant/tracking/c38/Destroy()
return ..()
/obj/item/implant/tracking/Initialize(mapload)
@@ -11,6 +11,8 @@
/obj/item/implant/uplink/Initialize(mapload, owner, uplink_flag)
. = ..()
if(!uplink_flag)
uplink_flag = src.uplink_flag
var/datum/component/uplink/new_uplink = AddComponent(/datum/component/uplink, _owner = owner, _lockable = TRUE, _enabled = FALSE, uplink_flag = uplink_flag, starting_tc = starting_tc)
new_uplink.unlock_text = "Your Syndicate Uplink has been cunningly implanted in you, for a small TC fee. Simply trigger the uplink to access it."
RegisterSignal(src, COMSIG_COMPONENT_REMOVING, .proc/_component_removal)
+102 -35
View File
@@ -81,24 +81,52 @@
inhand_icon_state = "utility_ce"
worn_icon_state = "utility_ce"
/obj/item/storage/belt/utility/chief/full
preload = TRUE
/obj/item/storage/belt/utility/chief/full/PopulateContents()
new /obj/item/screwdriver/power(src)
new /obj/item/crowbar/power(src)
new /obj/item/weldingtool/electric(src) // SKYRAT EDIT - original: new /obj/item/weldingtool/experimental(src)
new /obj/item/multitool(src)
new /obj/item/stack/cable_coil(src)
new /obj/item/extinguisher/mini(src)
new /obj/item/analyzer/ranged(src) //SKYRAT EDIT - original: new /obj/item/analyzer(src)
SSwardrobe.provide_type(/obj/item/screwdriver/power, src)
SSwardrobe.provide_type(/obj/item/crowbar/power, src)
SSwardrobe.provide_type(/obj/item/weldingtool/electric, src)//This can be changed if this is too much //It's been 5 years // SKYRAT EDIT - ORIGINAL: SSwardrobe.provide_type(/obj/item/weldingtool/experimental, src)
SSwardrobe.provide_type(/obj/item/multitool, src)
SSwardrobe.provide_type(/obj/item/stack/cable_coil, src)
SSwardrobe.provide_type(/obj/item/extinguisher/mini, src)
SSwardrobe.provide_type(/obj/item/analyzer/ranged, src) //SKYRAT EDIT - ORIGINAL: SSwardrobe.provide_type(/obj/item/analyzer, src)
//much roomier now that we've managed to remove two tools
/obj/item/storage/belt/utility/chief/full/get_types_to_preload()
var/list/to_preload = list() //Yes this is a pain. Yes this is the point
to_preload += /obj/item/screwdriver/power
to_preload += /obj/item/crowbar/power
to_preload += /obj/item/weldingtool/electric // SKYRAT EDIT - Electric welder
to_preload += /obj/item/multitool
to_preload += /obj/item/stack/cable_coil
to_preload += /obj/item/extinguisher/mini
to_preload += /obj/item/analyzer/ranged // SKYRAT EDIT - Ranged Analyzer
return to_preload
/obj/item/storage/belt/utility/full/PopulateContents()
new /obj/item/screwdriver(src)
new /obj/item/wrench(src)
new /obj/item/weldingtool(src)
new /obj/item/crowbar(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
new /obj/item/stack/cable_coil(src)
SSwardrobe.provide_type(/obj/item/screwdriver, src)
SSwardrobe.provide_type(/obj/item/wrench, src)
SSwardrobe.provide_type(/obj/item/weldingtool, src)
SSwardrobe.provide_type(/obj/item/crowbar, src)
SSwardrobe.provide_type(/obj/item/wirecutters, src)
SSwardrobe.provide_type(/obj/item/multitool, src)
SSwardrobe.provide_type(/obj/item/stack/cable_coil, src)
/obj/item/storage/belt/utility/full/get_types_to_preload()
var/list/to_preload = list() //Yes this is a pain. Yes this is the point
to_preload += /obj/item/screwdriver
to_preload += /obj/item/wrench
to_preload += /obj/item/weldingtool
to_preload += /obj/item/crowbar
to_preload += /obj/item/wirecutters
to_preload += /obj/item/multitool
to_preload += /obj/item/stack/cable_coil
return to_preload
/obj/item/storage/belt/utility/full/powertools
preload = FALSE
/obj/item/storage/belt/utility/full/powertools/PopulateContents()
new /obj/item/screwdriver/power(src)
@@ -119,22 +147,47 @@
new /obj/item/stack/cable_coil(src)
/obj/item/storage/belt/utility/full/engi/PopulateContents()
new /obj/item/screwdriver(src)
new /obj/item/wrench(src)
new /obj/item/weldingtool/largetank(src)
new /obj/item/crowbar(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
new /obj/item/stack/cable_coil(src)
SSwardrobe.provide_type(/obj/item/screwdriver, src)
SSwardrobe.provide_type(/obj/item/wrench, src)
SSwardrobe.provide_type(/obj/item/weldingtool/largetank, src)
SSwardrobe.provide_type(/obj/item/crowbar, src)
SSwardrobe.provide_type(/obj/item/wirecutters, src)
SSwardrobe.provide_type(/obj/item/multitool, src)
SSwardrobe.provide_type(/obj/item/stack/cable_coil, src)
/obj/item/storage/belt/utility/full/engi/get_types_to_preload()
var/list/to_preload = list() //Yes this is a pain. Yes this is the point
to_preload += /obj/item/screwdriver
to_preload += /obj/item/wrench
to_preload += /obj/item/weldingtool/largetank
to_preload += /obj/item/crowbar
to_preload += /obj/item/wirecutters
to_preload += /obj/item/multitool
to_preload += /obj/item/stack/cable_coil
return to_preload
/obj/item/storage/belt/utility/atmostech/PopulateContents()
new /obj/item/screwdriver(src)
new /obj/item/wrench(src)
new /obj/item/weldingtool(src)
new /obj/item/crowbar(src)
new /obj/item/wirecutters(src)
new /obj/item/t_scanner(src)
new /obj/item/extinguisher/mini(src)
SSwardrobe.provide_type(/obj/item/screwdriver, src)
SSwardrobe.provide_type(/obj/item/wrench, src)
SSwardrobe.provide_type(/obj/item/weldingtool, src)
SSwardrobe.provide_type(/obj/item/crowbar, src)
SSwardrobe.provide_type(/obj/item/wirecutters, src)
SSwardrobe.provide_type(/obj/item/t_scanner, src)
SSwardrobe.provide_type(/obj/item/extinguisher/mini, src)
/obj/item/storage/belt/utility/atmostech/get_types_to_preload()
var/list/to_preload = list() //Yes this is a pain. Yes this is the point
to_preload += /obj/item/screwdriver
to_preload += /obj/item/wrench
to_preload += /obj/item/weldingtool
to_preload += /obj/item/crowbar
to_preload += /obj/item/wirecutters
to_preload += /obj/item/t_scanner
to_preload += /obj/item/extinguisher/mini
return to_preload
/obj/item/storage/belt/utility/syndicate
preload = FALSE
/obj/item/storage/belt/utility/syndicate/PopulateContents()
new /obj/item/screwdriver/nuke(src)
@@ -215,16 +268,30 @@
/obj/item/stack/sticky_tape //surgical tape
))
/obj/item/storage/belt/medical/paramedic
preload = TRUE
/obj/item/storage/belt/medical/paramedic/PopulateContents()
new /obj/item/sensor_device(src)
new /obj/item/pinpointer/crew/prox(src)
new /obj/item/stack/medical/gauze/twelve(src)
new /obj/item/reagent_containers/syringe(src)
new /obj/item/stack/medical/bone_gel(src)
new /obj/item/stack/sticky_tape/surgical(src)
new /obj/item/reagent_containers/glass/bottle/formaldehyde(src)
SSwardrobe.provide_type(/obj/item/sensor_device, src)
SSwardrobe.provide_type(/obj/item/pinpointer/crew/prox, src)
SSwardrobe.provide_type(/obj/item/stack/medical/gauze/twelve, src)
SSwardrobe.provide_type(/obj/item/reagent_containers/syringe, src)
SSwardrobe.provide_type(/obj/item/stack/medical/bone_gel, src)
SSwardrobe.provide_type(/obj/item/stack/sticky_tape/surgical, src)
SSwardrobe.provide_type(/obj/item/reagent_containers/glass/bottle/formaldehyde, src)
update_appearance()
/obj/item/storage/belt/medical/paramedic/get_types_to_preload()
var/list/to_preload = list() //Yes this is a pain. Yes this is the point
to_preload += /obj/item/sensor_device
to_preload += /obj/item/pinpointer/crew/prox
to_preload += /obj/item/stack/medical/gauze/twelve
to_preload += /obj/item/reagent_containers/syringe
to_preload += /obj/item/stack/medical/bone_gel
to_preload += /obj/item/stack/sticky_tape/surgical
to_preload += /obj/item/reagent_containers/glass/bottle/formaldehyde
return to_preload
/obj/item/storage/belt/security
name = "security belt"
desc = "Can hold security gear like handcuffs and flashes."
+9
View File
@@ -141,6 +141,15 @@
..() // we want the survival stuff too.
new /obj/item/radio/off(src)
/obj/item/storage/box/survival/proc/wardrobe_removal()
if(!isplasmaman(loc)) //We need to specially fill the box with plasmaman gear, since it's intended for one
return
var/obj/item/mask = locate(mask_type) in src
var/obj/item/internals = locate(internal_type) in src
new /obj/item/tank/internals/plasmaman/belt(src)
qdel(mask) // Get rid of the items that shouldn't be
qdel(internals)
// Mining survival box
/obj/item/storage/box/survival/mining
mask_type = /obj/item/clothing/mask/gas/explorer
@@ -4,6 +4,10 @@
w_class = WEIGHT_CLASS_NORMAL
var/rummage_if_nodrop = TRUE
var/component_type = /datum/component/storage/concrete
/// Should we preload the contents of this type?
/// BE CAREFUL, THERE'S SOME REALLY NASTY SHIT IN THIS TYPEPATH
/// SANTA IS EVIL
var/preload = FALSE
/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user)
return src
@@ -54,3 +58,9 @@
continue
important_thing.forceMove(drop_location())
return ..()
/// Returns a list of object types to be preloaded by our code
/// I'll say it again, be very careful with this. We only need it for a few things
/// Don't do anything stupid, please
/obj/item/storage/proc/get_types_to_preload()
return
@@ -30,6 +30,7 @@
desc = "An alien organ that twists and writhes when exposed to light."
icon = 'icons/obj/surgery.dmi'
icon_state = "demon_heart-on"
visual = TRUE
color = "#1C1C1C"
decay_factor = 0
/// How many life ticks in the dark the owner has been dead for. Used for nightmare respawns.
+1
View File
@@ -561,6 +561,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/character_preview_view)
character.dna.real_name = character.real_name
if(icon_updates)
character.icon_render_key = null //turns out if you don't set this to null update_body_parts does nothing, since it assumes the operation was cached
character.update_body()
character.update_hair()
character.update_body_parts()
+1 -1
View File
@@ -575,7 +575,7 @@
SIGNAL_HANDLER
var/turf/T = get_turf(src)
if(T.z != epicenter.z)
if(T?.z != epicenter.z)
return
if(get_dist(epicenter, T) > explosion_detection_dist)
return
+12
View File
@@ -258,6 +258,8 @@
shoes = /obj/item/clothing/shoes/sneakers/black
box = /obj/item/storage/box/survival
preload = TRUE // These are used by the prefs ui, and also just kinda could use the extra help at roundstart
var/backpack = /obj/item/storage/backpack
var/satchel = /obj/item/storage/backpack/satchel
var/duffelbag = /obj/item/storage/backpack/duffelbag
@@ -333,6 +335,16 @@
types += duffelbag
return types
/datum/outfit/job/get_types_to_preload()
var/list/preload = ..()
preload += backpack
preload += satchel
preload += duffelbag
preload += /obj/item/storage/backpack/satchel/leather
var/skirtpath = "[uniform]/skirt"
preload += text2path(skirtpath)
return preload
/// An overridable getter for more dynamic goodies.
/datum/job/proc/get_mail_goodies(mob/recipient)
return mail_goodies
+2
View File
@@ -58,6 +58,8 @@ Assistant
var/index = (jumpsuit_number % GLOB.colored_assistant.jumpsuits.len) + 1
//We don't cache these, because they can delete on init
//Too fragile, better to just eat the cost
if (target.jumpsuit_style == PREF_SUIT)
uniform = GLOB.colored_assistant.jumpsuits[index]
else
+5
View File
@@ -75,6 +75,11 @@
if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
backpack_contents[/obj/item/stack/sheet/mineral/bananium/five] = 1
/datum/outfit/job/clown/get_types_to_preload()
. = ..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
. += /obj/item/stack/sheet/mineral/bananium/five
/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
if(visualsOnly)
+5
View File
@@ -110,3 +110,8 @@
head = /obj/item/clothing/head/soft/mime
if(!visualsOnly)
J.cooks++
/datum/outfit/job/cook/get_types_to_preload()
. = ..()
. += /obj/item/clothing/suit/apron/chef
. += /obj/item/clothing/head/soft/mime
+6
View File
@@ -48,3 +48,9 @@
if(GARBAGEDAY in SSevents.holidays)
backpack_contents += /obj/item/gun/ballistic/revolver
r_pocket = /obj/item/ammo_box/a357
/datum/outfit/job/janitor/get_types_to_preload()
. = ..()
if(GARBAGEDAY in SSevents.holidays)
. += /obj/item/gun/ballistic/revolver
. += /obj/item/ammo_box/a357
+5
View File
@@ -56,3 +56,8 @@
else
use_purple_suit = TRUE
..()
/datum/outfit/job/lawyer/get_types_to_preload()
. = ..()
. += /obj/item/clothing/under/rank/civilian/lawyer/purpsuit
. += /obj/item/clothing/suit/toggle/lawyer/purple
+4
View File
@@ -56,3 +56,7 @@
..()
if(prob(0.4))
neck = /obj/item/clothing/neck/tie/horrible
/datum/outfit/job/scientist/get_types_to_preload()
. = ..()
. += /obj/item/clothing/neck/tie/horrible
@@ -24,6 +24,7 @@
name = "regenerative core"
desc = "All that remains of a hivelord. It can be used to help keep your body going, but it will rapidly decay into uselessness."
icon_state = "roro core 2"
visual = FALSE
item_flags = NOBLUDGEON
slot = ORGAN_SLOT_REGENERATIVE_CORE
organ_flags = NONE
@@ -2,6 +2,7 @@
name = "brain"
desc = "A piece of juicy meat found in a person's head."
icon_state = "brain"
visual = TRUE
throw_speed = 3
throw_range = 5
layer = ABOVE_MOB_LAYER
@@ -1,5 +1,6 @@
/obj/item/organ/alien
icon_state = "xgibmid2"
visual = FALSE
food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/acid = 10)
var/list/alien_powers = list()
@@ -106,6 +106,9 @@
/// Can other carbons be shoved into this one to make it fall?
var/can_be_shoved_into = FALSE
/// Only load in visual organs
var/visual_only_organs = FALSE
COOLDOWN_DECLARE(bleeding_message_cd)
@@ -3,6 +3,7 @@
real_name = "Test Dummy"
status_flags = GODMODE|CANPUSH
mouse_drag_pointer = MOUSE_INACTIVE_POINTER
visual_only_organs = TRUE
var/in_use = FALSE
INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
@@ -17,6 +18,60 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
/mob/living/carbon/human/dummy/attach_rot(mapload)
return
/mob/living/carbon/human/dummy/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE)
harvest_organs()
return ..()
///Let's extract our dummies organs and limbs for storage, to reduce the cache missed that spamming a dummy cause
/mob/living/carbon/human/dummy/proc/harvest_organs()
for(var/slot in list(ORGAN_SLOT_BRAIN, ORGAN_SLOT_HEART, ORGAN_SLOT_LUNGS, ORGAN_SLOT_APPENDIX, \
ORGAN_SLOT_EYES, ORGAN_SLOT_EARS, ORGAN_SLOT_TONGUE, ORGAN_SLOT_LIVER, ORGAN_SLOT_STOMACH))
var/obj/item/organ/current_organ = getorganslot(slot) //Time to cache it lads
if(current_organ)
current_organ.Remove(src, special = TRUE) //Please don't somehow kill our dummy
SSwardrobe.stash_object(current_organ)
var/datum/species/current_species = dna.species
for(var/organ_path in current_species.mutant_organs)
var/obj/item/organ/current_organ = getorgan(organ_path)
if(current_organ)
current_organ.Remove(src, special = TRUE) //Please don't somehow kill our dummy
SSwardrobe.stash_object(current_organ)
for(var/obj/item/organ/external/organ in internal_organs)
if(organ.type in current_species.external_organs)
organ.Remove(src)
SSwardrobe.stash_object(organ)
//Instead of just deleting our equipment, we save what we can and reinsert it into SSwardrobe's store
//Hopefully this makes preference reloading not the worst thing ever
/mob/living/carbon/human/dummy/delete_equipment()
var/list/items_to_check = get_all_slots() + held_items
var/list/to_nuke = list() //List of items queued for deletion, can't qdel them before iterating their contents in case they hold something
///Travel to the bottom of the contents chain, expanding it out
for(var/i = 1; i <= length(items_to_check); i++) //Needs to be a c style loop since it can expand
var/obj/item/checking = items_to_check[i]
if(!checking) //Nulls in the list, depressing
continue
if(!isitem(checking)) //What the fuck are you on
to_nuke += checking
continue
var/list/contents = checking.contents
if(length(contents))
items_to_check |= contents //Please don't make an infinite loop somehow thx
to_nuke += checking //Goodbye
continue
//I'm making the bet that if you're empty of other items you're not going to OOM if reapplied. I assume you're here because I was wrong
if(ismob(checking.loc))
var/mob/checkings_owner = checking.loc
checkings_owner.temporarilyRemoveItemFromInventory(checking, TRUE) //Clear out of there yeah?
SSwardrobe.stash_object(checking)
for(var/obj/item/delete as anything in to_nuke)
qdel(delete)
/mob/living/carbon/human/dummy/has_equipped(obj/item/item, slot, initial = FALSE)
return item.visual_equipped(src, slot, initial)
@@ -212,6 +212,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
///List of visual overlays created by handle_body()
var/list/body_vis_overlays = list()
//Should we preload this species's organs?
var/preload = TRUE
///////////
// PROCS //
///////////
@@ -311,8 +314,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
* * old_species - datum, used when regenerate organs is called in a switching species to remove old mutant organs.
* * replace_current - boolean, forces all old organs to get deleted whether or not they pass the species' ability to keep that organ
* * excluded_zones - list, add zone defines to block organs inside of the zones from getting handled. see headless mutation for an example
* * visual_only - boolean, only load organs that change how the species looks. Do not use for normal gameplay stuff
*/
/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE,list/excluded_zones)
/datum/species/proc/regenerate_organs(mob/living/carbon/C, datum/species/old_species, replace_current = TRUE, list/excluded_zones, visual_only = FALSE)
//what should be put in if there is no mutantorgan (brains handled separately)
var/list/slot_mutantorgans = list(ORGAN_SLOT_BRAIN = mutantbrain, ORGAN_SLOT_HEART = mutantheart, ORGAN_SLOT_LUNGS = mutantlungs, ORGAN_SLOT_APPENDIX = mutantappendix, \
ORGAN_SLOT_EYES = mutanteyes, ORGAN_SLOT_EARS = mutantears, ORGAN_SLOT_TONGUE = mutanttongue, ORGAN_SLOT_LIVER = mutantliver, ORGAN_SLOT_STOMACH = mutantstomach)
@@ -322,8 +326,12 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/obj/item/organ/oldorgan = C.getorganslot(slot) //used in removing
var/obj/item/organ/neworgan = slot_mutantorgans[slot] //used in adding
if(visual_only && !initial(neworgan.visual))
continue
var/used_neworgan = FALSE
neworgan = new neworgan()
neworgan = SSwardrobe.provide_type(neworgan)
var/should_have = neworgan.get_availability(src) //organ proc that points back to a species trait (so if the species is supposed to have this organ)
if(oldorgan && (!should_have || replace_current) && !(oldorgan.zone in excluded_zones) && !(oldorgan.organ_flags & ORGAN_UNREMOVABLE))
@@ -339,7 +347,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
oldorgan.Remove(C,TRUE)
QDEL_NULL(oldorgan) //we cannot just tab this out because we need to skip the deleting if it is a decoy brain.
if(oldorgan)
oldorgan.setOrganDamage(0)
else if(should_have && !(initial(neworgan.zone) in excluded_zones))
@@ -363,7 +370,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
for(var/organ_path in mutant_organs)
var/obj/item/organ/current_organ = C.getorgan(organ_path)
if(!current_organ || replace_current)
var/obj/item/organ/replacement = new organ_path()
var/obj/item/organ/replacement = SSwardrobe.provide_type(organ_path)
// If there's an existing mutant organ, we're technically replacing it.
// Let's abuse the snowflake proc that skillchips added. Basically retains
// feature parity with every other organ too.
@@ -406,7 +413,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
C.mob_biotypes = inherent_biotypes
regenerate_organs(C,old_species)
regenerate_organs(C, old_species, visual_only = C.visual_only_organs)
if(exotic_bloodtype && C.dna.blood_type != exotic_bloodtype)
C.dna.blood_type = exotic_bloodtype
@@ -426,6 +433,16 @@ GLOBAL_LIST_EMPTY(features_by_species)
else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand
INVOKE_ASYNC(C, /mob/proc/put_in_hands, new mutanthands)
if(ishuman(C))
var/mob/living/carbon/human/human = C
for(var/obj/item/organ/external/organ_path as anything in external_organs)
//Load a persons preferences from DNA
var/feature_key_name = human.dna.features[initial(organ_path.feature_key)]
var/obj/item/organ/external/new_organ = SSwardrobe.provide_type(organ_path)
new_organ.set_sprite(feature_key_name)
new_organ.Insert(human)
for(var/X in inherent_traits)
ADD_TRAIT(C, X, SPECIES_TRAIT)
@@ -2249,3 +2266,21 @@ GLOBAL_LIST_EMPTY(features_by_species)
/// Returns the species's scream sound.
/datum/species/proc/get_scream_sound(mob/living/carbon/human/human)
return
/datum/species/proc/get_types_to_preload()
var/list/to_store = list()
to_store += mutant_organs
for(var/obj/item/organ/external/horny as anything in external_organs)
to_store += horny //Haha get it?
//Don't preload brains, cause reuse becomes a horrible headache
to_store += mutantheart
to_store += mutantlungs
to_store += mutanteyes
to_store += mutantears
to_store += mutanttongue
to_store += mutantliver
to_store += mutantstomach
to_store += mutantappendix
//We don't cache mutant hands because it's not constrained enough, too high a potential for failure
return to_store
@@ -99,6 +99,7 @@
//useless organs we throw in just to fuck with surgeons a bit more
/obj/item/organ/fly
desc = "You have no idea what the hell this is, or how it manages to keep something alive in any capacity."
visual = FALSE
/obj/item/organ/fly/Initialize(mapload)
. = ..()
@@ -28,7 +28,7 @@
payday_modifier = 0.75
family_heirlooms = list(/obj/item/flashlight/lantern/heirloom_moth)
/datum/species/moth/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE,list/excluded_zones)
/datum/species/moth/regenerate_organs(mob/living/carbon/C, datum/species/old_species, replace_current= TRUE, list/excluded_zones, visual_only)
. = ..()
if(ishuman(C))
var/mob/living/carbon/human/H = C
+1
View File
@@ -6,6 +6,7 @@
name = "appendix"
icon_state = "appendix"
base_icon_state = "appendix"
visual = FALSE
zone = BODY_ZONE_PRECISE_GROIN
slot = ORGAN_SLOT_APPENDIX
food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/bad_food = 5)
@@ -2,6 +2,7 @@
/obj/item/organ/cyberimp
name = "cybernetic implant"
desc = "A state-of-the-art implant that improves a baseline's functionality."
visual = FALSE
status = ORGAN_ROBOTIC
organ_flags = ORGAN_SYNTHETIC
var/implant_color = "#FFFFFF"
+2
View File
@@ -4,6 +4,7 @@
desc = "There are three parts to the ear. Inner, middle and outer. Only one of these parts should be normally visible."
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EARS
visual = FALSE
gender = PLURAL
healing_factor = STANDARD_ORGAN_HEALING
@@ -63,6 +64,7 @@
name = "cat ears"
icon = 'icons/obj/clothing/hats.dmi'
icon_state = "kitty"
visual = TRUE
damage_multiplier = 2
//SKYRAT EDIT REMOVAL BEGIN - CUSTOMIZATION
@@ -9,6 +9,7 @@
///Unremovable is until the features are completely finished
organ_flags = ORGAN_UNREMOVABLE | ORGAN_EDIBLE
visual = TRUE
///Sometimes we need multiple layers, for like the back, middle and front of the person
var/layers
+6 -3
View File
@@ -2,6 +2,7 @@
name = BODY_ZONE_PRECISE_EYES
icon_state = "eyeballs"
desc = "I see you!"
visual = TRUE
zone = BODY_ZONE_PRECISE_EYES
slot = ORGAN_SLOT_EYES
gender = PLURAL
@@ -44,7 +45,6 @@
old_eye_color = human_owner.eye_color
if(eye_color)
human_owner.eye_color = eye_color
human_owner.regenerate_icons()
else
eye_color = human_owner.eye_color
if(HAS_TRAIT(human_owner, TRAIT_NIGHT_VISION) && !lighting_alpha)
@@ -62,7 +62,6 @@
is_emissive = TRUE
if(eye_color)
affected_human.eye_color = eye_color
affected_human.regenerate_icons()
else
eye_color = affected_human.eye_color
if(HAS_TRAIT(affected_human, TRAIT_NIGHT_VISION) && !lighting_alpha)
@@ -79,7 +78,7 @@
if(ishuman(eye_owner) && eye_color)
var/mob/living/carbon/human/human_owner = eye_owner
human_owner.eye_color = old_eye_color
human_owner.regenerate_icons()
human_owner.update_body()
eye_owner.cure_blind(EYE_DAMAGE)
eye_owner.cure_nearsighted(EYE_DAMAGE)
eye_owner.set_blindness(0)
@@ -88,6 +87,10 @@
eye_owner.update_sight()
is_emissive = FALSE
//Gotta reset the eye color, because that persists
/obj/item/organ/eyes/enter_wardrobe()
. = ..()
eye_color = initial(eye_color)
/obj/item/organ/eyes/on_life(delta_time, times_fired)
. = ..()
+2 -1
View File
@@ -3,6 +3,7 @@
desc = "I feel bad for the heartless bastard who lost this."
icon_state = "heart-on"
base_icon_state = "heart"
visual = FALSE
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_HEART
@@ -256,6 +257,7 @@
/obj/item/organ/heart/ethereal
name = "crystal core"
icon_state = "ethereal_heart" //Welp. At least it's more unique in functionaliy.
visual = TRUE //This is used by the ethereal species for color
desc = "A crystal-like organ that functions similarly to a heart for Ethereals. It can revive its owner."
///Cooldown for the next time we can crystalize
@@ -273,7 +275,6 @@
. = ..()
add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY)
/obj/item/organ/heart/ethereal/Insert(mob/living/carbon/owner, special = 0)
. = ..()
RegisterSignal(owner, COMSIG_MOB_STATCHANGE, .proc/on_stat_change)
+1
View File
@@ -4,6 +4,7 @@
/obj/item/organ/liver
name = "liver"
icon_state = "liver"
visual = FALSE
w_class = WEIGHT_CLASS_SMALL
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_LIVER
+1
View File
@@ -3,6 +3,7 @@
var/operated = FALSE //whether we can still have our damages fixed through surgery
name = "lungs"
icon_state = "lungs"
visual = FALSE
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_LUNGS
gender = PLURAL
@@ -33,6 +33,8 @@
var/reagent_vol = 10
var/failure_time = 0
///Do we effect the appearance of our mob. Used to save time in preference code
var/visual = TRUE
// Players can look at prefs before atoms SS init, and without this
// they would not be able to see external organs, such as moth wings.
@@ -42,6 +44,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
/obj/item/organ/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
if(organ_flags & ORGAN_EDIBLE)
AddComponent(/datum/component/edible,\
initial_reagents = food_reagents,\
@@ -153,10 +156,14 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
if(damage > high_threshold)
. += span_warning("[src] is starting to look discolored.")
/obj/item/organ/Initialize(mapload)
. = ..()
///Used as callbacks by object pooling
/obj/item/organ/proc/exit_wardrobe()
START_PROCESSING(SSobj, src)
//See above
/obj/item/organ/proc/enter_wardrobe()
STOP_PROCESSING(SSobj, src)
/obj/item/organ/Destroy()
if(owner)
// The special flag is important, because otherwise mobs can die
@@ -4,6 +4,7 @@
/obj/item/organ/stomach
name = "stomach"
icon_state = "stomach"
visual = FALSE
w_class = WEIGHT_CLASS_SMALL
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_STOMACH
+1
View File
@@ -3,6 +3,7 @@
/obj/item/organ/tail
name = "tail"
desc = "A severed tail. What did you cut this off of?"
visual = TRUE
icon_state = "severedtail"
zone = BODY_ZONE_PRECISE_GROIN
slot = ORGAN_SLOT_TAIL
+1
View File
@@ -2,6 +2,7 @@
name = "tongue"
desc = "A fleshy muscle mostly used for lying."
icon_state = "tonguenormal"
visual = FALSE
zone = BODY_ZONE_PRECISE_MOUTH
slot = ORGAN_SLOT_TONGUE
attack_verb_continuous = list("licks", "slobbers", "slaps", "frenches", "tongues")
@@ -1,6 +1,7 @@
/obj/item/organ/vocal_cords //organs that are activated through speech with the :x/MODE_KEY_VOCALCORDS channel
name = "vocal cords"
icon_state = "appendix"
visual = FALSE
zone = BODY_ZONE_PRECISE_MOUTH
slot = ORGAN_SLOT_VOICE
gender = PLURAL
@@ -18,6 +19,7 @@
owner.say(message, spans = spans, sanitize = FALSE)
/obj/item/organ/adamantine_resonator
visual = FALSE
name = "adamantine resonator"
desc = "Fragments of adamantine exist in all golems, stemming from their origins as purely magical constructs. These are used to \"hear\" messages from their leaders."
zone = BODY_ZONE_HEAD
+1
View File
@@ -62,6 +62,7 @@
#include "crayons.dm"
#include "create_and_destroy.dm"
#include "designs.dm"
#include "dummy_spawn.dm"
#include "dynamic_ruleset_sanity.dm"
#include "egg_glands.dm"
#include "emoting.dm"
+25
View File
@@ -0,0 +1,25 @@
///This set of tests is focused on ensuring the stability of preference dummies
///And by extension the hacks built to make them fast
///Organ consistency, object pooling via the wardrobe ss, etc
//Test spawning one of every species
/datum/unit_test/dummy_spawn_species
/datum/unit_test/dummy_spawn_species/Run()
var/mob/living/carbon/human/dummy/lad = allocate(/mob/living/carbon/human/dummy)
for(var/datum/species/testing_testing as anything in subtypesof(/datum/species))
lad.set_species(testing_testing, icon_update = FALSE, pref_load = TRUE) //I wonder if I should somehow hook into the species pref here
///Equips and devests our dummy of one of every job outfit
/datum/unit_test/dummy_spawn_outfit
/datum/unit_test/dummy_spawn_outfit/Run()
var/mob/living/carbon/human/dummy/lad = allocate(/mob/living/carbon/human/dummy)
for(var/datum/job/one_two_three as anything in subtypesof(/datum/job))
var/datum/job/can_you_hear_this = SSjob.GetJobType(one_two_three)
if(!can_you_hear_this)
log_world("Job type [one_two_three] could not be retrieved from SSjob")
continue
lad.job = can_you_hear_this
lad.dress_up_as_job(can_you_hear_this, TRUE)
lad.wipe_state() //Nuke it all
@@ -3,6 +3,7 @@
desc = "An implant that can be placed in a user's head to control circuits using their brain."
icon = 'icons/obj/wiremod.dmi'
icon_state = "bci"
visual = FALSE
zone = BODY_ZONE_HEAD
w_class = WEIGHT_CLASS_TINY
+1
View File
@@ -4,6 +4,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_ZOMBIE
icon_state = "blacktumor"
visual = FALSE
var/causes_damage = TRUE
var/datum/species/old_species = /datum/species/human
var/living_transformation_time = 30
@@ -182,7 +182,7 @@ GLOBAL_LIST_EMPTY(total_uf_len_by_block)
holder.transform = holder.transform.Translate(0, translate)
current_body_size = features["body_size"]
/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, var/list/override_features, var/list/override_mutantparts, var/list/override_markings, retain_features = FALSE, retain_mutantparts = FALSE)
/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE, var/list/override_features, var/list/override_mutantparts, var/list/override_markings, retain_features = FALSE, retain_mutantparts = FALSE)
if(QDELETED(src))
CRASH("You're trying to change your species post deletion, this is a recipe for madness")
if(mrace && has_dna())
@@ -194,7 +194,7 @@ GLOBAL_LIST_EMPTY(total_uf_len_by_block)
else
return
deathsound = new_race.deathsound
dna.species.on_species_loss(src, new_race)
dna.species.on_species_loss(src, new_race, pref_load)
var/datum/species/old_species = dna.species
dna.species = new_race
@@ -221,7 +221,7 @@ GLOBAL_LIST_EMPTY(total_uf_len_by_block)
dna.update_body_size()
dna.species.on_species_gain(src, old_species)
dna.species.on_species_gain(src, old_species, pref_load)
if(ishuman(src))
@@ -62,6 +62,9 @@
randname = "[randname]-[rand(100, 999)]"
return randname
/datum/species/robotic/get_types_to_preload()
return ..() - typesof(/obj/item/organ/cyberimp/arm/power_cord) // Don't cache things that lead to hard deletions.
/datum/species/robotic/ipc
name = "I.P.C."
id = SPECIES_IPC
+1
View File
@@ -511,6 +511,7 @@
#include "code\controllers\subsystem\title.dm"
#include "code\controllers\subsystem\vis_overlays.dm"
#include "code\controllers\subsystem\vote.dm"
#include "code\controllers\subsystem\wardrobe.dm"
#include "code\controllers\subsystem\weather.dm"
#include "code\controllers\subsystem\wiremod_composite.dm"
#include "code\controllers\subsystem\processing\acid.dm"