Speeds up the preference menu, implement object pooling (#17133)

* Speeds up the preference menu

https://github.com/tgstation/tgstation/pull/63225

* Fix compile
This commit is contained in:
Ling
2023-01-02 01:22:11 +01:00
committed by GitHub
parent 38510590be
commit 1e546cd676
50 changed files with 962 additions and 283 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -0,0 +1,358 @@
/// This subsystem strives to make loading large amounts of select objects as smooth at execution as possible
/// It preloads a set of types to store, and caches them until requested
/// Doesn't catch everything mind, this is intentional. There's many types that expect to either
/// A: Not sit in a list for 2 hours, or B: have extra context passed into them, or for their parent to be their location
/// You should absolutely not spam this system, it will break things in new and wonderful ways
/// S close enough for government work though.
/// Fuck you goonstation
SUBSYSTEM_DEF(wardrobe)
name = "Wardrobe"
wait = 10 // This is more like a queue then anything else
flags = SS_BACKGROUND
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT // We're going to fill up our cache while players sit in the lobby
/// How much to cache outfit items
/// Multiplier, 2 would mean cache enough items to stock 1 of each preloaded order twice, etc
var/cache_intensity = 2
/// How many more then the template of a type are we allowed to have before we delete applicants?
var/overflow_lienency = 2
/// List of type -> list(insertion callback, removal callback) callbacks for insertion/removal to use.
/// Set in setup_callbacks, used in canonization.
var/list/initial_callbacks = list()
/// Canonical list of types required to fill all preloaded stocks once.
/// Type -> list(count, last inspection timestamp, call on insert, call on removal)
var/list/canon_minimum = list()
/// List of types to load. Type -> count //(I'd do a list of lists but this needs to be refillable)
var/list/order_list = list()
/// List of lists. Contains our preloaded atoms. Type -> list(last inspect time, list(instances))
var/list/preloaded_stock = list()
/// The last time we inspected our stock
var/last_inspect_time = 0
/// How often to inspect our stock, in deciseconds
var/inspect_delay = 30 SECONDS
/// What we're currently doing
var/current_task = SSWARDROBE_STOCK
/// How many times we've had to generate a stock item on request
var/stock_miss = 0
/// How many times we've successfully returned a cached item
var/stock_hit = 0
/// How many items would we make just by loading the master list once?
var/one_go_master = 0
/datum/controller/subsystem/wardrobe/Initialize(start_timeofday)
. = ..()
setup_callbacks()
load_outfits()
load_species()
load_pda_nicknacks()
load_storage_contents()
hard_refresh_queue()
stock_hit = 0
stock_miss = 0
/// Resets the load queue to the master template, accounting for the existing stock
/datum/controller/subsystem/wardrobe/proc/hard_refresh_queue()
for(var/datum/type_to_queue as anything in canon_minimum)
var/list/master_info = canon_minimum[type_to_queue]
var/amount_to_load = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/list/stock_info = preloaded_stock[type_to_queue]
if(stock_info) // If we already have stuff, reduce the amount we load
amount_to_load -= length(stock_info[WARDROBE_STOCK_CONTENTS])
set_queue_item(type_to_queue, amount_to_load)
/datum/controller/subsystem/wardrobe/stat_entry(msg)
var/total_provided = max(stock_hit + stock_miss, 1)
var/current_max_store = (one_go_master * cache_intensity) + (overflow_lienency * length(canon_minimum))
msg += " P:[length(canon_minimum)] Q:[length(order_list)] S:[length(preloaded_stock)] I:[cache_intensity] O:[overflow_lienency]"
msg += " H:[stock_hit] M:[stock_miss] T:[total_provided] H/T:[PERCENT(stock_hit / total_provided)]% M/T:[PERCENT(stock_miss / total_provided)]%"
msg += " MAX:[current_max_store]"
msg += " ID:[inspect_delay] NI:[last_inspect_time + inspect_delay]"
return ..()
/datum/controller/subsystem/wardrobe/fire(resumed=FALSE)
if(current_task != SSWARDROBE_INSPECT && world.time - last_inspect_time >= inspect_delay)
current_task = SSWARDROBE_INSPECT
switch(current_task)
if(SSWARDROBE_STOCK)
stock_wardrobe()
if(SSWARDROBE_INSPECT)
run_inspection()
if(state != SS_RUNNING)
return
current_task = SSWARDROBE_STOCK
last_inspect_time = world.time
/// Turns the order list into actual loaded items, this is where most work is done
/datum/controller/subsystem/wardrobe/proc/stock_wardrobe()
for(var/atom/movable/type_to_stock as anything in order_list)
var/amount_to_stock = order_list[type_to_stock]
for(var/i in 1 to amount_to_stock)
if(MC_TICK_CHECK)
order_list[type_to_stock] = (amount_to_stock - (i - 1)) // Account for types we've already created
return
var/atom/movable/new_member = new type_to_stock()
stash_object(new_member)
order_list -= type_to_stock
if(MC_TICK_CHECK)
return
/// Once every medium while, go through the current stock and make sure we don't have too much of one thing
/// Or that we're not too low on some other stock
/// This exists as a failsafe, so the wardrobe doesn't just end up generating too many items or accidentially running out somehow
/datum/controller/subsystem/wardrobe/proc/run_inspection()
for(var/datum/loaded_type as anything in canon_minimum)
var/list/master_info = canon_minimum[loaded_type]
var/last_looked_at = master_info[WARDROBE_CACHE_LAST_INSPECT]
if(last_looked_at == last_inspect_time)
continue
var/list/stock_info = preloaded_stock[loaded_type]
var/amount_held = 0
if(stock_info)
var/list/held_objects = stock_info[WARDROBE_STOCK_CONTENTS]
amount_held = length(held_objects)
var/target_stock = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/target_delta = amount_held - target_stock
// If we've got too much
if(target_delta > overflow_lienency)
unload_stock(loaded_type, target_delta - overflow_lienency)
if(state != SS_RUNNING)
return
// If we have more then we target, just don't you feel me?
target_delta = min(target_delta, 0) //I only want negative numbers to matter here
// If we don't have enough, queue enough to make up the remainder
// If we have too much in the queue, cull to 0. We do this so time isn't wasted creating and destroying entries
set_queue_item(loaded_type, abs(target_delta))
master_info[WARDROBE_CACHE_LAST_INSPECT] = last_inspect_time
if(MC_TICK_CHECK)
return
/// Takes a path to get the callback owner for
/// Returns the deepest path in our callback store that matches the input
/// The hope is this will prevent dumb conflicts, since the furthest down is always going to be the most relevant
/datum/controller/subsystem/wardrobe/proc/get_callback_type(datum/to_check)
var/longest_path
var/longest_path_length = 0
for(var/datum/path as anything in initial_callbacks)
if(ispath(to_check, path))
var/stringpath = "[path]"
var/pathlength = length(splittext(stringpath, "/")) // We get the "depth" of the path
if(pathlength < longest_path_length)
continue
longest_path = path
longest_path_length = pathlength
return longest_path
/**
* Canonizes the type, which means it's now managed by the subsystem, and will be created deleted and passed out to comsumers
*
* Arguments:
* * type to stock - What type exactly do you want us to remember?
*
*/
/datum/controller/subsystem/wardrobe/proc/canonize_type(type_to_stock)
if(!type_to_stock)
return
if(!ispath(type_to_stock))
stack_trace("Non path [type_to_stock] attempted to canonize itself. Something's fucky")
var/list/master_info = canon_minimum[type_to_stock]
if(!master_info)
master_info = new /list(WARDROBE_CACHE_CALL_REMOVAL)
master_info[WARDROBE_CACHE_COUNT] = 0
//Decide on the appropriate callbacks to use
var/callback_type = get_callback_type(type_to_stock)
var/list/callback_info = initial_callbacks[callback_type]
if(callback_info)
master_info[WARDROBE_CACHE_CALL_INSERT] = callback_info[WARDROBE_CALLBACK_INSERT]
master_info[WARDROBE_CACHE_CALL_REMOVAL] = callback_info[WARDROBE_CALLBACK_REMOVE]
canon_minimum[type_to_stock] = master_info
master_info[WARDROBE_CACHE_COUNT] += 1
one_go_master++
/datum/controller/subsystem/wardrobe/proc/add_queue_item(queued_type, amount)
var/amount_held = order_list[queued_type] || 0
set_queue_item(queued_type, amount_held + amount)
/datum/controller/subsystem/wardrobe/proc/remove_queue_item(queued_type, amount)
var/amount_held = order_list[queued_type]
if(!amount_held)
return
set_queue_item(queued_type, amount_held - amount)
/datum/controller/subsystem/wardrobe/proc/set_queue_item(queued_type, amount)
var/list/master_info = canon_minimum[queued_type]
if(!master_info)
stack_trace("We just tried to queue a type \[[queued_type]\] that's not stored in the master canon")
return
var/target_amount = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/list/stock_info = preloaded_stock[queued_type]
if(stock_info)
target_amount -= length(stock_info[WARDROBE_STOCK_CONTENTS])
amount = min(amount, target_amount) // If we're trying to set more then we need, don't!
if(amount <= 0) // If we already have all we need, end it
order_list -= queued_type
return
order_list[queued_type] = amount
/// Take an existing object, and insert it into our storage
/// If we can't or won't take it, it's deleted. You do not own this object after passing it in
/datum/controller/subsystem/wardrobe/proc/stash_object(atom/movable/object)
var/object_type = object.type
var/list/master_info = canon_minimum[object_type]
// I will not permit objects you didn't reserve ahead of time
if(!master_info)
qdel(object)
return
var/stock_target = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
var/amount_held = 0
var/list/stock_info = preloaded_stock[object_type]
if(stock_info)
amount_held = length(stock_info[WARDROBE_STOCK_CONTENTS])
// Doublely so for things we already have too much of
if(amount_held - stock_target >= overflow_lienency)
qdel(object)
return
// Fuck off
if(QDELETED(object))
stack_trace("We tried to stash a qdeleted object, what did you do")
return
if(!stock_info)
stock_info = new /list(WARDROBE_STOCK_CALL_REMOVAL)
stock_info[WARDROBE_STOCK_CONTENTS] = list()
stock_info[WARDROBE_STOCK_CALL_INSERT] = master_info[WARDROBE_CACHE_CALL_INSERT]
stock_info[WARDROBE_STOCK_CALL_REMOVAL] = master_info[WARDROBE_CACHE_CALL_REMOVAL]
preloaded_stock[object_type] = stock_info
var/datum/callback/do_on_insert = stock_info[WARDROBE_STOCK_CALL_INSERT]
if(do_on_insert)
do_on_insert.object = object
do_on_insert.Invoke()
do_on_insert.object = null
object.moveToNullspace()
stock_info[WARDROBE_STOCK_CONTENTS] += object
/datum/controller/subsystem/wardrobe/proc/provide_type(datum/requested_type, atom/movable/location)
var/atom/movable/requested_object
var/list/stock_info = preloaded_stock[requested_type]
if(!stock_info)
stock_miss++
requested_object = new requested_type(location)
return requested_object
var/list/contents = stock_info[WARDROBE_STOCK_CONTENTS]
var/contents_length = length(contents)
requested_object = contents[contents_length]
contents.len--
if(QDELETED(requested_object))
stack_trace("We somehow ended up with a qdeleted or null object in SSwardrobe's stock. Something's weird, likely to do with reinsertion. Typepath of [requested_type]")
stock_miss++
requested_object = new requested_type(location)
return requested_object
if(location)
requested_object.forceMove(location)
var/datum/callback/do_on_removal = stock_info[WARDROBE_STOCK_CALL_REMOVAL]
if(do_on_removal)
do_on_removal.object = requested_object
do_on_removal.Invoke()
do_on_removal.object = null
stock_hit++
add_queue_item(requested_type, 1) // Requeue the item, under the assumption we'll never see it again
if(!(contents_length - 1))
preloaded_stock -= requested_type
return requested_object
/// Unloads an amount of some type we have in stock
/// Private function, for internal use only
/datum/controller/subsystem/wardrobe/proc/unload_stock(datum/unload_type, amount, force = FALSE)
var/list/stock_info = preloaded_stock[unload_type]
if(!stock_info)
return
var/list/unload_from = stock_info[WARDROBE_STOCK_CONTENTS]
for(var/i in 1 to min(amount, length(unload_from)))
var/datum/nuke = unload_from[unload_from.len]
unload_from.len--
qdel(nuke)
if(!force && MC_TICK_CHECK && length(unload_from))
return
if(!length(stock_info[WARDROBE_STOCK_CONTENTS]))
preloaded_stock -= unload_type
/// Sets up insertion and removal callbacks by typepath
/// We will always use the deepest path. So /obj/item/blade/knife superceeds the entries of /obj/item and /obj/item/blade
/// Mind this
/datum/controller/subsystem/wardrobe/proc/setup_callbacks()
var/list/play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Turns out there's a global list of pdas. Let's work around that yeah?
play_with[WARDROBE_CALLBACK_INSERT] = CALLBACK(null, /obj/item/pda/proc/display_pda)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/pda/proc/cloak_pda)
initial_callbacks[/obj/item/pda] = play_with
play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Don't want organs rotting on the job
play_with[WARDROBE_CALLBACK_INSERT] = CALLBACK(null, /obj/item/organ/proc/enter_wardrobe)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/organ/proc/exit_wardrobe)
initial_callbacks[/obj/item/organ] = play_with
play_with = new /list(WARDROBE_CALLBACK_REMOVE)
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, /obj/item/storage/box/survival/proc/wardrobe_removal)
initial_callbacks[/obj/item/storage/box/survival] = play_with
/datum/controller/subsystem/wardrobe/proc/load_outfits()
for(var/datum/outfit/to_stock as anything in subtypesof(/datum/outfit))
if(!initial(to_stock.preload)) // Clearly not interested
continue
var/datum/outfit/hang_up = new to_stock()
for(var/datum/outfit_item as anything in hang_up.get_types_to_preload())
canonize_type(outfit_item)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_species()
for(var/datum/species/to_record as anything in subtypesof(/datum/species))
if(!initial(to_record.preload))
continue
var/datum/species/fossil_record = new to_record()
for(var/obj/item/species_request as anything in fossil_record.get_types_to_preload())
for(var/i in 1 to 5) // Store 5 of each species, since that seems on par with 1 of each outfit
canonize_type(species_request)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_pda_nicknacks()
for(var/obj/item/pda/pager as anything in typesof(/obj/item/pda))
var/obj/item/pda/flip_phone = new pager()
for(var/datum/outfit_item_type as anything in flip_phone.get_types_to_preload())
canonize_type(outfit_item_type)
qdel(flip_phone)
CHECK_TICK
/datum/controller/subsystem/wardrobe/proc/load_storage_contents()
for(var/obj/item/storage/crate as anything in subtypesof(/obj/item/storage))
if(!initial(crate.preload))
continue
var/obj/item/pda/another_crate = new crate()
//Unlike other uses, I really don't want people being lazy with this one.
var/list/somehow_more_boxes = another_crate.get_types_to_preload()
if(!length(somehow_more_boxes))
stack_trace("You appear to have set preload to true on [crate] without defining get_types_to_preload. Please be more strict about your scope, this stuff is spooky")
for(var/datum/a_really_small_box as anything in somehow_more_boxes)
canonize_type(a_really_small_box)
qdel(another_crate)

