diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index b92cce82d07..c425f6e40fb 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -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) 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 fddca983f16..c878ee4d125 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 ce34a5ed17f..4fd196670de 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]") @@ -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() 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 01f519f9eb8..c2fe14d28fe 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) @@ -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 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 1ed0538c371..f28c6527f4d 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 349b9b7f21f..0ff9be3f7bc 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 64458dbd2dc..993b63286dd 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -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." diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 1fdc907798b..3265c4bc0fc 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -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 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 4b2270bbafd..6b06597c7d1 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -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() diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 7b56a9e6a73..845380117ab 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -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 diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index cd409d9d438..4e552b5bd6a 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -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 diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 012c41a9905..98c12de01e0 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -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 diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm index e6b93620858..e40064cf6f8 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -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) 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 1d79fe2842b..5559f7d2101 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -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) diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index fc2fd152fdd..381d883fb12 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 4304017ef63..28b769125ab 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -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 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 1b39e832edc..e7e180131bc 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 f1d966b5c55..27e421f0250 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 //SKYRAT EDIT REMOVAL BEGIN - CUSTOMIZATION diff --git a/code/modules/surgery/organs/external/_external_organs.dm b/code/modules/surgery/organs/external/_external_organs.dm index 8d39a36dfec..dae67fc039e 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 cf4ffb4a419..c09c97a5c08 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 @@ -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) . = ..() 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 49cfafb3d2a..9d7b309f34e 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 6ed3b926ff3..5b02e379923 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/modular_skyrat/modules/customization/datums/dna.dm b/modular_skyrat/modules/customization/datums/dna.dm index e848f45b07b..340b49a8334 100644 --- a/modular_skyrat/modules/customization/datums/dna.dm +++ b/modular_skyrat/modules/customization/datums/dna.dm @@ -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)) diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/robotic.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/robotic.dm index 18bbdd51224..140b07d25af 100644 --- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/robotic.dm +++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/robotic.dm @@ -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 diff --git a/tgstation.dme b/tgstation.dme index 6a70854986c..b0fc4f61c5c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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"