From e5f9765d2ac67f54e5d110c728961750c4135dde Mon Sep 17 00:00:00 2001 From: Poojawa Date: Mon, 23 Apr 2018 00:02:42 -0500 Subject: [PATCH] datums/controller stuff --- .../controllers/configuration/config_entry.dm | 6 +- code/controllers/subsystem/air.dm | 2 +- code/controllers/subsystem/mapping.dm | 8 +- code/datums/action.dm | 27 +- code/datums/brain_damage/mild.dm | 12 +- code/datums/components/rotation.dm | 14 +- .../components/storage/concrete/_concrete.dm | 185 +++++ .../storage/concrete/bag_of_holding.dm | 22 + .../components/storage/concrete/bluespace.dm | 21 + .../components/storage/concrete/implant.dm | 17 + .../components/storage/concrete/pockets.dm | 65 ++ .../components/storage/concrete/rped.dm | 31 + .../components/storage/concrete/special.dm | 5 + .../components/storage/concrete/stack.dm | 67 ++ code/datums/components/storage/storage.dm | 730 ++++++++++++++++++ code/datums/diseases/_MobProcs.dm | 16 +- code/datums/diseases/_disease.dm | 4 +- .../diseases/advance/symptoms/species.dm | 4 +- .../diseases/advance/symptoms/weakness.dm | 44 -- code/datums/diseases/beesease.dm | 2 +- code/datums/diseases/magnitis.dm | 2 +- code/datums/diseases/parrotpossession.dm | 2 +- code/datums/diseases/transformation.dm | 4 +- code/datums/mood_events/mood_event.dm | 1 + code/datums/mutations.dm | 8 +- code/datums/numbered_display.dm | 10 + code/datums/shuttles.dm | 52 ++ code/datums/traits/negative.dm | 4 +- tgstation.dme | 9 + 29 files changed, 1283 insertions(+), 91 deletions(-) create mode 100644 code/datums/components/storage/concrete/_concrete.dm create mode 100644 code/datums/components/storage/concrete/bag_of_holding.dm create mode 100644 code/datums/components/storage/concrete/bluespace.dm create mode 100644 code/datums/components/storage/concrete/implant.dm create mode 100644 code/datums/components/storage/concrete/pockets.dm create mode 100644 code/datums/components/storage/concrete/rped.dm create mode 100644 code/datums/components/storage/concrete/special.dm create mode 100644 code/datums/components/storage/concrete/stack.dm create mode 100644 code/datums/components/storage/storage.dm delete mode 100644 code/datums/diseases/advance/symptoms/weakness.dm create mode 100644 code/datums/numbered_display.dm diff --git a/code/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm index a3923d50fc..24bf518a03 100644 --- a/code/controllers/configuration/config_entry.dm +++ b/code/controllers/configuration/config_entry.dm @@ -1,5 +1,3 @@ -#undef CURRENT_RESIDENT_FILE - #define LIST_MODE_NUM 0 #define LIST_MODE_TEXT 1 #define LIST_MODE_FLAG 2 @@ -8,7 +6,7 @@ var/name //read-only, this is determined by the last portion of the derived entry type var/config_entry_value var/default //read-only, just set value directly - + var/resident_file //the file which this was loaded from, if any var/modified = FALSE //set to TRUE if the default has been overridden by a config entry @@ -19,7 +17,7 @@ /datum/config_entry/New() if(type == abstract_type) - CRASH("Abstract config entry [type] instatiated!") + CRASH("Abstract config entry [type] instatiated!") name = lowertext(type2top(type)) if(islist(config_entry_value)) var/list/L = config_entry_value diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 240be1f212..9f366d5c37 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -393,5 +393,5 @@ SUBSYSTEM_DEF(air) #undef SSAIR_ACTIVETURFS #undef SSAIR_EXCITEDGROUPS #undef SSAIR_HIGHPRESSURE -#undef SSAIR_HOTSPOT +#undef SSAIR_HOTSPOTS #undef SSAIR_SUPERCONDUCTIVITY diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index ec3783c9c0..82a59dc857 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -367,14 +367,14 @@ GLOBAL_LIST_EMPTY(the_station_areas) away_level = template.load_new_z() else return - + message_admins("Admin [key_name_admin(usr)] has loaded [away_name] away mission.") log_admin("Admin [key_name(usr)] has loaded [away_name] away mission.") if(!away_level) message_admins("Loading [away_name] failed!") return - - + + if(GLOB.the_gateway) //Link any found away gate with station gate var/obj/machinery/gateway/centeraway/new_gate @@ -384,4 +384,4 @@ GLOBAL_LIST_EMPTY(the_station_areas) break //Link station gate with away gate and remove wait time. GLOB.the_gateway.awaygate = new_gate - GLOB.the_gateway.wait = world.time + GLOB.the_gateway.wait = world.time \ No newline at end of file diff --git a/code/datums/action.dm b/code/datums/action.dm index e230f49089..c5563e9a34 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -21,7 +21,7 @@ var/mob/owner /datum/action/New(Target) - target = Target + link_to(Target) button = new button.linked_action = src button.name = name @@ -29,6 +29,9 @@ if(desc) button.desc = desc +/datum/action/proc/link_to(Target) + target = Target + /datum/action/Destroy() if(owner) Remove(owner) @@ -689,3 +692,25 @@ else owner.remove_alt_appearance("smallsprite") small = FALSE + +/datum/action/item_action/storage_gather_mode + name = "Switch gathering mode" + desc = "Switches the gathering mode of a storage object." + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "storage_gather_switch" + +/datum/action/item_action/storage_gather_mode/ApplyIcon(obj/screen/movable/action_button/current_button) + . = ..() + var/old_layer = target.layer + var/old_plane = target.plane + target.layer = FLOAT_LAYER //AAAH + target.plane = FLOAT_PLANE //^ what that guy said + current_button.cut_overlays() + current_button.add_overlay(target) + target.layer = old_layer + target.plane = old_plane + current_button.appearance_cache = target.appearance + +/datum/action/item_action/storage_gather_mode/Trigger() + GET_COMPONENT_FROM(STR, /datum/component/storage, target) + STR.gather_mode_switch(owner) diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm index 375898f1fb..5c23fb4c8b 100644 --- a/code/datums/brain_damage/mild.dm +++ b/code/datums/brain_damage/mild.dm @@ -50,7 +50,7 @@ owner.derpspeech = min(owner.derpspeech + 5, 25) if(prob(3)) owner.emote("drool") - else if(owner && owner.stat == CONSCIOUS && prob(3)) + else if(owner.stat == CONSCIOUS && prob(3)) owner.say(pick_list_replacements(BRAIN_DAMAGE_FILE, "brain_damage")) ..() @@ -138,13 +138,13 @@ /datum/brain_trauma/mild/muscle_weakness/on_life() var/fall_chance = 1 - if(owner && owner.m_intent == MOVE_INTENT_RUN) + if(owner.m_intent == MOVE_INTENT_RUN) fall_chance += 2 if(prob(fall_chance) && !owner.lying && !owner.buckled) to_chat(owner, "Your leg gives out!") owner.Knockdown(35) - else if(owner && owner.get_active_held_item()) + else if(owner.get_active_held_item()) var/drop_chance = 1 var/obj/item/I = owner.get_active_held_item() drop_chance += I.w_class @@ -167,11 +167,11 @@ if(prob(7)) switch(rand(1,5)) if(1) - if(owner && owner.canmove && !isspaceturf(owner.loc)) + if(owner.canmove && !isspaceturf(owner.loc)) to_chat(owner, "Your leg spasms!") step(owner, pick(GLOB.cardinals)) if(2) - if(owner && owner.incapacitated()) + if(owner.incapacitated()) return var/obj/item/I = owner.get_active_held_item() if(I) @@ -203,7 +203,7 @@ owner.ClickOn(owner) owner.a_intent = prev_intent if(5) - if(owner && owner.incapacitated()) + if(owner.incapacitated()) return var/obj/item/I = owner.get_active_held_item() var/list/turf/targets = list() diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm index c7c6b40754..eca12454fd 100644 --- a/code/datums/components/rotation.dm +++ b/code/datums/components/rotation.dm @@ -1,15 +1,15 @@ -#define ROTATION_ALTCLICK 1 -#define ROTATION_WRENCH 2 -#define ROTATION_VERBS 4 -#define ROTATION_COUNTERCLOCKWISE 8 -#define ROTATION_CLOCKWISE 16 -#define ROTATION_FLIP 32 +#define ROTATION_ALTCLICK (1<<0) +#define ROTATION_WRENCH (1<<1) +#define ROTATION_VERBS (1<<2) +#define ROTATION_COUNTERCLOCKWISE (1<<3) +#define ROTATION_CLOCKWISE (1<<4) +#define ROTATION_FLIP (1<<5) /datum/component/simple_rotation var/datum/callback/can_user_rotate //Checks if user can rotate var/datum/callback/can_be_rotated //Check if object can be rotated at all var/datum/callback/after_rotation //Additional stuff to do after rotation - + var/rotation_flags = NONE var/default_rotation_direction = ROTATION_CLOCKWISE diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm new file mode 100644 index 0000000000..54b3fa031e --- /dev/null +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -0,0 +1,185 @@ + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage/concrete + var/drop_all_on_deconstruct = TRUE + var/drop_all_on_destroy = FALSE + var/transfer_contents_on_component_transfer = FALSE + var/list/datum/component/storage/slaves = list() + +/datum/component/storage/concrete/Initialize() + . = ..() + RegisterSignal(COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) + RegisterSignal(COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) + +/datum/component/storage/concrete/Destroy() + var/atom/real_location = real_location() + for(var/atom/_A in real_location) + _A.mouse_opacity = initial(_A.mouse_opacity) + if(drop_all_on_destroy) + do_quick_empty() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.change_master(null) + return ..() + +/datum/component/storage/concrete/master() + return src + +/datum/component/storage/concrete/real_location() + return parent + +/datum/component/storage/concrete/OnTransfer(datum/new_parent) + if(!isatom(new_parent)) + return COMPONENT_INCOMPATIBLE + var/list/mob/_is_using + if(is_using) + _is_using = is_using.Copy() + close_all() + if(transfer_contents_on_component_transfer) + var/atom/old = parent + for(var/i in old) + var/atom/movable/AM = i + AM.forceMove(new_parent) + if(_is_using) + for(var/i in _is_using) + var/mob/M = i + show_to(M) + +/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) + . = TRUE + var/atom/real_location = real_location() + if(I.loc != real_location) + I.forceMove(real_location) + refresh_mob_views() + +/datum/component/storage/concrete/refresh_mob_views() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.refresh_mob_views() + +/datum/component/storage/concrete/emp_act(severity) + var/atom/real_location = real_location() + for(var/i in real_location) + var/atom/A = i + A.emp_act(severity) + +/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) + if(S == src) + return FALSE + slaves += S + return TRUE + +/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) + slaves -= S + return FALSE + +/datum/component/storage/concrete/proc/on_contents_del(atom/A) + var/atom/real_location = parent + if(A in real_location) + usr = null + remove_from_storage(A, null) + +/datum/component/storage/concrete/proc/on_deconstruct(disassembled) + if(drop_all_on_deconstruct) + do_quick_empty() + +/datum/component/storage/concrete/can_see_contents() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + . |= slave.can_see_contents() + +//Resets screen loc and other vars of something being removed from storage. +/datum/component/storage/concrete/_removal_reset(atom/movable/thing) + thing.layer = initial(thing.layer) + thing.plane = initial(thing.plane) + thing.mouse_opacity = initial(thing.mouse_opacity) + if(thing.maptext) + thing.maptext = "" + +/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) + //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped + //or moving objects, things that should never happen + var/atom/parent = src.parent + var/list/seeing_mobs = can_see_contents() + for(var/mob/M in seeing_mobs) + M.client.screen -= AM + if(ismob(parent.loc) && isitem(AM)) + var/obj/item/I = AM + var/mob/M = parent.loc + I.dropped(M) + if(new_location) + //Reset the items values + _removal_reset(AM) + AM.forceMove(new_location) + //We don't want to call this if the item is being destroyed + AM.on_exit_storage(src) + else + //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) + AM.moveToNullspace() + refresh_mob_views() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + return TRUE + +/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) + return TRUE + +/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) + . = handle_item_insertion(I, prevent_warning, M, slave) + if(. && !prevent_warning) + slave.mob_item_insertion_feedback(usr, M, I) + +/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum + var/datum/component/storage/concrete/master = master() + var/atom/parent = src.parent + var/moved = FALSE + if(!istype(I)) + return FALSE + if(M) + if(!M.temporarilyRemoveItemFromInventory(I)) + return FALSE + else + moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. + if(I.pulledby) + I.pulledby.stop_pulling() + if(silent) + prevent_warning = TRUE + if(!_insert_physical_item(I)) + if(moved) + if(M) + if(!M.put_in_active_hand(I)) + I.forceMove(parent.drop_location()) + else + I.forceMove(parent.drop_location()) + return FALSE + I.on_enter_storage(master) + refresh_mob_views() + I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt + if(M) + if(M.client && M.active_storage != src) + M.client.screen -= I + if(M.observers && M.observers.len) + for(var/i in M.observers) + var/mob/dead/observe = i + if(observe.client && observe.active_storage != src) + observe.client.screen -= I + if(!remote) + parent.add_fingerprint(M) + if(!prevent_warning) + mob_item_insertion_feedback(usr, M, I) + update_icon() + return TRUE + +/datum/component/storage/concrete/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.update_icon() diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm new file mode 100644 index 0000000000..ba458d2f76 --- /dev/null +++ b/code/datums/components/storage/concrete/bag_of_holding.dm @@ -0,0 +1,22 @@ +/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user) + var/atom/A = parent + if((istype(W, /obj/item/storage/backpack/holding) || count_by_type(W.GetAllContents(), /obj/item/storage/backpack/holding))) + var/turf/loccheck = get_turf(A) + if(is_reebe(loccheck.z)) + user.visible_message("An unseen force knocks [user] to the ground!", "\"I think not!\"") + user.Knockdown(60) + return + var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort") + if(safety == "Abort" || !in_range(A, user) || !A || !W || user.incapacitated()) + return + A.investigate_log("has become a singularity. Caused by [user.key]", INVESTIGATE_SINGULO) + to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") + qdel(W) + var/obj/singularity/singulo = new /obj/singularity (get_turf(A)) + singulo.energy = 300 //should make it a bit bigger~ + message_admins("[key_name_admin(user)] detonated a bag of holding") + log_game("[key_name(user)] detonated a bag of holding") + qdel(A) + singulo.process() + return + . = ..() diff --git a/code/datums/components/storage/concrete/bluespace.dm b/code/datums/components/storage/concrete/bluespace.dm new file mode 100644 index 0000000000..4763783837 --- /dev/null +++ b/code/datums/components/storage/concrete/bluespace.dm @@ -0,0 +1,21 @@ +/datum/component/storage/concrete/bluespace + var/dumping_range = 8 + var/dumping_sound = 'sound/items/pshoom.ogg' + var/alt_sound = 'sound/items/pshoom_2.ogg' + +/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) + var/atom/A = parent + if(A.Adjacent(M)) + var/atom/dumping_location = dest.get_dumping_location() + if(get_dist(M, dumping_location) < dumping_range) + if(dumping_location.storage_contents_dump_act(src, M)) + if(alt_sound && prob(1)) + playsound(src, alt_sound, 40, 1) + else + playsound(src, dumping_sound, 40, 1) + M.Beam(dumping_location, icon_state="rped_upgrade", time=5) + return TRUE + to_chat(M, "The [A.name] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) + return FALSE + diff --git a/code/datums/components/storage/concrete/implant.dm b/code/datums/components/storage/concrete/implant.dm new file mode 100644 index 0000000000..d3f3d6b586 --- /dev/null +++ b/code/datums/components/storage/concrete/implant.dm @@ -0,0 +1,17 @@ +/datum/component/storage/concrete/implant + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 6 + max_items = 2 + drop_all_on_destroy = TRUE + drop_all_on_deconstruct = TRUE + silent = TRUE + +/datum/component/storage/concrete/implant/Initialize() + . = ..() + cant_hold = typecacheof(list(/obj/item/disk/nuclear)) + +/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) + if(!istype(I)) + return ..() + max_combined_w_class += I.max_combined_w_class + max_items += I.max_items diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm new file mode 100644 index 0000000000..124102a6bd --- /dev/null +++ b/code/datums/components/storage/concrete/pockets.dm @@ -0,0 +1,65 @@ +/datum/component/storage/concrete/pockets + max_items = 2 + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 50 + rustle_sound = FALSE + +/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) + . = ..() + if(. && silent && !prevent_warning) + if(quickdraw) + to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") + else + to_chat(user, "You discreetly slip [I] into [parent].") + +/datum/component/storage/concrete/pockets + max_w_class = WEIGHT_CLASS_NORMAL + +/datum/component/storage/concrete/pockets/small + max_items = 1 + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/tiny + max_items = 1 + max_w_class = WEIGHT_CLASS_TINY + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/small/detective + attack_hand_interact = TRUE // so the detectives would discover pockets in their hats + +/datum/component/storage/concrete/pockets/shoes + attack_hand_interact = FALSE + quickdraw = TRUE + silent = TRUE + +/datum/component/storage/concrete/pockets/shoes/Initialize() + . = ..() + can_hold = typecacheof(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/device/firing_pin + )) + +/datum/component/storage/concrete/pockets/shoes/clown/Initialize() + . = ..() + can_hold = typecacheof(list( + /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, + /obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector, + /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper, + /obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini, + /obj/item/device/firing_pin, /obj/item/bikehorn)) + +/datum/component/storage/concrete/pockets/pocketprotector + max_items = 3 + max_w_class = WEIGHT_CLASS_TINY + +/datum/component/storage/concrete/pockets/pocketprotector/Initialize() + . = ..() + can_hold = typecacheof(list( //Same items as a PDA + /obj/item/pen, + /obj/item/toy/crayon, + /obj/item/lipstick, + /obj/item/device/flashlight/pen, + /obj/item/clothing/mask/cigarette)) \ No newline at end of file diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm new file mode 100644 index 0000000000..9966fa3749 --- /dev/null +++ b/code/datums/components/storage/concrete/rped.dm @@ -0,0 +1,31 @@ +/datum/component/storage/concrete/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 100 + max_items = 50 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating() && !stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE + +/datum/component/storage/concrete/bluespace/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 800 + max_items = 400 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating() && !stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE diff --git a/code/datums/components/storage/concrete/special.dm b/code/datums/components/storage/concrete/special.dm new file mode 100644 index 0000000000..556e9e9fc9 --- /dev/null +++ b/code/datums/components/storage/concrete/special.dm @@ -0,0 +1,5 @@ +/datum/component/storage/concrete/secret_satchel/can_be_inserted(I,msg,user) + if(SSpersistence.spawned_objects[I]) + to_chat(user, "[I] is unstable after its journey through space and time, it wouldn't survive another trip.") + return FALSE + return ..() diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm new file mode 100644 index 0000000000..1f0c44c650 --- /dev/null +++ b/code/datums/components/storage/concrete/stack.dm @@ -0,0 +1,67 @@ +//Stack-only storage. +/datum/component/storage/concrete/stack + display_numerical_stacking = TRUE + var/max_combined_stack_amount = 300 + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 + +/datum/component/storage/concrete/stack/proc/total_stack_amount() + . = 0 + var/atom/real_location = real_location() + for(var/i in real_location) + var/obj/item/stack/S = i + if(!istype(S)) + continue + . += S.amount + +/datum/component/storage/concrete/stack/proc/remaining_space() + return max(0, max_combined_stack_amount - total_stack_amount()) + +//emptying procs do not need modification as stacks automatically merge. + +/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) + if(!istype(I, /obj/item/stack)) + if(override) + return ..() + return FALSE + var/atom/real_location = real_location() + var/obj/item/stack/S = I + var/can_insert = min(S.amount, remaining_space()) + if(!can_insert) + return FALSE + for(var/i in real_location) //combine. + if(QDELETED(I)) + return + var/obj/item/stack/_S = i + if(!istype(_S)) + continue + if(_S.merge_type == S.merge_type) + _S.add(can_insert) + S.use(can_insert, TRUE) + return TRUE + return ..(S.change_stack(null, can_insert), override) + +/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) + var/atom/real_location = real_location() + var/obj/item/stack/S = I + if(!istype(S)) + return ..() + if(S.amount > S.max_amount) + var/overrun = S.amount - S.max_amount + S.amount = S.max_amount + var/obj/item/stack/temp = new S.type(real_location, overrun) + handle_item_insertion(temp) + return ..(S, new_location) + +/datum/component/storage/concrete/stack/_process_numerical_display() + var/atom/real_location = real_location() + . = list() + for(var/i in real_location) + var/obj/item/stack/I = i + if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. + continue + if(!.[I.merge_type]) + .[I.merge_type] = new /datum/numbered_display(I, I.amount) + else + var/datum/numbered_display/ND = .[I.merge_type] + ND.number += I.amount diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm new file mode 100644 index 0000000000..ce9e8ab72a --- /dev/null +++ b/code/datums/components/storage/storage.dm @@ -0,0 +1,730 @@ +#define COLLECT_ONE 0 +#define COLLECT_EVERYTHING 1 +#define COLLECT_SAME 2 + +#define DROP_NOTHING 0 +#define DROP_AT_PARENT 1 +#define DROP_AT_LOCATION 2 + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage + dupe_mode = COMPONENT_DUPE_UNIQUE + var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. + + var/list/can_hold //if this is set, only things in this typecache will fit. + var/list/cant_hold //if this is set, anything in this typecache will not be able to fit. + + var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. + + var/locked = FALSE //when locked nothing can see inside or use it. + + var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. + var/max_combined_w_class = 14 //max combined sizes of objects that will fit. + var/max_items = 7 //max number of objects that will fit. + + var/emp_shielded = FALSE + + var/silent = FALSE //whether this makes a message when things are put in. + var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. + var/rustle_sound = TRUE //play rustle sound on interact. + var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. + var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. + + var/collection_mode = COLLECT_EVERYTHING + + var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. + + var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. + + var/obj/screen/storage/boxes //storage display object + var/obj/screen/close/closer //close button object + + var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. + + var/attack_hand_interact = TRUE //interact on attack hand. + var/quickdraw = FALSE //altclick interact + + var/datum/action/item_action/storage_gather_mode/modeswitch_action + + //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. + var/screen_max_columns = 7 //These two determine maximum screen sizes. + var/screen_max_rows = INFINITY + var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer + var/screen_pixel_y = 16 + var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. + var/screen_start_y = 2 + //End + +/datum/component/storage/Initialize(datum/component/storage/concrete/master) + if(!isatom(parent)) + . = COMPONENT_INCOMPATIBLE + if(master) + change_master(master) + boxes = new(null, src) + closer = new(null, src) + orient2hud() + + RegisterSignal(COMSIG_CONTAINS_STORAGE, .proc/on_check) + RegisterSignal(COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) + RegisterSignal(COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) + RegisterSignal(COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) + RegisterSignal(COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) + RegisterSignal(COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) + RegisterSignal(COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) + RegisterSignal(COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) + RegisterSignal(COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) + RegisterSignal(COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) + RegisterSignal(COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) + RegisterSignal(COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) + RegisterSignal(COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) + + RegisterSignal(COMSIG_PARENT_ATTACKBY, .proc/attackby) + + RegisterSignal(COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) + RegisterSignal(COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) + RegisterSignal(COMSIG_ATOM_EMP_ACT, .proc/emp_act) + RegisterSignal(COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) + RegisterSignal(COMSIG_ATOM_EXITED, .proc/_removal_reset) + + RegisterSignal(COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) + RegisterSignal(COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) + RegisterSignal(COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) + + RegisterSignal(COMSIG_MOVABLE_THROW, .proc/close_all) + + RegisterSignal(COMSIG_CLICK_ALT, .proc/on_alt_click) + RegisterSignal(COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) + RegisterSignal(COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_recieve) + + update_actions() + +/datum/component/storage/Destroy() + close_all() + QDEL_NULL(boxes) + QDEL_NULL(closer) + LAZYCLEARLIST(is_using) + return ..() + +/datum/component/storage/OnTransfer(datum/new_parent) + update_actions() + +/datum/component/storage/proc/update_actions() + QDEL_NULL(modeswitch_action) + if(!isitem(parent) || !allow_quick_gather) + return + var/obj/item/I = parent + modeswitch_action = new(I) + if(I.obj_flags & IN_INVENTORY) + var/mob/M = I.loc + if(!istype(M)) + return + modeswitch_action.Grant(M) + +/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) + if(!istype(new_master)) + return FALSE + master.on_slave_unlink(src) + master = new_master + master.on_slave_link(src) + return TRUE + +/datum/component/storage/proc/master() + if(master == src) + return //infinite loops yo. + return master + +/datum/component/storage/proc/real_location() + var/datum/component/storage/concrete/master = master() + return master? master.real_location() : null + +/datum/component/storage/proc/attack_self(mob/M) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if((M.get_active_held_item() == parent) && allow_quick_empty) + quick_empty(M) + +/datum/component/storage/proc/preattack_intercept(obj/O, mob/M, params) + if(!isitem(O) || !click_gather || O.SendSignal(COMSIG_CONTAINS_STORAGE)) + return FALSE + . = COMPONENT_NO_ATTACK + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + return + if(!isturf(I.loc)) + return + var/list/things = I.loc.contents.Copy() + if(collection_mode == COLLECT_SAME) + things = typecache_filter_list(things, typecacheof(I.type)) + var/len = length(things) + if(!len) + to_chat(M, "You failed to pick up anything with [parent].") + return + var/datum/progressbar/progress = new(M, len, I.loc) + var/list/rejections = list() + while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) + stoplag(1) + qdel(progress) + to_chat(M, "You put everything you could [insert_preposition] [parent].") + +/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) + var/atom/source_real_location = src_object.real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != source_real_location) + continue + if(user.active_storage != src_object) + if(I.on_found(user)) + break + if(can_be_inserted(I,FALSE,user)) + handle_item_insertion(I, TRUE, user) + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != thing_loc) + continue + if(I.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no + if(real_location.contents.len >= max_items) + break + rejections += I.type // therefore full bags are still a little spammy + continue + + handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. + + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/quick_empty(mob/M) + var/atom/A = parent + if((!ishuman(M) && (A.loc != M)) || (M.stat != CONSCIOUS) || M.restrained() || !M.canmove) + return + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + A.add_fingerprint(M) + to_chat(M, "You start dumping out [parent].") + var/turf/T = get_turf(A) + var/list/things = contents() + var/datum/progressbar/progress = new(M, length(things), T) + while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) + stoplag(1) + qdel(progress) + +/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != real_location) + continue + remove_from_storage(I, target) + if(TICK_CHECK) + progress.update(progress.goal - length(things)) + return TRUE + progress.update(progress.goal - length(things)) + return FALSE + +/datum/component/storage/proc/do_quick_empty(atom/_target) + if(!_target) + _target = get_turf(parent) + if(usr) + hide_from(usr) + var/list/contents = contents() + var/atom/real_location = real_location() + for(var/obj/item/I in contents) + if(I.loc != real_location) + continue + remove_from_storage(I, _target) + return TRUE + +/datum/component/storage/proc/set_locked(new_state) + locked = new_state + if(locked) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + var/atom/real_location = real_location() + for(var/obj/item/I in real_location.contents) + if(QDELETED(I)) + continue + if(!.[I.type]) + .[I.type] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .[I.type] + ND.number++ + +//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/datum/component/storage/proc/orient2hud() + var/atom/real_location = real_location() + var/adjusted_contents = real_location.contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_numerical_stacking) + numbered_contents = _process_numerical_display() + adjusted_contents = numbered_contents.len + + var/columns = CLAMP(max_items, 1, screen_max_columns) + var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) + standard_orient_objs(rows, columns, numbered_contents) + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) + boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" + var/cx = screen_start_x + var/cy = screen_start_y + if(islist(numerical_display_contents)) + for(var/type in numerical_display_contents) + var/datum/numbered_display/ND = numerical_display_contents[type] + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + else + var/atom/real_location = real_location() + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" + +/datum/component/storage/proc/show_to(mob/M) + if(!M.client) + return FALSE + var/atom/real_location = real_location() + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in real_location) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud() + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= real_location.contents + M.active_storage = src + LAZYOR(is_using, M) + return TRUE + +/datum/component/storage/proc/hide_from(mob/M) + if(!M.client) + return TRUE + var/atom/real_location = real_location() + M.client.screen -= boxes + M.client.screen -= closer + M.client.screen -= real_location.contents + if(M.active_storage == src) + M.active_storage = null + LAZYREMOVE(is_using, M) + return TRUE + +/datum/component/storage/proc/close(mob/M) + hide_from(M) + +/datum/component/storage/proc/close_all() + . = FALSE + for(var/mob/M in can_see_contents()) + close(M) + . = TRUE //returns TRUE if any mobs actually got a close(M) call + +/datum/component/storage/proc/emp_act(severity) + var/atom/A = parent + if(!isliving(A.loc) && !emp_shielded) + var/datum/component/storage/concrete/master = master() + master.emp_act(severity) + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/datum/component/storage/proc/orient_objs(tx, ty, mx, my) + var/atom/real_location = real_location() + var/cx = tx + var/cy = ty + boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + closer.screen_loc = "[mx+1],[my]" + +//Resets something that is being removed from storage. +/datum/component/storage/proc/_removal_reset(atom/movable/thing) + if(!istype(thing)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master._removal_reset(thing) + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted +/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) + if(!istype(AM)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.remove_from_storage(AM, new_location) + +/datum/component/storage/proc/refresh_mob_views() + var/list/seeing = can_see_contents() + for(var/i in seeing) + show_to(i) + return TRUE + +/datum/component/storage/proc/can_see_contents() + var/list/cansee = list() + for(var/mob/M in is_using) + if(M.active_storage == src && M.client) + cansee |= M + else + LAZYREMOVE(is_using, M) + return cansee + +//Tries to dump content +/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) + var/atom/A = parent + var/atom/dump_destination = dest_object.get_dumping_location() + if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, 1, -5) + return TRUE + return FALSE + +//This proc is called when you want to place an item into the storage item. +/datum/component/storage/proc/attackby(obj/item/I, mob/M, params) + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = TRUE //no afterattack + if(iscyborg(M)) + return + if(!can_be_inserted(I, FALSE, M)) + var/atom/real_location = real_location() + if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + handle_item_insertion(I, FALSE, M) + +/datum/component/storage/proc/return_inv() + . = list() + . += contents() + for(var/i in contents()) + var/atom/a = i + GET_COMPONENT_FROM(STR, /datum/component/storage, a) + if(STR) + . += STR.return_inv() + +/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! + var/atom/real_location = real_location() + return real_location.contents.Copy() + +/datum/component/storage/proc/signal_return_inv(list/interface) + if(!islist(interface)) + return FALSE + interface |= return_inv() + return TRUE + +/datum/component/storage/proc/mousedrop_onto(atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + var/atom/A = parent + if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked + A.add_fingerprint(M) + if(!over_object) + return FALSE + if(ismecha(M.loc)) // stops inventory actions in a mech + return FALSE + // this must come before the screen objects only block, dunno why it wasn't before + if(over_object == M) + user_show_to_mob(M) + if(!M.incapacitated()) + if(!istype(over_object, /obj/screen)) + dump_content_at(over_object, M) + return + if(A.loc != M) + return + playsound(A, "rustle", 50, 1, -5) + if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(A, H.held_index) + return + A.add_fingerprint(M) + +/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) + var/atom/A = parent + if(!istype(M)) + return FALSE + A.add_fingerprint(M) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(M.CanReach(parent, view_only = TRUE)) + show_to(M) + +/datum/component/storage/proc/mousedrop_recieve(atom/movable/O, mob/M) + if(isitem(O)) + var/obj/item/I = O + if(iscarbon(M) || isdrone(M)) + var/mob/living/L = M + if(!L.incapacitated() && I == L.get_active_held_item()) + if(!I.SendSignal(COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. + handle_item_insertion(I, FALSE, L) + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) + if(!istype(I) || (I.flags_1 & ABSTRACT_1)) + return FALSE //Not an item + var/atom/real_location = real_location() + var/atom/parent = src.parent + if(real_location == I.loc) + return FALSE //Means the item is already in the storage item + if(locked) + if(M) + parent.add_fingerprint(M) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(real_location.contents.len >= max_items) + if(!stop_messages) + to_chat(M, "[parent] is full, make some space!") + return FALSE //Storage item is full + if(length(can_hold)) + if(!is_type_in_typecache(I, can_hold)) + if(!stop_messages) + to_chat(M, "[parent] cannot hold [I]!") + return FALSE + if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold. + if(!stop_messages) + to_chat(M, "[parent] cannot hold [I]!") + return FALSE + if(I.w_class > max_w_class) + if(!stop_messages) + to_chat(M, "[I] is too big for [parent]!") + return FALSE + var/sum_w_class = I.w_class + for(var/obj/item/_I in real_location) + sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(M, "[I] won't fit in [parent], make some space!") + return FALSE + if(isitem(parent)) + var/obj/item/IP = parent + GET_COMPONENT_FROM(STR_I, /datum/component/storage, I) + if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) + if(!stop_messages) + to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") + return FALSE //To prevent the stacking of same sized storage items. + if(I.flags_1 & NODROP_1) //SHOULD be handled in unEquip, but better safe than sorry. + to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [parent]!") + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.slave_can_insert_object(src, I, stop_messages, M) + +/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) + return FALSE + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, 1, -5) + for(var/mob/viewing in viewers(user, null)) + if(M == viewing) + to_chat(usr, "You put [I] [insert_preposition]to [parent].") + else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + +/datum/component/storage/proc/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + +/datum/component/storage/proc/signal_insertion_attempt(obj/item/I, mob/M, silent = FALSE, force = FALSE) + if(!force && !can_be_inserted(I, TRUE, M)) + return FALSE + return handle_item_insertion(I, silent, M) + +/datum/component/storage/proc/signal_can_insert(obj/item/I, mob/M, silent = FALSE) + return can_be_inserted(I, silent, M) + +/datum/component/storage/proc/show_to_ghost(mob/dead/observer/M) + return user_show_to_mob(M, TRUE) + +/datum/component/storage/proc/signal_show_attempt(mob/showto, force = FALSE) + return user_show_to_mob(showto, force) + +/datum/component/storage/proc/on_check() + return TRUE + +/datum/component/storage/proc/check_locked() + return locked + +/datum/component/storage/proc/signal_take_type(type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(user) + if(!user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + else if(!destination.CanReachStorage(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(length(taking) > amount) + taking.Cut(amount) + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + handle_item_insertion(new type(real_location), TRUE) + CHECK_TICK + return TRUE + +/datum/component/storage/proc/on_attack_hand(mob/user) + var/atom/A = parent + if(!attack_hand_interact) + return + if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item + user.active_storage.close(user) + close(user) + . = COMPONENT_NO_ATTACK_HAND + return + + if(rustle_sound) + playsound(A, "rustle", 50, 1, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.l_store = null + return + if(H.r_store == A && !H.get_active_held_item()) + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.r_store = null + return + + if(A.loc == user) + . = COMPONENT_NO_ATTACK_HAND + show_to(user) + +/datum/component/storage/proc/signal_on_pickup(mob/user) + var/atom/A = parent + update_actions() + for(var/mob/M in range(1, A)) + if(M.active_storage == src) + close(M) + +/datum/component/storage/proc/signal_take_obj(atom/movable/AM, new_loc, force = FALSE) + if(!(AM in real_location())) + return FALSE + return remove_from_storage(AM, new_loc) + +/datum/component/storage/proc/signal_quick_empty(atom/loctarget) + return do_quick_empty(loctarget) + +/datum/component/storage/proc/signal_hide_attempt(mob/target) + return hide_from(target) + +/datum/component/storage/proc/on_alt_click(mob/user) + if(!isliving(user) || user.incapacitated() || !quickdraw || !user.CanReach(parent)) + return + var/obj/item/I = locate() in real_location() + if(!I) + return + remove_from_storage(I, get_turf(user)) + if(!user.put_in_hands(I)) + to_chat(user, "You fumble for [I] and it falls on the floor.") + return + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + +/datum/component/storage/proc/gather_mode_switch(mob/user) + collection_mode = (collection_mode+1)%3 + switch(collection_mode) + if(COLLECT_SAME) + to_chat(user, "[parent] now picks up all items of a single type at once.") + if(COLLECT_EVERYTHING) + to_chat(user, "[parent] now picks up all items in a tile at once.") + if(COLLECT_ONE) + to_chat(user, "[parent] now picks up one item at a time.") diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm index 7955f81e66..74d4b90493 100644 --- a/code/datums/diseases/_MobProcs.dm +++ b/code/datums/diseases/_MobProcs.dm @@ -17,6 +17,14 @@ if(HasDisease(D)) return FALSE + var/can_infect = FALSE + for(var/host_type in D.infectable_biotypes) + if(host_type in mob_biotypes) + can_infect = TRUE + break + if(!can_infect) + return FALSE + if(!(type in D.viable_mobtypes)) return FALSE @@ -132,14 +140,6 @@ if(has_trait(TRAIT_VIRUSIMMUNE) && !D.bypasses_immunity) return FALSE - var/can_infect = FALSE - for(var/host_type in D.infectable_hosts) - if(host_type in dna.species.species_traits) - can_infect = TRUE - break - if(!can_infect) - return FALSE - for(var/thing in D.required_organs) if(!((locate(thing) in bodyparts) || (locate(thing) in internal_organs))) return FALSE diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm index 614cd14abb..221dd7e804 100644 --- a/code/datums/diseases/_disease.dm +++ b/code/datums/diseases/_disease.dm @@ -30,7 +30,7 @@ var/list/required_organs = list() var/needs_all_cures = TRUE var/list/strain_data = list() //dna_spread special bullshit - var/list/infectable_hosts = list(SPECIES_ORGANIC) //if the disease can spread on organics, synthetics, or undead + var/list/infectable_biotypes = list(MOB_ORGANIC) //if the disease can spread on organics, synthetics, or undead var/process_dead = FALSE //if this ticks while the host is dead /datum/disease/Destroy() @@ -139,7 +139,7 @@ var/static/list/copy_vars = list("name", "visibility_flags", "disease_flags", "spread_flags", "form", "desc", "agent", "spread_text", "cure_text", "max_stages", "stage_prob", "viable_mobtypes", "cures", "infectivity", "cure_chance", "bypasses_immunity", "permeability_mod", "severity", "required_organs", "needs_all_cures", "strain_data", - "infectable_hosts", "process_dead") + "infectable_biotypes", "process_dead") var/datum/disease/D = new type() for(var/V in copy_vars) diff --git a/code/datums/diseases/advance/symptoms/species.dm b/code/datums/diseases/advance/symptoms/species.dm index 837252c1e6..d0c32f3244 100644 --- a/code/datums/diseases/advance/symptoms/species.dm +++ b/code/datums/diseases/advance/symptoms/species.dm @@ -12,7 +12,7 @@ if(!..()) return A.process_dead = TRUE - A.infectable_hosts |= SPECIES_UNDEAD + A.infectable_biotypes |= MOB_UNDEAD /datum/symptom/inorganic_adaptation name = "Inorganic Biology" @@ -27,4 +27,4 @@ /datum/symptom/inorganic_adaptation/Start(datum/disease/advance/A) if(!..()) return - A.infectable_hosts |= SPECIES_INORGANIC \ No newline at end of file + A.infectable_biotypes |= MOB_INORGANIC \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/weakness.dm b/code/datums/diseases/advance/symptoms/weakness.dm deleted file mode 100644 index a96266effe..0000000000 --- a/code/datums/diseases/advance/symptoms/weakness.dm +++ /dev/null @@ -1,44 +0,0 @@ -/* -////////////////////////////////////// - -Weakness - - Slightly noticeable. - Lowers resistance slightly. - Decreases stage speed moderately. - Decreases transmittablity moderately. - Moderate Level. - -Bonus - Deals stamina damage to the host - -////////////////////////////////////// -*/ - -/datum/symptom/weakness - - name = "Weakness" - stealth = -1 - resistance = -1 - stage_speed = -2 - transmittable = -2 - level = 3 - severity = 3 - -/datum/symptom/weakness/Activate(datum/disease/advance/A) - ..() - if(prob(SYMPTOM_ACTIVATION_PROB)) - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2) - to_chat(M, "[pick("You feel weak.", "You feel lazy.")]") - if(3, 4) - to_chat(M, "[pick("You feel very frail.", "You think you might faint.")]") - M.adjustStaminaLoss(15) - else - to_chat(M, "[pick("You feel tremendously weak!", "Your body trembles as exhaustion creeps over you.")]") - M.adjustStaminaLoss(30) - if(M.getStaminaLoss() > 60 && !M.stat) - M.visible_message("[M] faints!", "You swoon and faint...") - M.AdjustSleeping(100) - return diff --git a/code/datums/diseases/beesease.dm b/code/datums/diseases/beesease.dm index f6504fa464..ab64fd515c 100644 --- a/code/datums/diseases/beesease.dm +++ b/code/datums/diseases/beesease.dm @@ -10,7 +10,7 @@ viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) desc = "If left untreated subject will regurgitate bees." severity = DISEASE_SEVERITY_MEDIUM - infectable_hosts = list(SPECIES_ORGANIC, SPECIES_UNDEAD) //bees nesting in corpses + infectable_biotypes = list(MOB_ORGANIC, MOB_UNDEAD) //bees nesting in corpses /datum/disease/beesease/stage_act() ..() diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm index 13959e9bda..7c5f05c440 100644 --- a/code/datums/diseases/magnitis.dm +++ b/code/datums/diseases/magnitis.dm @@ -10,7 +10,7 @@ permeability_mod = 0.75 desc = "This disease disrupts the magnetic field of your body, making it act as if a powerful magnet. Injections of iron help stabilize the field." severity = DISEASE_SEVERITY_MEDIUM - infectable_hosts = list(SPECIES_ORGANIC, SPECIES_ROBOTIC) + infectable_biotypes = list(MOB_ORGANIC, MOB_ROBOTIC) process_dead = TRUE /datum/disease/magnitis/stage_act() diff --git a/code/datums/diseases/parrotpossession.dm b/code/datums/diseases/parrotpossession.dm index 4fe0dc21b0..66395ef5a5 100644 --- a/code/datums/diseases/parrotpossession.dm +++ b/code/datums/diseases/parrotpossession.dm @@ -11,7 +11,7 @@ viable_mobtypes = list(/mob/living/carbon/human) desc = "Subject is possesed by the vengeful spirit of a parrot. Call the priest." severity = DISEASE_SEVERITY_MEDIUM - infectable_hosts = list(SPECIES_ORGANIC, SPECIES_UNDEAD, SPECIES_INORGANIC, SPECIES_ROBOTIC) + infectable_biotypes = list(MOB_ORGANIC, MOB_UNDEAD, MOB_INORGANIC, MOB_ROBOTIC) bypasses_immunity = TRUE //2spook var/mob/living/simple_animal/parrot/Poly/ghost/parrot diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index db88bd6777..52b63ccd5f 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -149,7 +149,7 @@ stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") stage5 = list("Your skin feels as if it's about to burst off!") new_form = /mob/living/silicon/robot - infectable_hosts = list(SPECIES_ORGANIC, SPECIES_UNDEAD, SPECIES_ROBOTIC) + infectable_biotypes = list(MOB_ORGANIC, MOB_UNDEAD, MOB_ROBOTIC) /datum/disease/transformation/robot/stage_act() ..() @@ -263,4 +263,4 @@ stage4 = list("You're ravenous.") stage5 = list("You have become a morph.") new_form = /mob/living/simple_animal/hostile/morph - infectable_hosts = list(SPECIES_ORGANIC, SPECIES_INORGANIC, SPECIES_UNDEAD) //magic! + infectable_biotypes = list(MOB_ORGANIC, MOB_INORGANIC, MOB_UNDEAD) //magic! diff --git a/code/datums/mood_events/mood_event.dm b/code/datums/mood_events/mood_event.dm index 6b4301b83a..dfe3049bdd 100644 --- a/code/datums/mood_events/mood_event.dm +++ b/code/datums/mood_events/mood_event.dm @@ -11,6 +11,7 @@ /datum/mood_event/Destroy() remove_effects() + return ..() /datum/mood_event/proc/add_effects(param) return diff --git a/code/datums/mutations.dm b/code/datums/mutations.dm index a171cf91da..7de9ca8b4a 100644 --- a/code/datums/mutations.dm +++ b/code/datums/mutations.dm @@ -56,13 +56,13 @@ GLOBAL_LIST_EMPTY(mutations_list) /datum/mutation/human/proc/on_acquiring(mob/living/carbon/human/owner) if(!owner || !istype(owner) || owner.stat == DEAD || (src in owner.dna.mutations)) - return 1 + return TRUE if(species_allowed.len && !species_allowed.Find(owner.dna.species.id)) - return 1 + return TRUE if(health_req && owner.health < health_req) - return 1 + return TRUE if(limb_req && !owner.get_bodypart(limb_req)) - return 1 + return TRUE owner.dna.mutations.Add(src) if(text_gain_indication) to_chat(owner, text_gain_indication) diff --git a/code/datums/numbered_display.dm b/code/datums/numbered_display.dm new file mode 100644 index 0000000000..9aa880aa75 --- /dev/null +++ b/code/datums/numbered_display.dm @@ -0,0 +1,10 @@ +//Used in storage. +/datum/numbered_display + var/obj/item/sample_object + var/number + +/datum/numbered_display/New(obj/item/sample, _number = 1) + if(!istype(sample)) + qdel(src) + sample_object = sample + number = _number diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm index 404c584fd4..fe18071cf8 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -21,6 +21,20 @@ mappath = "[prefix][shuttle_id].dmm" . = ..() +/datum/map_template/shuttle/load(turf/T, centered) + . = ..() + if(!.) + return + var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]), + locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ])) + for(var/i in 1 to turfs.len) + var/turf/place = turfs[i] + if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination. + continue + if(length(place.baseturfs) < 2) // Some snowflake shuttle shit + continue + place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle) + //Whatever special stuff you want /datum/map_template/shuttle/proc/on_bought() return @@ -60,7 +74,21 @@ port_id = "infiltrator" can_be_bought = FALSE +/datum/map_template/shuttle/aux_base + port_id = "aux_base" + can_be_bought = FALSE +/datum/map_template/shuttle/escape_pod + port_id = "escape_pod" + can_be_bought = FALSE + +/datum/map_template/shuttle/assault_pod + port_id = "assault_pod" + can_be_bought = FALSE + +/datum/map_template/shuttle/pirate + port_id = "pirate" + can_be_bought = FALSE // Shuttles start here: @@ -355,3 +383,27 @@ /datum/map_template/shuttle/arrival/pubby suffix = "pubby" name = "arrival shuttle (Pubby)" + +/datum/map_template/shuttle/arrival/omega + suffix = "omega" + name = "arrival shuttle (Omega)" + +/datum/map_template/shuttle/aux_base/default + suffix = "default" + name = "auxilliary base (Default)" + +/datum/map_template/shuttle/escape_pod/default + suffix = "default" + name = "escape pod (Default)" + +/datum/map_template/shuttle/escape_pod/large + suffix = "large" + name = "escape pod (Large)" + +/datum/map_template/shuttle/assault_pod/default + suffix = "default" + name = "assault pod (Default)" + +/datum/map_template/shuttle/pirate/default + suffix = "default" + name = "pirate ship (Default)" diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index 237a72ffda..753918f618 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -53,9 +53,7 @@ if(!where) where = "at your feet" if(where == "in your backpack") - var/obj/item/storage/B = H.back - B.orient2hud(trait_holder) - B.show_to(trait_holder) + H.back.SendSignal(COMSIG_TRY_STORAGE_SHOW, H) where_text = "There is a precious family [heirloom.name] [where], passed down from generation to generation. Keep it safe!" /datum/trait/family_heirloom/post_add() diff --git a/tgstation.dme b/tgstation.dme index f851aa65c4..9444583f5d 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -344,6 +344,15 @@ #include "code\datums\components\thermite.dm" #include "code\datums\components\wet_floor.dm" #include "code\datums\components\decals\blood.dm" +#include "code\datums\components\storage\storage.dm" +#include "code\datums\components\storage\concrete\_concrete.dm" +#include "code\datums\components\storage\concrete\bag_of_holding.dm" +#include "code\datums\components\storage\concrete\bluespace.dm" +#include "code\datums\components\storage\concrete\implant.dm" +#include "code\datums\components\storage\concrete\pockets.dm" +#include "code\datums\components\storage\concrete\rped.dm" +#include "code\datums\components\storage\concrete\special.dm" +#include "code\datums\components\storage\concrete\stack.dm" #include "code\datums\diseases\_disease.dm" #include "code\datums\diseases\_MobProcs.dm" #include "code\datums\diseases\anxiety.dm"