From 9b605b9cc0a320860cb4c91018ee94f1d8b78719 Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Wed, 8 Dec 2021 16:20:03 -0800 Subject: [PATCH] Speeds up the preference menu, significantly. Adds object pooling, other stuff too (#63225) * Removes like 50% of the cost of using the ui, it turns out that the storage component is fucking moronic. Likely significantly reduces the overtime of typecacheof * Reduces the cost of reloading the dummy by ~50% Turns out just initializing and deleting organs was like half the cost of reloading a default dummy. It occured to me (Mothblocks) that we don't actually care about any organs we can't see or that don't effect visuals. So almost all of our organ loading can just be skipped. This saves a significant chunk of cpu time, items next! Co-authored-by: Seth Scherer --- code/__DEFINES/subsystems.dm | 21 +- code/controllers/subsystem/wardrobe.dm | 358 ++++++++++++++++++ code/datums/components/storage/storage.dm | 29 +- code/datums/outfit.dm | 79 +++- code/game/objects/items/body_egg.dm | 1 + code/game/objects/items/devices/PDA/PDA.dm | 73 ++-- .../objects/items/devices/PDA/PDA_types.dm | 16 +- code/game/objects/items/devices/PDA/cart.dm | 8 - .../objects/items/implants/implant_track.dm | 12 +- .../objects/items/implants/implantuplink.dm | 2 + code/game/objects/items/storage/belt.dm | 137 +++++-- code/game/objects/items/storage/boxes.dm | 9 + code/game/objects/items/storage/storage.dm | 10 + .../antagonists/nightmare/nightmare_organs.dm | 1 + code/modules/client/preferences.dm | 1 + code/modules/clothing/spacesuits/hardsuit.dm | 2 +- code/modules/jobs/job_types/_job.dm | 12 + code/modules/jobs/job_types/assistant.dm | 2 + code/modules/jobs/job_types/clown.dm | 5 + code/modules/jobs/job_types/cook.dm | 5 + code/modules/jobs/job_types/janitor.dm | 6 + code/modules/jobs/job_types/lawyer.dm | 5 + code/modules/jobs/job_types/scientist.dm | 4 + .../mining/equipment/regenerative_core.dm | 1 + code/modules/mob/living/brain/brain_item.dm | 1 + .../modules/mob/living/carbon/alien/organs.dm | 1 + .../mob/living/carbon/carbon_defines.dm | 3 + code/modules/mob/living/carbon/human/dummy.dm | 55 +++ .../mob/living/carbon/human/species.dm | 39 +- .../carbon/human/species_types/flypeople.dm | 1 + .../carbon/human/species_types/mothmen.dm | 2 +- code/modules/surgery/organs/appendix.dm | 1 + .../surgery/organs/augments_internal.dm | 1 + code/modules/surgery/organs/ears.dm | 2 + .../organs/external/_external_organs.dm | 1 + code/modules/surgery/organs/eyes.dm | 9 +- code/modules/surgery/organs/heart.dm | 3 +- code/modules/surgery/organs/liver.dm | 1 + code/modules/surgery/organs/lungs.dm | 1 + code/modules/surgery/organs/organ_internal.dm | 11 +- .../surgery/organs/stomach/_stomach.dm | 1 + code/modules/surgery/organs/tails.dm | 1 + code/modules/surgery/organs/tongue.dm | 1 + code/modules/surgery/organs/vocal_cords.dm | 2 + code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/dummy_spawn.dm | 25 ++ .../wiremod/shell/brain_computer_interface.dm | 1 + code/modules/zombie/organs.dm | 1 + tgstation.dme | 1 + 49 files changed, 843 insertions(+), 122 deletions(-) create mode 100644 code/controllers/subsystem/wardrobe.dm create mode 100644 code/modules/unit_tests/dummy_spawn.dm diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index ddc2e1bc043..00c31e5f887 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -259,7 +259,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 @@ -268,6 +268,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) diff --git a/code/controllers/subsystem/wardrobe.dm b/code/controllers/subsystem/wardrobe.dm new file mode 100644 index 00000000000..55ab24c3d69 --- /dev/null +++ b/code/controllers/subsystem/wardrobe.dm @@ -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) diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 49f8acc0152..398533798bd 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -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() diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm index c27d16efa91..69186ff8ae1 100755 --- a/code/datums/outfit.dm +++ b/code/datums/outfit.dm @@ -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]") @@ -318,6 +321,40 @@ 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 + + return preload + /// Return a json list of this outfit /datum/outfit/proc/get_json_data() . = list() diff --git a/code/game/objects/items/body_egg.dm b/code/game/objects/items/body_egg.dm index dc769ead283..296aebc40a7 100644 --- a/code/game/objects/items/body_egg.dm +++ b/code/game/objects/items/body_egg.dm @@ -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 diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 5accfb8efc9..d73239213dd 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -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) @@ -160,6 +173,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 @@ -967,9 +988,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 @@ -979,11 +999,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() @@ -1182,18 +1200,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() @@ -1297,6 +1303,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 diff --git a/code/game/objects/items/devices/PDA/PDA_types.dm b/code/game/objects/items/devices/PDA/PDA_types.dm index 9b5db73659e..0242f9a1fad 100644 --- a/code/game/objects/items/devices/PDA/PDA_types.dm +++ b/code/game/objects/items/devices/PDA/PDA_types.dm @@ -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" diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 64af4990ee1..b70262ad965 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -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!" diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm index 3b362f2fe72..76244f23f30 100644 --- a/code/game/objects/items/implants/implant_track.dm +++ b/code/game/objects/items/implants/implant_track.dm @@ -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) diff --git a/code/game/objects/items/implants/implantuplink.dm b/code/game/objects/items/implants/implantuplink.dm index 034398f3514..dc3cda32d98 100644 --- a/code/game/objects/items/implants/implantuplink.dm +++ b/code/game/objects/items/implants/implantuplink.dm @@ -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) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index b0374253579..e29889fe601 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -80,24 +80,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/experimental(src)//This can be changed if this is too much - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/extinguisher/mini(src) - 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/experimental, src)//This can be changed if this is too much //It's been 5 years + 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, 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/experimental + to_preload += /obj/item/multitool + to_preload += /obj/item/stack/cable_coil + to_preload += /obj/item/extinguisher/mini + to_preload += /obj/item/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) @@ -118,22 +146,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) @@ -210,16 +263,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." diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 89041f46b8e..5ca77cac5b4 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -133,6 +133,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 diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index eeaf6772a8b..f5b1c1830e4 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -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 diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm index f1a77d69b76..711cbef4ac7 100644 --- a/code/modules/antagonists/nightmare/nightmare_organs.dm +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -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. diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index c3ac4973d27..e056e54b74c 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -487,6 +487,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() diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index cdd8e170192..ebe13b69953 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -572,7 +572,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 diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 9d3a5cc628c..235dbc0fc71 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -249,6 +249,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 @@ -324,6 +326,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 diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index d8274d48c12..bc35186f457 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -56,6 +56,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 diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm index a87c906380e..04391ad2e21 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -73,6 +73,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) diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index 37ddab85974..b90f5d84202 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -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 diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm index cc00c5cef5d..2d709cf41ba 100644 --- a/code/modules/jobs/job_types/janitor.dm +++ b/code/modules/jobs/job_types/janitor.dm @@ -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 diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm index 1d9bfbff66a..be4ed72678a 100644 --- a/code/modules/jobs/job_types/lawyer.dm +++ b/code/modules/jobs/job_types/lawyer.dm @@ -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 diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index c198986badd..53b19952c0c 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -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 diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index 1f34d10dbac..0cb38a5b6c9 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -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 diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 53c73da528f..9d957a753f0 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -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 diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index d4c5a44f237..9b909dc9783 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -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() diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index e4e68a0e566..765ead5145e 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -106,4 +106,7 @@ /// 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) diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index 153712eba9a..d73c40b342b 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -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) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 4490af17a3b..53950856192 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -214,6 +214,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 // /////////// @@ -313,8 +316,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) @@ -324,8 +328,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)) @@ -341,7 +349,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)) @@ -365,7 +372,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 @@ -432,8 +439,8 @@ GLOBAL_LIST_EMPTY(features_by_species) //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 = new organ_path(null, feature_key_name, human.body_type) - + 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) @@ -2140,3 +2147,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 diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index 7be67c4ab28..8221228ff4b 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -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) . = ..() diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 08f98465376..92b63ef988c 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -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 diff --git a/code/modules/surgery/organs/appendix.dm b/code/modules/surgery/organs/appendix.dm index 4d26156adb6..7bc0acba3bb 100644 --- a/code/modules/surgery/organs/appendix.dm +++ b/code/modules/surgery/organs/appendix.dm @@ -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) diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index 8b46c4d7ea8..2832ca9696d 100644 --- a/code/modules/surgery/organs/augments_internal.dm +++ b/code/modules/surgery/organs/augments_internal.dm @@ -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" diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm index e54f961d8b2..e033ebe1296 100644 --- a/code/modules/surgery/organs/ears.dm +++ b/code/modules/surgery/organs/ears.dm @@ -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 /obj/item/organ/ears/cat/Insert(mob/living/carbon/human/ear_owner, special = 0, drop_if_replaced = TRUE) diff --git a/code/modules/surgery/organs/external/_external_organs.dm b/code/modules/surgery/organs/external/_external_organs.dm index 46ac2808ada..4a5a1fe0f6f 100644 --- a/code/modules/surgery/organs/external/_external_organs.dm +++ b/code/modules/surgery/organs/external/_external_organs.dm @@ -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 diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index 996c3c5c0fc..86c73e8c6a4 100644 --- a/code/modules/surgery/organs/eyes.dm +++ b/code/modules/surgery/organs/eyes.dm @@ -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 @@ -41,7 +42,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) @@ -57,7 +57,6 @@ old_eye_color = affected_human.eye_color 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) @@ -74,7 +73,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) @@ -82,6 +81,10 @@ eye_owner.clear_fullscreen("eye_damage", 0) eye_owner.update_sight() +//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) . = ..() diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 52c2fa74102..f1cbb1220f8 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -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) diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index 7c5b867ddf5..f148b74a509 100755 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -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 diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 049bd168ea5..76a002bc40d 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -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 diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm index b1fec6f9d3e..54e3a22865f 100644 --- a/code/modules/surgery/organs/organ_internal.dm +++ b/code/modules/surgery/organs/organ_internal.dm @@ -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 diff --git a/code/modules/surgery/organs/stomach/_stomach.dm b/code/modules/surgery/organs/stomach/_stomach.dm index 812de868568..536489915dc 100644 --- a/code/modules/surgery/organs/stomach/_stomach.dm +++ b/code/modules/surgery/organs/stomach/_stomach.dm @@ -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 diff --git a/code/modules/surgery/organs/tails.dm b/code/modules/surgery/organs/tails.dm index 6f08d0d28e2..07e7169455b 100644 --- a/code/modules/surgery/organs/tails.dm +++ b/code/modules/surgery/organs/tails.dm @@ -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 diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm index 361f39a6af1..7561be778c3 100644 --- a/code/modules/surgery/organs/tongue.dm +++ b/code/modules/surgery/organs/tongue.dm @@ -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") diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index 535615b789a..7d7ffbb6a82 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -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 diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index a35e369d533..882950ddc7f 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -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" diff --git a/code/modules/unit_tests/dummy_spawn.dm b/code/modules/unit_tests/dummy_spawn.dm new file mode 100644 index 00000000000..f708cd181da --- /dev/null +++ b/code/modules/unit_tests/dummy_spawn.dm @@ -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 diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index 36cc2aa9656..853c9ba9857 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -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 diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index 9da0053701a..42cec530da3 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -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 diff --git a/tgstation.dme b/tgstation.dme index 0565e55d05c..3603dd12a4f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -466,6 +466,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"