View File

@@ -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()

View File

@@ -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()

View File

@@ -2,6 +2,7 @@
name = "body egg"
desc = "All slimy and yuck."
icon_state = "innards"
visual = TRUE
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_PARASITE_EGG

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,5 +1,6 @@
/obj/item/organ/alien
icon_state = "xgibmid2"
visual = FALSE
var/list/alien_powers = list()
/obj/item/organ/alien/Initialize()

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <span class='danger'>Nightmare</span>. The ability <span class='warning'>shadow walk</span> allows unlimited, unrestricted movement in the dark while activated. \
Your <span class='warning'>light eater</span> 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

View File

@@ -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

View File

@@ -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."

View File

@@ -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"

View File

@@ -4,6 +4,7 @@
desc = "There are three parts to the ear. Inner, middle and outer. Only one of these parts should be normally visible."
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_EARS
visual = FALSE
gender = PLURAL
healing_factor = STANDARD_ORGAN_HEALING
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)

View File

@@ -2,6 +2,7 @@
name = BODY_ZONE_PRECISE_EYES
icon_state = "eyeballs"
desc = "I see you!"
visual = TRUE
zone = BODY_ZONE_PRECISE_EYES
slot = ORGAN_SLOT_EYES
gender = PLURAL
@@ -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

View File

