From 1e546cd676f1d1ebe5fd1b203b24612f794c4916 Mon Sep 17 00:00:00 2001 From: Ling Date: Mon, 2 Jan 2023 01:22:11 +0100 Subject: [PATCH] Speeds up the preference menu, implement object pooling (#17133) * Speeds up the preference menu https://github.com/tgstation/tgstation/pull/63225 * Fix compile --- code/__DEFINES/DNA.dm | 2 + code/__DEFINES/subsystems.dm | 20 + code/controllers/subsystem/wardrobe.dm | 358 ++++++++++++++++++ code/datums/components/storage/storage.dm | 29 +- code/datums/outfit.dm | 75 +++- code/game/objects/items/body_egg.dm | 1 + code/game/objects/items/devices/PDA/PDA.dm | 68 +++- .../objects/items/devices/PDA/PDA_types.dm | 16 +- code/game/objects/items/devices/PDA/cart.dm | 6 - code/game/objects/items/devices/scanners.dm | 4 +- .../objects/items/implants/implant_track.dm | 16 +- code/game/objects/items/storage/belt.dm | 200 ++++++---- code/game/objects/items/storage/boxes.dm | 9 + code/game/objects/items/storage/storage.dm | 10 + .../changeling/powers/regenerate.dm | 4 +- 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/clown.dm | 5 + code/modules/jobs/job_types/cook.dm | 4 + 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 | 50 +++ .../mob/living/carbon/human/species.dm | 248 ++++++------ .../living/carbon/human/species_types/IPC.dm | 4 +- .../carbon/human/species_types/dullahan.dm | 2 +- .../carbon/human/species_types/felinid.dm | 3 +- .../human/species_types/lizardpeople.dm | 2 +- .../carbon/human/species_types/mothmen.dm | 2 +- .../carbon/human/species_types/polysmorphs.dm | 2 +- .../human/species_types/shadowpeople.dm | 3 +- .../carbon/human/species_types/vampire.dm | 2 +- code/modules/surgery/organs/appendix.dm | 4 + .../surgery/organs/augments_internal.dm | 1 + code/modules/surgery/organs/ears.dm | 2 + code/modules/surgery/organs/eyes.dm | 9 +- code/modules/surgery/organs/heart.dm | 4 + code/modules/surgery/organs/liver.dm | 4 + code/modules/surgery/organs/lungs.dm | 4 + code/modules/surgery/organs/organ_internal.dm | 25 ++ code/modules/surgery/organs/stomach.dm | 4 + code/modules/surgery/organs/tails.dm | 8 +- code/modules/surgery/organs/tongue.dm | 1 + code/modules/surgery/organs/vocal_cords.dm | 2 + code/modules/zombie/organs.dm | 1 + yogstation.dme | 1 + 50 files changed, 962 insertions(+), 283 deletions(-) create mode 100644 code/controllers/subsystem/wardrobe.dm diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index 9409c29b3ab0..1b41f155696d 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -151,6 +151,8 @@ #define ROBOTIC_LIMBS 26 /// have no mouth to ingest/eat with #define NOMOUTH 27 +/// has a tail +#define HAS_TAIL 28 //organ slots #define ORGAN_SLOT_BRAIN "brain" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 05e8c30ff65d..fcaf6e44f1fa 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -269,3 +269,23 @@ /// The timer key used to know how long subsystem initialization takes #define SS_INIT_TIMER_KEY "ss_init" + + +// 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 diff --git a/code/controllers/subsystem/wardrobe.dm b/code/controllers/subsystem/wardrobe.dm new file mode 100644 index 000000000000..55ab24c3d69c --- /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 0f0b57b212fe..49273b5fe9e4 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -119,14 +119,33 @@ /datum/component/storage/PreTransfer() update_actions() -/datum/component/storage/proc/set_holdable(can_hold_list, cant_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) + +/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) - can_hold = typecacheof(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) - cant_hold = typecacheof(cant_hold_list) + if (cant_hold_list != null) + 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 2e545ed19940..eecc59aeb24b 100755 --- a/code/datums/outfit.dm +++ b/code/datums/outfit.dm @@ -75,6 +75,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 + ///ID of the slot containing a gas tank var/internals_slot = null @@ -156,49 +159,49 @@ //Start with uniform,suit,backpack for additional slots if(uniform) - H.equip_to_slot_or_del(new uniform(H),SLOT_W_UNIFORM, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(uniform, H), SLOT_W_UNIFORM, TRUE) if(suit) - H.equip_to_slot_or_del(new suit(H),SLOT_WEAR_SUIT, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(suit, H), SLOT_WEAR_SUIT, TRUE) if(back) - H.equip_to_slot_or_del(new back(H),SLOT_BACK, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(back, H), SLOT_BACK, TRUE) if(belt) - H.equip_to_slot_or_del(new belt(H),SLOT_BELT, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(belt, H), SLOT_BELT, TRUE) if(gloves) - H.equip_to_slot_or_del(new gloves(H),SLOT_GLOVES, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(gloves, H), SLOT_GLOVES, TRUE) if(shoes) - H.equip_to_slot_or_del(new shoes(H),SLOT_SHOES, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(shoes, H), SLOT_SHOES, TRUE) if(head) - H.equip_to_slot_or_del(new head(H),SLOT_HEAD, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(head, H), SLOT_HEAD, TRUE) if(mask) - H.equip_to_slot_or_del(new mask(H),SLOT_WEAR_MASK, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(mask, H), SLOT_WEAR_MASK, TRUE) if(neck) - H.equip_to_slot_or_del(new neck(H),SLOT_NECK, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(neck, H), SLOT_NECK, TRUE) if(ears) - H.equip_to_slot_or_del(new ears(H),SLOT_EARS, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(ears, H), SLOT_EARS, TRUE) if(glasses) - H.equip_to_slot_or_del(new glasses(H),SLOT_GLASSES, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(glasses, H), SLOT_GLASSES, TRUE) if(id) - H.equip_to_slot_or_del(new id(H),SLOT_WEAR_ID, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(id, H), SLOT_WEAR_ID, TRUE) if(suit_store) - H.equip_to_slot_or_del(new suit_store(H),SLOT_S_STORE, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(suit_store, H), SLOT_S_STORE, TRUE) 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),SLOT_L_STORE, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(l_pocket, H), SLOT_L_STORE, TRUE) if(r_pocket) - H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(r_pocket, H), SLOT_R_STORE, TRUE) if(box) if(!backpack_contents) @@ -212,7 +215,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),SLOT_IN_BACKPACK, TRUE) + H.equip_to_slot_or_del(SSwardrobe.provide_type(path, H),SLOT_IN_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 @@ -227,7 +230,7 @@ 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) H.update_body() @@ -288,6 +291,38 @@ listclearnulls(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 + + 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 91abab8e840d..373930eb4671 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 5ddd7dee933f..395810d763b1 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -44,7 +44,7 @@ GLOBAL_LIST_EMPTY(PDAs) //Main variables var/owner = null // String name of owner - var/default_cartridge = 0 // Access level defined by cartridge + var/default_cartridge = 0 // Typepath of the default cartridge to use var/obj/item/cartridge/cartridge = null //current cartridge var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. @@ -89,7 +89,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/overlays_x_offset = 0 //x offset to use for certain overlays var/underline_flag = TRUE //flag for underline @@ -121,13 +124,24 @@ 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) update_icon() +/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) @@ -151,6 +165,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 @@ -937,9 +959,8 @@ GLOBAL_LIST_EMPTY(PDAs) return if(inserted_item) - usr.put_in_hands(inserted_item) to_chat(usr, span_notice("You remove [inserted_item] from [src].")) - inserted_item = null + usr.put_in_hands(inserted_item) //Don't need to manage the pen ref, handled on Exited() update_icon() else to_chat(usr, span_warning("This PDA does not have a pen in it!")) @@ -1103,18 +1124,6 @@ GLOBAL_LIST_EMPTY(PDAs) qdel(src) return -/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 ..() - //pAI verb and proc for sending PDA messages. /mob/living/silicon/proc/cmd_send_pdamesg(mob/user) var/list/plist = list() @@ -1240,6 +1249,23 @@ GLOBAL_LIST_EMPTY(PDAs) /obj/item/pda/proc/pda_no_detonate() 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 e5adc83af366..26c1c8171736 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" desc = "A portable microcomputer by Thinktronic Systems, LTD. The surface is coated with polytetrafluoroethylene and banana drippings." ttone = "honk" @@ -81,7 +81,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 icon_state = "pda-mime" silent = TRUE ttone = "silence" @@ -113,13 +113,13 @@ /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 icon_state = "pda-rd" /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 icon_state = "pda-captain" /obj/item/pda/captain/Initialize() @@ -134,7 +134,7 @@ /obj/item/pda/quartermaster name = "quartermaster PDA" default_cartridge = /obj/item/cartridge/quartermaster - inserted_item = /obj/item/pen/fountain + insert_type = /obj/item/pen/fountain icon_state = "pda-qm" /obj/item/pda/shaftminer @@ -156,7 +156,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 icon_state = "pda-lawyer" ttone = "objection" @@ -175,7 +175,7 @@ icon_state = "pda-library" icon_alert = "pda-r-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! @@ -198,7 +198,7 @@ /obj/item/pda/bar name = "bartender PDA" icon_state = "pda-bartender" - 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 72d70f3f7525..66476d5ff460 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -49,12 +49,6 @@ var/mob/living/simple_animal/bot/active_bot var/list/botlist = list() -/obj/item/cartridge/Initialize() - . = ..() - 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" diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 065db469f900..c8e7a614e00f 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -363,9 +363,9 @@ GENE SCANNER mutant = TRUE else if (S.mutantlungs != initial(S.mutantlungs)) mutant = TRUE - else if (S.mutant_brain != initial(S.mutant_brain)) + else if (S.mutantbrain != initial(S.mutantbrain)) mutant = TRUE - else if (S.mutant_heart != initial(S.mutant_heart)) + else if (S.mutantheart != initial(S.mutantheart)) mutant = TRUE else if (S.mutanteyes != initial(S.mutanteyes)) mutant = TRUE diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm index ae19507ed359..9485cd08638a 100644 --- a/code/game/objects/items/implants/implant_track.dm +++ b/code/game/objects/items/implants/implant_track.dm @@ -9,17 +9,27 @@ name = "TRAC implant" desc = "A smaller tracking implant that supplies power for only a few minutes." var/lifespan = 5 MINUTES //how long does the implant last? + ///The id of the timer that's qdeleting us + var/timerid allow_teleport = FALSE -/obj/item/implant/tracking/tra32/Initialize() +/obj/item/implant/tracking/tra32/implant(mob/living/target, mob/user, silent, force) . = ..() - QDEL_IN(src, lifespan) + timerid = QDEL_IN(src, lifespan) + +/obj/item/implant/tracking/tra32/removed(mob/living/source, silent, special) + . = ..() + deltimer(timerid) + timerid = null + +/obj/item/implant/tracking/tra32/Destroy() + return ..() /obj/item/implant/tracking/tra32/emp_act(severity) //Demands to call the severity and I do not care to oppose it . = ..() Destroy() -/obj/item/implant/tracking/New() +/obj/item/implant/tracking/Initialize(mapload) ..() GLOB.tracked_implants += src diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index b4465e9fcb33..a54d0accc91d 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -99,67 +99,131 @@ icon_state = "utilitybelt_ce" item_state = "utility_ce" +/obj/item/storage/belt/utility/chief/full + preload = TRUE + /obj/item/storage/belt/utility/chief/full/PopulateContents() - new /obj/item/handdrill(src) - new /obj/item/jawsoflife(src) - new /obj/item/analyzer/ranged(src) - new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much - new /obj/item/multitool/tricorder(src) //yogs: changes the multitool to the tricorder and removes the analyzer - new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) - new /obj/item/extinguisher/mini(src) - new /obj/item/holosign_creator/multi/CE(src) + SSwardrobe.provide_type(/obj/item/handdrill, src) + SSwardrobe.provide_type(/obj/item/jawsoflife, src) + SSwardrobe.provide_type(/obj/item/analyzer/ranged, 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/tricorder, src) //yogs: changes the multitool to the tricorder and removes the analyzer + SSwardrobe.provide_type(/obj/item/stack/cable_coil, src) + SSwardrobe.provide_type(/obj/item/extinguisher/mini, src) + SSwardrobe.provide_type(/obj/item/holosign_creator/multi/CE, 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/handdrill + to_preload += /obj/item/jawsoflife + to_preload += /obj/item/analyzer/ranged + to_preload += /obj/item/weldingtool/experimental + to_preload += /obj/item/multitool/tricorder + to_preload += /obj/item/stack/cable_coil + to_preload += /obj/item/extinguisher/mini + to_preload += /obj/item/holosign_creator/multi/CE + return to_preload + +/obj/item/storage/belt/utility/chief/admin/full + preload = FALSE + /obj/item/storage/belt/utility/chief/admin/full/PopulateContents() - new /obj/item/construction/rcd/combat/admin(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/shuttle_creator/admin(src) - new /obj/item/handdrill(src) - new /obj/item/jawsoflife(src) - new /obj/item/analyzer/ranged(src) - new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much - new /obj/item/multitool/tricorder(src) //yogs: changes the multitool to the tricorder and removes the analyzer - new /obj/item/storage/bag/construction/admin/full(src) - new /obj/item/extinguisher/mini(src) - new /obj/item/holosign_creator/multi/CE(src) + SSwardrobe.provide_type(/obj/item/construction/rcd/combat/admin, src) + SSwardrobe.provide_type(/obj/item/pipe_dispenser, src) + SSwardrobe.provide_type(/obj/item/shuttle_creator/admin, src) + SSwardrobe.provide_type(/obj/item/handdrill, src) + SSwardrobe.provide_type(/obj/item/jawsoflife, src) + SSwardrobe.provide_type(/obj/item/analyzer/ranged, src) + SSwardrobe.provide_type(/obj/item/weldingtool/experimental, src) //This can be changed if this is too much + SSwardrobe.provide_type(/obj/item/multitool/tricorder, src) //yogs: changes the multitool to the tricorder and removes the analyzer + SSwardrobe.provide_type(/obj/item/storage/bag/construction/admin/full, src) + SSwardrobe.provide_type(/obj/item/extinguisher/mini, src) + SSwardrobe.provide_type(/obj/item/holosign_creator/multi/CE, src) /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,MAXCOIL,pick("red","yellow","orange")) + 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/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,MAXCOIL,pick("red","yellow","orange")) - new /obj/item/barrier_taperoll/engineering(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) + SSwardrobe.provide_type(/obj/item/barrier_taperoll/engineering, 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 + to_preload += /obj/item/barrier_taperoll/engineering + 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) - new /obj/item/barrier_taperoll/engineering() + 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) + SSwardrobe.provide_type(/obj/item/barrier_taperoll/engineering, 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 + to_preload += /obj/item/barrier_taperoll/engineering + return to_preload /obj/item/storage/belt/utility/servant/PopulateContents() - new /obj/item/screwdriver/brass(src) - new /obj/item/wirecutters/brass(src) - new /obj/item/wrench/brass(src) - new /obj/item/crowbar/brass(src) - new /obj/item/weldingtool/experimental/brass(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src, MAXCOIL, "yellow") + SSwardrobe.provide_type(/obj/item/screwdriver/brass, src) + SSwardrobe.provide_type(/obj/item/wirecutters/brass, src) + SSwardrobe.provide_type(/obj/item/wrench/brass, src) + SSwardrobe.provide_type(/obj/item/crowbar/brass, src) + SSwardrobe.provide_type(/obj/item/weldingtool/experimental/brass, src) + SSwardrobe.provide_type(/obj/item/multitool, src) + SSwardrobe.provide_type(/obj/item/stack/cable_coil, src) + +/obj/item/storage/belt/utility/servant/get_types_to_preload() + var/list/to_preload = list() //Yes this is a pain. Yes this is the point + to_preload += /obj/item/screwdriver/brass + to_preload += /obj/item/wirecutters/brass + to_preload += /obj/item/wrench/brass + to_preload += /obj/item/crowbar/brass + to_preload += /obj/item/weldingtool/experimental/brass + to_preload += /obj/item/multitool + to_preload += /obj/item/stack/cable_coil + return to_preload /obj/item/storage/belt/medical name = "medical belt" @@ -231,12 +295,12 @@ item_state = "medical_cmo" /obj/item/storage/belt/medical/chief/full/PopulateContents() - new /obj/item/scalpel/advanced(src) - new /obj/item/retractor/advanced(src) - new /obj/item/cautery/advanced(src) - new /obj/item/pinpointer/crew(src) - new /obj/item/sensor_device(src) - new /obj/item/healthanalyzer/advanced(src) + SSwardrobe.provide_type(/obj/item/scalpel/advanced, src) + SSwardrobe.provide_type(/obj/item/retractor/advanced, src) + SSwardrobe.provide_type(/obj/item/cautery/advanced, src) + SSwardrobe.provide_type(/obj/item/pinpointer/crew, src) + SSwardrobe.provide_type(/obj/item/sensor_device, src) + SSwardrobe.provide_type(/obj/item/healthanalyzer/advanced, src) /obj/item/storage/belt/security name = "security belt" @@ -281,12 +345,12 @@ )) /obj/item/storage/belt/security/full/PopulateContents() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/barrier_taperoll/police(src) + SSwardrobe.provide_type(/obj/item/reagent_containers/spray/pepper, src) + SSwardrobe.provide_type(/obj/item/restraints/handcuffs, src) + SSwardrobe.provide_type(/obj/item/grenade/flashbang, src) + SSwardrobe.provide_type(/obj/item/assembly/flash/handheld, src) + SSwardrobe.provide_type(/obj/item/melee/baton/loaded, src) + SSwardrobe.provide_type(/obj/item/barrier_taperoll/police, src) update_icon() /obj/item/storage/belt/security/chief @@ -302,13 +366,13 @@ STR.max_combined_w_class = 21 /obj/item/storage/belt/security/chief/full/PopulateContents() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/barrier_taperoll/police(src) - new /obj/item/shield/riot/tele(src) + SSwardrobe.provide_type(/obj/item/reagent_containers/spray/pepper, src) + SSwardrobe.provide_type(/obj/item/restraints/handcuffs, src) + SSwardrobe.provide_type(/obj/item/grenade/flashbang, src) + SSwardrobe.provide_type(/obj/item/assembly/flash/handheld, src) + SSwardrobe.provide_type(/obj/item/melee/baton/loaded, src) + SSwardrobe.provide_type(/obj/item/barrier_taperoll/police, src) + SSwardrobe.provide_type(/obj/item/shield/riot/tele, src) update_icon() /obj/item/storage/belt/security/webbing diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 520ab3a15df6..c36c0c54e881 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -137,6 +137,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(/obj/item/clothing/mask/breath) in src + var/obj/item/internals = locate(/obj/item/tank/internals/emergency_oxygen) in src + new /obj/item/tank/internals/plasmaman/belt(src) + qdel(mask) // Get rid of the items that shouldn't be + qdel(internals) + /obj/item/storage/box/survival_mining/PopulateContents() new /obj/item/clothing/mask/gas/explorer(src) new /obj/item/tank/internals/emergency_oxygen(src) diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index 836a62d2b05e..aee98909f0c1 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 @@ -48,3 +52,9 @@ /obj/item/storage/proc/emptyStorage() var/datum/component/storage/ST = GetComponent(/datum/component/storage) ST.do_quick_empty() + +/// 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/changeling/powers/regenerate.dm b/code/modules/antagonists/changeling/powers/regenerate.dm index 17bc57ebe754..fd3b3bddd15d 100644 --- a/code/modules/antagonists/changeling/powers/regenerate.dm +++ b/code/modules/antagonists/changeling/powers/regenerate.dm @@ -26,8 +26,8 @@ C.regenerate_limbs(1) if(!user.getorganslot(ORGAN_SLOT_BRAIN)) var/obj/item/organ/brain/B - if(C.has_dna() && C.dna.species.mutant_brain) - B = new C.dna.species.mutant_brain() + if(C.has_dna() && C.dna.species.mutantbrain) + B = new C.dna.species.mutantbrain() else B = new() B.organ_flags &= ~ORGAN_VITAL diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 541fa4395c2c..5410e6d7d5b7 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -2301,6 +2301,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) character.dna.species.mutant_bodyparts |= "tail_polysmorph" 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 a106d24a8f4b..e3320e840cc9 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -561,7 +561,7 @@ /obj/item/clothing/head/helmet/space/hardsuit/rd/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) 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 882052f55b6b..178f6e656890 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -207,6 +207,8 @@ box = /obj/item/storage/box/survival ipc_box = /obj/item/storage/box/ipc + preload = TRUE // These are used by the prefs ui, and also just kinda could use the extra help at roundstart + var/obj/item/id_type = /obj/item/card/id var/obj/item/modular_computer/pda_type = /obj/item/modular_computer/tablet/pda/preset/basic var/backpack = /obj/item/storage/backpack @@ -307,6 +309,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/clown.dm b/code/modules/jobs/job_types/clown.dm index 7e8ba583a197..00da71a3a908 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -69,6 +69,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 0f86ef9b24ec..56fa98635ed3 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -80,3 +80,7 @@ var/datum/martial_art/cqc/under_siege/justacook = new justacook.teach(H) +/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/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm index 52764839d9d1..a0d64409ad0a 100644 --- a/code/modules/jobs/job_types/lawyer.dm +++ b/code/modules/jobs/job_types/lawyer.dm @@ -60,3 +60,8 @@ if(J.lawyers>1) uniform = /obj/item/clothing/under/lawyer/purpsuit suit = /obj/item/clothing/suit/toggle/lawyer/purple + +/datum/outfit/job/lawyer/get_types_to_preload() + . = ..() + . += /obj/item/clothing/under/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 9157e162321a..2cbee9f257c2 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -61,3 +61,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 3c6109bd110b..e3768bf4f8b8 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -22,6 +22,7 @@ name = "regenerative core" desc = "All that remains of a hivelord. It can be used to heal quickly, but it will rapidly decay into uselessness. Radiation found in active space installments will slow its healing effects." icon_state = "roro core 2" + visual = FALSE item_flags = NOBLUDGEON slot = "hivecore" force = 0 diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 8a189e6fb49d..67551a1726df 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 c9e7c40213be..0dfc7d3df4b7 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 var/list/alien_powers = list() /obj/item/organ/alien/Initialize() diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index fa98bedce45f..8d96f588ca9b 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -83,6 +83,9 @@ var/list/all_scars var/visible_tumors = FALSE //if you are seem with some tumors, for examine + /// Only load in visual organs + var/visual_only_organs = FALSE + COOLDOWN_DECLARE(bleeding_message_cd) var/list/image/infra_images diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index 904b27adb9af..ce9b1e64d9af 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) @@ -14,6 +15,55 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) /mob/living/carbon/human/dummy/Life() 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) + +//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/proc/wipe_state() delete_equipment() cut_overlays(TRUE) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index ae5942ad598e..161b390d4fe8 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -148,30 +148,35 @@ GLOBAL_LIST_EMPTY(mentor_races) /// list of mobs that will ignore this species var/list/mob/living/ignored_by = list() //Breathing! - ///the mutant lungs, if different to the normal - var/obj/item/organ/lungs/mutantlungs = null ///what type of gas is breathed var/breathid = "o2" - ///the brain, if any - var/obj/item/organ/brain/mutant_brain = /obj/item/organ/brain - ///the heart - var/obj/item/organ/heart/mutant_heart = /obj/item/organ/heart - ///the eyes - var/obj/item/organ/eyes/mutanteyes = /obj/item/organ/eyes - ///the ears - var/obj/item/organ/ears/mutantears = /obj/item/organ/ears - ///the special hands, if any, for example the zombie has those claw hands - var/obj/item/mutanthands - ///the tongue, used for ssssssssssssss and other such speech impediments - var/obj/item/organ/tongue/mutanttongue = /obj/item/organ/tongue - ///the tail, if any - var/obj/item/organ/tail/mutanttail = null + //Do NOT remove by setting to null. use OR make a RESPECTIVE TRAIT (removing stomach? add the NOSTOMACH trait to your species) + //why does it work this way? because traits also disable the downsides of not having an organ, removing organs but not having the trait will make your species die + + ///Replaces default brain with a different organ + var/obj/item/organ/brain/mutantbrain = /obj/item/organ/brain + ///Replaces default heart with a different organ + var/obj/item/organ/heart/mutantheart = /obj/item/organ/heart + ///Replaces default lungs with a different organ + var/obj/item/organ/lungs/mutantlungs = /obj/item/organ/lungs + ///Replaces default eyes with a different organ + var/obj/item/organ/eyes/mutanteyes = /obj/item/organ/eyes + ///Replaces default ears with a different organ + var/obj/item/organ/ears/mutantears = /obj/item/organ/ears + ///Replaces default tongue with a different organ + var/obj/item/organ/tongue/mutanttongue = /obj/item/organ/tongue + ///Replaces default liver with a different organ + var/obj/item/organ/liver/mutantliver = /obj/item/organ/liver + ///Replaces default stomach with a different organ + var/obj/item/organ/stomach/mutantstomach = /obj/item/organ/stomach + ///Replaces default appendix with a different organ. + var/obj/item/organ/appendix/mutantappendix = /obj/item/organ/appendix + ///Forces a species tail + var/obj/item/organ/tail/mutanttail = /obj/item/organ/tail + ///Forces an item into this species' hands. Only an honorary mutantthing because this is not an organ and not loaded in the same way, you've been warned to do your research. + var/obj/item/mutanthands - ///The special liver, if any - var/obj/item/organ/liver/mutantliver - ///the special stomach, if any - var/obj/item/organ/stomach/mutantstomach var/override_float = FALSE ///Bitflag that controls what in game ways can select this species as a spawnable source. Think magic mirror and pride mirror, slime extract, ERT etc, see defines in __DEFINES/mobs.dm, defaults to NONE, so people actually have to think about it @@ -182,6 +187,9 @@ GLOBAL_LIST_EMPTY(mentor_races) var/smells_like = "something alien" + //Should we preload this species's organs? + var/preload = TRUE + /////////// // PROCS // /////////// @@ -257,135 +265,90 @@ GLOBAL_LIST_EMPTY(mentor_races) /datum/species/proc/has_toes() return FALSE -//Will regenerate missing organs -/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE) - var/obj/item/organ/brain/brain = C.getorganslot(ORGAN_SLOT_BRAIN) - var/obj/item/organ/heart/heart = C.getorganslot(ORGAN_SLOT_HEART) - var/obj/item/organ/lungs/lungs = C.getorganslot(ORGAN_SLOT_LUNGS) - var/obj/item/organ/appendix/appendix = C.getorganslot(ORGAN_SLOT_APPENDIX) - var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) - var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) - var/obj/item/organ/tongue/tongue = C.getorganslot(ORGAN_SLOT_TONGUE) - var/obj/item/organ/liver/liver = C.getorganslot(ORGAN_SLOT_LIVER) - var/obj/item/organ/stomach/stomach = C.getorganslot(ORGAN_SLOT_STOMACH) - var/obj/item/organ/tail/tail = C.getorganslot(ORGAN_SLOT_TAIL) +/** + * Corrects organs in a carbon, removing ones it doesn't need and adding ones it does. + * + * Takes all organ slots, removes organs a species should not have, adds organs a species should have. + * can use replace_current to refresh all organs, creating an entirely new set. + * + * Arguments: + * * C - carbon, the owner of the species datum AKA whoever we're regenerating organs in + * * 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 + * * 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, 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, ORGAN_SLOT_TAIL = mutanttail) - var/should_have_brain = TRUE - var/should_have_heart = !(NOBLOOD in species_traits) - var/should_have_lungs = !(TRAIT_NOBREATH in inherent_traits) - var/should_have_appendix = !(TRAIT_NOHUNGER in inherent_traits) - var/should_have_eyes = TRUE - var/should_have_ears = TRUE - var/should_have_tongue = TRUE - var/should_have_liver = !(NOLIVER in species_traits) - var/should_have_stomach = !(NOSTOMACH in species_traits) - var/should_have_tail = mutanttail + 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, ORGAN_SLOT_TAIL)) - if(heart && (!should_have_heart || replace_current)) - heart.Remove(C,1) - QDEL_NULL(heart) - if(should_have_heart && !heart) - heart = new mutant_heart() - heart.Insert(C) + var/obj/item/organ/oldorgan = C.getorganslot(slot) //used in removing + var/obj/item/organ/neworgan = slot_mutantorgans[slot] //used in adding - if(lungs && (!should_have_lungs || replace_current)) - lungs.Remove(C,1) - QDEL_NULL(lungs) - if(should_have_lungs && !lungs) - if(mutantlungs) - lungs = new mutantlungs() - else - lungs = new() - lungs.Insert(C) + if(visual_only && !initial(neworgan.visual)) + continue - if(liver && (!should_have_liver || replace_current)) - liver.Remove(C,1) - QDEL_NULL(liver) - if(should_have_liver && !liver) - if(mutantliver) - liver = new mutantliver() - else - liver = new() - liver.Insert(C) + var/used_neworgan = FALSE + 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(stomach && (!should_have_stomach || replace_current)) - stomach.Remove(C,1) - QDEL_NULL(stomach) - if(should_have_stomach && !stomach) - if(mutantstomach) - stomach = new mutantstomach() - else - stomach = new() - stomach.Insert(C) + if(oldorgan && (!should_have || replace_current)) + if(slot == ORGAN_SLOT_BRAIN) + var/obj/item/organ/brain/brain = oldorgan + if(!brain.decoy_override)//"Just keep it if it's fake" - confucius, probably + brain.Remove(C,TRUE, TRUE) //brain argument used so it doesn't cause any... sudden death. + QDEL_NULL(brain) + oldorgan = null //now deleted + else + 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(appendix && (!should_have_appendix || replace_current)) - appendix.Remove(C,1) - QDEL_NULL(appendix) - if(should_have_appendix && !appendix) - appendix = new() - appendix.Insert(C) + if(oldorgan) + oldorgan.setOrganDamage(0) + else if(should_have) + if(slot == ORGAN_SLOT_TAIL) + // Special snowflake code to handle tail appearances + var/obj/item/organ/tail/new_tail = neworgan + if(iscatperson(C)) + new_tail.tail_type = C.dna.features["tail_human"] + if(ispolysmorph(C)) + new_tail.tail_type = C.dna.features["tail_polysmorph"] + if(islizard(C)) + var/obj/item/organ/tail/lizard/new_lizard_tail = neworgan + new_lizard_tail.tail_type = C.dna.features["tail_lizard"] + new_lizard_tail.spines = C.dna.features["spines"] - if(tail && (!should_have_tail || replace_current)) - tail.Remove(C,1) - QDEL_NULL(tail) - if(should_have_tail && !tail) - tail = new mutanttail() - if(iscatperson(C)) - tail.tail_type = C.dna.features["tail_human"] - if(ispolysmorph(C)) - tail.tail_type = C.dna.features["tail_polysmorph"] - if(islizard(C)) - var/obj/item/organ/tail/lizard/T = tail - T.tail_type = C.dna.features["tail_lizard"] - T.spines = C.dna.features["spines"] - tail.Insert(C) + used_neworgan = TRUE + neworgan.Insert(C, TRUE, FALSE) - if(C.get_bodypart(BODY_ZONE_HEAD)) - if(brain && (replace_current || !should_have_brain)) - if(!brain.decoy_override)//Just keep it if it's fake - brain.Remove(C,TRUE,TRUE) - QDEL_NULL(brain) - if(should_have_brain && !brain) - brain = new mutant_brain() - brain.Insert(C, TRUE, TRUE) - - if(eyes && (replace_current || !should_have_eyes)) - eyes.Remove(C,1) - QDEL_NULL(eyes) - if(should_have_eyes && !eyes) - eyes = new mutanteyes - eyes.Insert(C) - - if(ears && (replace_current || !should_have_ears)) - ears.Remove(C,1) - QDEL_NULL(ears) - if(should_have_ears && !ears) - ears = new mutantears - ears.Insert(C) - - if(tongue && (replace_current || !should_have_tongue)) - tongue.Remove(C,1) - QDEL_NULL(tongue) - if(should_have_tongue && !tongue) - tongue = new mutanttongue - tongue.Insert(C) + if(!used_neworgan) + qdel(neworgan) if(old_species) for(var/mutantorgan in old_species.mutant_organs) - var/obj/item/organ/I = C.getorgan(mutantorgan) - if(I) - I.Remove(C) - QDEL_NULL(I) - else - for(var/mutantorgan in mutant_organs) + // Snowflake check. If our species share this mutant organ, let's not remove it + // just yet as we'll be properly replacing it later. + if(mutantorgan in mutant_organs) + continue var/obj/item/organ/I = C.getorgan(mutantorgan) if(I) I.Remove(C) QDEL_NULL(I) - for(var/path in mutant_organs) - var/obj/item/organ/I = new path() - I.Insert(C) + 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 = 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. + //if(current_organ) + // current_organ.before_organ_replacement(replacement) + // organ.Insert will qdel any current organs in that slot, so we don't need to. + replacement.Insert(C, TRUE, FALSE) /datum/species/proc/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) // Drop the items the new species can't wear @@ -2157,7 +2120,8 @@ GLOBAL_LIST_EMPTY(mentor_races) /datum/species/proc/can_wag_tail(mob/living/carbon/human/H) if(H.IsParalyzed() || H.IsStun()) return FALSE - return (H.getorganslot(ORGAN_SLOT_TAIL)) + var/obj/item/organ/tail = H.getorganslot(ORGAN_SLOT_TAIL) + return tail.get_availability(H.dna.species) /datum/species/proc/is_wagging_tail(mob/living/carbon/human/H) return ("waggingtail_human" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts) @@ -2344,3 +2308,19 @@ GLOBAL_LIST_EMPTY(mentor_races) /datum/species/proc/force_drink_text(obj/O, mob/living/carbon/C, mob/user) . = TRUE C.visible_message(span_danger("[user] attempts to feed the contents of [O] to [C]."), span_userdanger("[user] attempts to feed the contents of [O] to [C].")) + +/datum/species/proc/get_types_to_preload() + var/list/to_store = list() + to_store += mutant_organs + //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 + to_store += mutanttail + //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/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm index a69c876ba799..213b3942ec19 100644 --- a/code/modules/mob/living/carbon/human/species_types/IPC.dm +++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm @@ -8,8 +8,8 @@ species_traits = list(NOTRANSSTING,NOEYESPRITES,NO_DNA_COPY,TRAIT_EASYDISMEMBER,ROBOTIC_LIMBS,NOZOMBIE,MUTCOLORS,NOHUSK,AGENDER,NOBLOOD) inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_COLDBLOODED,TRAIT_LIMBATTACHMENT,TRAIT_NOCRITDAMAGE,TRAIT_GENELESS,TRAIT_MEDICALIGNORE,TRAIT_NOCLONE,TRAIT_TOXIMMUNE,TRAIT_EASILY_WOUNDED,TRAIT_NODEFIB) inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) - mutant_brain = /obj/item/organ/brain/positron - mutant_heart = /obj/item/organ/heart/cybernetic/ipc + mutantbrain = /obj/item/organ/brain/positron + mutantheart = /obj/item/organ/heart/cybernetic/ipc mutanteyes = /obj/item/organ/eyes/robotic mutanttongue = /obj/item/organ/tongue/robot mutantliver = /obj/item/organ/liver/cybernetic/upgraded/ipc diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index c7700e292145..4bc1a41367db 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -6,7 +6,7 @@ inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH) default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None") use_skintones = TRUE - mutant_brain = /obj/item/organ/brain/dullahan + mutantbrain = /obj/item/organ/brain/dullahan mutanteyes = /obj/item/organ/eyes/dullahan mutanttongue = /obj/item/organ/tongue/dullahan mutantears = /obj/item/organ/ears/dullahan diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index e5be180c90fd..47de51ab9323 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -7,6 +7,7 @@ attack_sound = 'sound/weapons/slash.ogg' miss_sound = 'sound/weapons/slashmiss.ogg' + species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE,HAS_TAIL) mutant_bodyparts = list("ears", "tail_human") default_features = list("mcolor" = "FFF", "tail_human" = "Cat", "ears" = "Cat", "wings" = "None") rare_say_mod = list("meows"= 10) @@ -46,7 +47,7 @@ var/obj/item/organ/tail/cat/tail = new tail.Insert(H, drop_if_replaced = FALSE) else - mutanttail = null + mutanttail = initial(old_species.mutanttail) /datum/species/human/felinid/on_species_loss(mob/living/carbon/H, datum/species/new_species, pref_load) var/obj/item/organ/ears/cat/ears = H.getorgan(/obj/item/organ/ears/cat) diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 864a1152c080..2d6b23507ad5 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -4,7 +4,7 @@ id = "lizard" say_mod = "hisses" default_color = "00FF00" - species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAS_FLESH,HAS_BONE) + species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAS_FLESH,HAS_BONE,HAS_TAIL) inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE) mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs") mutanttongue = /obj/item/organ/tongue/lizard 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 6468bd60425f..f3efed0db1d6 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -27,7 +27,7 @@ smells_like = "dusty dryness" -/datum/species/moth/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE) +/datum/species/moth/regenerate_organs(mob/living/carbon/C, datum/species/old_species, replace_current = TRUE, visual_only = FALSE) . = ..() if(ishuman(C)) var/mob/living/carbon/human/H = C diff --git a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm index d311f8d0da9b..2ea0e1fc6620 100644 --- a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm +++ b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm @@ -16,7 +16,7 @@ damage_overlay_type = "polysmorph" deathsound = 'sound/voice/hiss6.ogg' screamsound = 'sound/voice/hiss5.ogg' - species_traits = list(NOEYESPRITES, FGENDER, MUTCOLORS, NOCOLORCHANGE, DIGITIGRADE, HAS_FLESH, HAS_BONE) + species_traits = list(NOEYESPRITES, FGENDER, MUTCOLORS, NOCOLORCHANGE, DIGITIGRADE, HAS_FLESH, HAS_BONE, HAS_TAIL) inherent_traits = list(TRAIT_ACIDBLOOD, TRAIT_SKINNY) inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) mutanteyes = /obj/item/organ/eyes/polysmorph diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 0dadcdd7e612..da8f8c3aac82 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -40,7 +40,7 @@ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) mutanteyes = /obj/item/organ/eyes/night_vision/nightmare mutant_organs = list(/obj/item/organ/heart/nightmare) - mutant_brain = /obj/item/organ/brain/nightmare + mutantbrain = /obj/item/organ/brain/nightmare var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." @@ -93,6 +93,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" var/respawn_progress = 0 var/obj/item/light_eater/blade diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 4d3dd70fb327..a16bad18939e 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -9,7 +9,7 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | ERT_SPAWN exotic_bloodtype = "U" use_skintones = TRUE - mutant_heart = /obj/item/organ/heart/vampire + mutantheart = /obj/item/organ/heart/vampire mutanttongue = /obj/item/organ/tongue/vampire limbs_id = "human" skinned_type = /obj/item/stack/sheet/animalhide/human diff --git a/code/modules/surgery/organs/appendix.dm b/code/modules/surgery/organs/appendix.dm index 353ef83ee234..dad5d62f72ca 100644 --- a/code/modules/surgery/organs/appendix.dm +++ b/code/modules/surgery/organs/appendix.dm @@ -1,6 +1,7 @@ /obj/item/organ/appendix name = "appendix" icon_state = "appendix" + visual = FALSE zone = BODY_ZONE_PRECISE_GROIN slot = ORGAN_SLOT_APPENDIX healing_factor = STANDARD_ORGAN_HEALING @@ -43,6 +44,9 @@ S.reagents.add_reagent(/datum/reagent/toxin/bad_food, 5) return S +/obj/item/organ/appendix/get_availability(datum/species/species) + return !(TRAIT_NOHUNGER in species.inherent_traits) + /obj/item/organ/appendix/cybernetic name = "cybernetic appendix" desc = "One of the most advanced cybernetic organs ever created." diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index e63e97953898..6f90af47cfce 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 44d90a0f50e8..07b838eea5b0 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 decay_factor = STANDARD_ORGAN_DECAY @@ -92,6 +93,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/H, special = 0, drop_if_replaced = TRUE) diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index 46e3feaf67a9..a6388afb6c05 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 @@ -37,7 +38,6 @@ old_eye_color = HMN.eye_color if(eye_color) HMN.eye_color = eye_color - HMN.regenerate_icons() else eye_color = HMN.eye_color if(HAS_TRAIT(HMN, TRAIT_NIGHT_VISION) && !lighting_alpha) @@ -52,10 +52,15 @@ if(ishuman(M) && eye_color) var/mob/living/carbon/human/HMN = M HMN.eye_color = old_eye_color - HMN.regenerate_icons() + HMN.update_body() M.update_tint() M.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() ..() var/mob/living/carbon/C = owner diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 5ae77faae3eb..699f3b64635c 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -4,6 +4,7 @@ name = "heart" desc = "I feel bad for the heartless bastard who lost this." icon_state = "heart" + visual = FALSE zone = BODY_ZONE_CHEST slot = ORGAN_SLOT_HEART healing_factor = STANDARD_ORGAN_HEALING @@ -107,6 +108,9 @@ owner.set_heartattack(TRUE) failed = TRUE +/obj/item/organ/heart/get_availability(datum/species/species) + return !(NOBLOOD in species.species_traits) + /obj/item/organ/heart/cursed name = "cursed heart" desc = "A heart that, when inserted, will force you to pump it manually." diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index 10b8f35b6cba..ba7b2245cb27 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 @@ -65,6 +66,9 @@ S.reagents.add_reagent(/datum/reagent/iron, 5) return S +/obj/item/organ/liver/get_availability(datum/species/species) + return !(NOLIVER in species.species_traits) + /obj/item/organ/liver/fly name = "insectoid liver" icon_state = "liver-x" //xenomorph liver? It's just a black liver so it fits. diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 8f78a9234b0a..d59ee3319dc1 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 @@ -481,6 +482,9 @@ S.reagents.add_reagent(/datum/reagent/medicine/salbutamol, 5) return S +/obj/item/organ/lungs/get_availability(datum/species/species) + return !(TRAIT_NOBREATH in species.inherent_traits) + /obj/item/organ/lungs/ipc name = "cooling radiator" desc = "A radiator in the shape of a lung used to exchange heat to cool down" diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm index 3f9385fc6951..27a42b7ed1b6 100644 --- a/code/modules/surgery/organs/organ_internal.dm +++ b/code/modules/surgery/organs/organ_internal.dm @@ -30,6 +30,9 @@ var/high_threshold_cleared var/low_threshold_cleared + ///Do we effect the appearance of our mob. Used to save time in preference code + var/visual = TRUE + /obj/item/organ/proc/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE,special_zone = null) if(!iscarbon(M) || owner == M) return @@ -175,6 +178,14 @@ START_PROCESSING(SSobj, src) return ..() +///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() STOP_PROCESSING(SSobj, src) if(owner) @@ -216,6 +227,20 @@ else organ_flags &= ~ORGAN_FAILING + +/** get_availability + * returns whether the species should innately have this organ. + * + * regenerate organs works with generic organs, so we need to get whether it can accept certain organs just by what this returns. + * This is set to return true or false, depending on if a species has a trait that would nulify the purpose of the organ. + * For example, lungs won't be given if you have NO_BREATH, stomachs check for NO_HUNGER, and livers check for NO_METABOLISM. + * If you want a carbon to have a trait that normally blocks an organ but still want the organ. Attach the trait to the organ using the organ_traits var + * Arguments: + * owner_species - species, needed to return whether the species has an organ specific trait + */ +/obj/item/organ/proc/get_availability(datum/species/owner_species) + return TRUE + //Looking for brains? //Try code/modules/mob/living/carbon/brain/brain_item.dm diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm index 9d439990a205..dd964516cd3f 100644 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -3,6 +3,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 @@ -86,6 +87,9 @@ SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust") ..() +/obj/item/organ/stomach/get_availability(datum/species/species) + return !(NOSTOMACH in species.species_traits) + /obj/item/organ/stomach/cybernetic name = "cybernetic stomach" desc = "A cybernetic metabolic furnace that can be connected to a digestive system in place of a stomach." diff --git a/code/modules/surgery/organs/tails.dm b/code/modules/surgery/organs/tails.dm index 74a6a821cb4d..47cc9d914222 100644 --- a/code/modules/surgery/organs/tails.dm +++ b/code/modules/surgery/organs/tails.dm @@ -4,15 +4,19 @@ name = "tail" desc = "A severed tail. What did you cut this off of?" icon_state = "severedtail" + visual = TRUE zone = BODY_ZONE_PRECISE_GROIN slot = ORGAN_SLOT_TAIL var/tail_type = "None" -/obj/item/organ/tail/Remove(mob/living/carbon/human/H, special = 0) +/obj/item/organ/tail/Remove(mob/living/carbon/human/H, special = 0) ..() if(H && H.dna && H.dna.species) H.dna.species.stop_wagging_tail(H) +/obj/item/organ/tail/get_availability(datum/species/species) + return (HAS_TAIL in species.species_traits) + /obj/item/organ/tail/cat name = "cat tail" desc = "A severed cat tail. Who's wagging now?" @@ -26,7 +30,7 @@ H.dna.features["tail_human"] = tail_type H.update_body() -/obj/item/organ/tail/cat/Remove(mob/living/carbon/human/H, special = 0) +/obj/item/organ/tail/cat/Remove(mob/living/carbon/human/H, special = 0) ..() if(istype(H)) H.dna.species.mutant_bodyparts -= "tail_human" diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm index feb5aec9304e..49b7693200d3 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 = list("licked", "slobbered", "slapped", "frenched", "tongued") diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index 11d760b8f7d3..f9746d572723 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -6,6 +6,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 @@ -31,6 +32,7 @@ zone = BODY_ZONE_HEAD slot = ORGAN_SLOT_ADAMANTINE_RESONATOR icon_state = "adamantine_resonator" + visual = FALSE /obj/item/organ/vocal_cords/adamantine name = "adamantine vocal cords" diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index ca3430c1bf1e..e14af7c957c5 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/yogstation.dme b/yogstation.dme index 56896eeba38e..fb1f3ef4738d 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -331,6 +331,7 @@ #include "code\controllers\subsystem\traumas.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\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\fields.dm"