@@ -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."

View File

@@ -4,6 +4,7 @@
/obj/item/organ/liver
name = "liver"
icon_state = "liver"
visual = FALSE
w_class = WEIGHT_CLASS_SMALL
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_LIVER
@@ -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.

View File

@@ -3,6 +3,7 @@
var/operated = FALSE //whether we can still have our damages fixed through surgery
name = "lungs"
icon_state = "lungs"
visual = FALSE
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_LUNGS
gender = PLURAL
@@ -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"

View File

@@ -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

View File

@@ -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."

View File

@@ -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"

View File

@@ -2,6 +2,7 @@
name = "tongue"
desc = "A fleshy muscle mostly used for lying."
icon_state = "tonguenormal"
visual = FALSE
zone = BODY_ZONE_PRECISE_MOUTH
slot = ORGAN_SLOT_TONGUE
attack_verb = list("licked", "slobbered", "slapped", "frenched", "tongued")

View File

@@ -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"

View File

@@ -4,6 +4,7 @@
zone = BODY_ZONE_HEAD
slot = ORGAN_SLOT_ZOMBIE
icon_state = "blacktumor"
visual = FALSE
var/causes_damage = TRUE
var/datum/species/old_species = /datum/species/human
var/living_transformation_time = 30

View File

@@ -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"