diff --git a/citadel.dme b/citadel.dme
index f89b26deff9b..5885e8bb4deb 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -174,7 +174,6 @@
#include "code\__DEFINES\controllers\timer.dm"
#include "code\__DEFINES\datums\beam.dm"
#include "code\__DEFINES\datums\design.dm"
-#include "code\__DEFINES\datums\event_args.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\components\riding.dm"
@@ -186,6 +185,7 @@
#include "code\__DEFINES\dcs\signals\signals_turf.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_beam.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_beam_legacy.dm"
+#include "code\__DEFINES\dcs\signals\datums\signals_inventory.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_perspective.dm"
#include "code\__DEFINES\dcs\signals\elements\signals_element_conflict_checking.dm"
#include "code\__DEFINES\dcs\signals\items\signals_inducer.dm"
@@ -235,6 +235,7 @@
#include "code\__DEFINES\inventory\accessories.dm"
#include "code\__DEFINES\inventory\bodytypes.dm"
#include "code\__DEFINES\inventory\carry_weight.dm"
+#include "code\__DEFINES\inventory\hud.dm"
#include "code\__DEFINES\inventory\icons.dm"
#include "code\__DEFINES\inventory\misc.dm"
#include "code\__DEFINES\inventory\procs.dm"
@@ -277,6 +278,7 @@
#include "code\__DEFINES\mobs\biology.dm"
#include "code\__DEFINES\mobs\characteristics.dm"
#include "code\__DEFINES\mobs\grab.dm"
+#include "code\__DEFINES\mobs\hands.dm"
#include "code\__DEFINES\mobs\iff.dm"
#include "code\__DEFINES\mobs\intent.dm"
#include "code\__DEFINES\mobs\life.dm"
@@ -447,6 +449,7 @@
#include "code\__HELPERS\lists\traverse.dm"
#include "code\__HELPERS\lists\types_typecaches.dm"
#include "code\__HELPERS\lists\unique.dm"
+#include "code\__HELPERS\lists\unsorted\pack_2d_flat_list.dm"
#include "code\__HELPERS\math\angle.dm"
#include "code\__HELPERS\math\distance.dm"
#include "code\__HELPERS\math\fractions.dm"
@@ -858,6 +861,7 @@
#include "code\datums\event_args\_event_args.dm"
#include "code\datums\event_args\actor.dm"
#include "code\datums\event_args\clickchain.dm"
+#include "code\datums\event_args\event_args.dm"
#include "code\datums\helper_datums\construction_datum.dm"
#include "code\datums\helper_datums\events.dm"
#include "code\datums\helper_datums\getrev.dm"
@@ -1507,6 +1511,9 @@
#include "code\game\objects\items-carry_weight.dm"
#include "code\game\objects\items-defense.dm"
#include "code\game\objects\items-interaction.dm"
+#include "code\game\objects\items-inventory-hooks.dm"
+#include "code\game\objects\items-inventory-rendering.dm"
+#include "code\game\objects\items-inventory.dm"
#include "code\game\objects\items.dm"
#include "code\game\objects\materials.dm"
#include "code\game\objects\misc.dm"
@@ -2008,8 +2015,15 @@
#include "code\game\objects\systems\cell_slot.dm"
#include "code\game\objects\systems\storage.dm"
#include "code\game\rendering\client.dm"
+#include "code\game\rendering\hud_preferences.dm"
+#include "code\game\rendering\hud_style.dm"
#include "code\game\rendering\mob.dm"
#include "code\game\rendering\screen.dm"
+#include "code\game\rendering\screen_legacy.dm"
+#include "code\game\rendering\actor_huds\actor_hud-screen_object.dm"
+#include "code\game\rendering\actor_huds\actor_hud.dm"
+#include "code\game\rendering\actor_huds\actor_hud_holder.dm"
+#include "code\game\rendering\actor_huds\huds\inventory.dm"
#include "code\game\rendering\atom_huds\atom_hud.dm"
#include "code\game\rendering\atom_huds\atom_hud_provider.dm"
#include "code\game\rendering\atom_huds\legacy.dm"
@@ -2033,7 +2047,6 @@
#include "code\game\rendering\legacy\robot.dm"
#include "code\game\rendering\legacy\spell_screen_objects.dm"
#include "code\game\rendering\legacy\intents\throwing.dm"
-#include "code\game\rendering\legacy\inventory\inventory.dm"
#include "code\game\rendering\legacy\objects\waypoint_tracker.dm"
#include "code\game\rendering\parallax\parallax.dm"
#include "code\game\rendering\parallax\parallax_holder.dm"
@@ -3543,15 +3556,21 @@
#include "code\modules\mob\health.dm"
#include "code\modules\mob\hear_say.dm"
#include "code\modules\mob\holder.dm"
-#include "code\modules\mob\inventory.dm"
+#include "code\modules\mob\inventory_legacy.dm"
#include "code\modules\mob\life.dm"
-#include "code\modules\mob\login.dm"
-#include "code\modules\mob\logout.dm"
#include "code\modules\mob\mob-client.dm"
#include "code\modules\mob\mob-damage.dm"
#include "code\modules\mob\mob-defense.dm"
+#include "code\modules\mob\mob-hands.dm"
#include "code\modules\mob\mob-iff.dm"
+#include "code\modules\mob\mob-inventory-abstraction.dm"
+#include "code\modules\mob\mob-inventory-helpers.dm"
+#include "code\modules\mob\mob-inventory-internal.dm"
+#include "code\modules\mob\mob-inventory-stripping.dm"
+#include "code\modules\mob\mob-inventory.dm"
#include "code\modules\mob\mob-keybind-triggers.dm"
+#include "code\modules\mob\mob-login.dm"
+#include "code\modules\mob\mob-logout.dm"
#include "code\modules\mob\mob.dm"
#include "code\modules\mob\mob_defines.dm"
#include "code\modules\mob\mob_helpers.dm"
@@ -3609,24 +3628,27 @@
#include "code\modules\mob\freelook\mask\cultnet.dm"
#include "code\modules\mob\freelook\mask\eye.dm"
#include "code\modules\mob\freelook\mask\update_triggers.dm"
-#include "code\modules\mob\inventory\hands.dm"
-#include "code\modules\mob\inventory\helpers.dm"
+#include "code\modules\mob\inventory\inventory-hands-check.dm"
+#include "code\modules\mob\inventory\inventory-hands-drop.dm"
+#include "code\modules\mob\inventory\inventory-hands-get.dm"
+#include "code\modules\mob\inventory\inventory-hands-legacy.dm"
+#include "code\modules\mob\inventory\inventory-hands-put.dm"
+#include "code\modules\mob\inventory\inventory-hands.dm"
+#include "code\modules\mob\inventory\inventory-hooks.dm"
+#include "code\modules\mob\inventory\inventory-rendering.dm"
#include "code\modules\mob\inventory\inventory.dm"
#include "code\modules\mob\inventory\inventory_slot.dm"
-#include "code\modules\mob\inventory\items.dm"
-#include "code\modules\mob\inventory\mobs.dm"
-#include "code\modules\mob\inventory\rendering.dm"
-#include "code\modules\mob\inventory\stripping.dm"
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\butchering.dm"
#include "code\modules\mob\living\death.dm"
#include "code\modules\mob\living\default_language.dm"
#include "code\modules\mob\living\health.dm"
-#include "code\modules\mob\living\inventory.dm"
+#include "code\modules\mob\living\inventory_legacy.dm"
#include "code\modules\mob\living\life.dm"
#include "code\modules\mob\living\living-damage.dm"
#include "code\modules\mob\living\living-defense-legacy.dm"
#include "code\modules\mob\living\living-defense.dm"
+#include "code\modules\mob\living\living-inventory.dm"
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defines.dm"
#include "code\modules\mob\living\living_powers.dm"
@@ -3657,6 +3679,7 @@
#include "code\modules\mob\living\bot\SLed209bot.dm"
#include "code\modules\mob\living\carbon\breathe.dm"
#include "code\modules\mob\living\carbon\carbon-defense.dm"
+#include "code\modules\mob\living\carbon\carbon-hands.dm"
#include "code\modules\mob\living\carbon\carbon.dm"
#include "code\modules\mob\living\carbon\carbon_defense.dm"
#include "code\modules\mob\living\carbon\carbon_defines.dm"
diff --git a/code/__DEFINES/_planes+layers.dm b/code/__DEFINES/_planes+layers.dm
index 4dad67ea7f6a..b14381857e10 100644
--- a/code/__DEFINES/_planes+layers.dm
+++ b/code/__DEFINES/_planes+layers.dm
@@ -403,6 +403,7 @@
*? Separate layer with which to apply colorblindness.
*/
#define INVENTORY_PLANE 96
+ #define INVENTORY_PLATE_LAYER 100
/**
*! -- Above HUD Plane
diff --git a/code/__DEFINES/datums/event_args.dm b/code/__DEFINES/datums/event_args.dm
deleted file mode 100644
index 2fa589bf4e6f..000000000000
--- a/code/__DEFINES/datums/event_args.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 silicons *//
-
-// make sure a var that is either event_args/actor or a single mob/user is event args; if it's not
-#define E_ARGS_WRAP_USER_TO_ACTOR(USER) USER = ismob(USER)? new /datum/event_args/actor(USER) : USER
diff --git a/code/__DEFINES/dcs/signals/datums/signals_inventory.dm b/code/__DEFINES/dcs/signals/datums/signals_inventory.dm
new file mode 100644
index 000000000000..3f232a7913a1
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/datums/signals_inventory.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* for /datum/inventory *//
+
+/// raised with (obj/item/item, datum/inventory_slot/slot_or_index)
+///
+/// * raised after COMSIG_INVENTORY_ITEM_EXITED_SLOT during swaps
+#define COMSIG_INVENTORY_ITEM_ENTERED_SLOT "inventory-item-entered-slot"
+/// raised with (obj/item/item, datum/inventory_slot/slot_or_index)
+///
+/// * raised before COMSIG_INVENTORY_ITEM_ENTERED_SLOT during swaps
+#define COMSIG_INVENTORY_ITEM_EXITED_SLOT "inventory-item-exited-slot"
+/// raised with ()
+///
+/// * raised on any inventory slot mutation
+#define COMSIG_INVENTORY_SLOT_REBUILD "inventory-slot-rebuild"
diff --git a/code/__DEFINES/inventory/hud.dm b/code/__DEFINES/inventory/hud.dm
new file mode 100644
index 000000000000..9773ab7bc94b
--- /dev/null
+++ b/code/__DEFINES/inventory/hud.dm
@@ -0,0 +1,55 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//? HUD screen_loc's are in code/__DEFINES/screen.dm ?//
+
+//* inventory_hud_anchor *//
+
+/// anchor to the main inventory drawer
+///
+/// * main axis runs towards the middle of screen on Y axis
+/// * cross axis runs towards the middle of screen on X axis
+/// * both main and cross cannot be 0, as that is where the drawer button is
+///
+/// * valid main-axis indices: >= 0, cross-axis != 0 if 0
+/// * valid cross-axis indices: >= 0, main-axis != 0 if 0
+#define INVENTORY_HUD_ANCHOR_TO_DRAWER "drawer"
+/// anchor to next to hands panel
+///
+/// * main axis runs left/right of hands if negative/positive
+/// * cross axis runs away from edge of screen of hands
+///
+/// * valid main-axis indices: != 0
+/// * valid cross-axis indices: >= 0
+#define INVENTORY_HUD_ANCHOR_TO_HANDS "hands"
+/// automatic - shove it in anywhere we can
+///
+/// * axis cannot be specified for this
+#define INVENTORY_HUD_ANCHOR_AUTOMATIC "automatic"
+
+//* inventory_hud_class *//
+
+/// always visible
+#define INVENTORY_HUD_CLASS_ALWAYS "always"
+/// only when drawer is open
+#define INVENTORY_HUD_CLASS_DRAWER "drawer"
+
+//* inventory_hud hide sources *//
+
+/// from f12 / zoom toggle
+#define INVENTORY_HUD_HIDE_SOURCE_F12 "F12"
+/// from drawer toggle
+#define INVENTORY_HUD_HIDE_SOURCE_DRAWER "drawer"
+
+//* inventory slot remappings for species *//
+
+/// inventory_hud_main_axis
+#define INVENTORY_SLOT_REMAP_MAIN_AXIS "main-axis"
+/// inventory_hud_cross_axis
+#define INVENTORY_SLOT_REMAP_CROSS_AXIS "cross-axis"
+/// name
+#define INVENTORY_SLOT_REMAP_NAME "name"
+/// inventory_hud_class
+#define INVENTORY_SLOT_REMAP_CLASS "class"
+/// inventory_hud_anchor
+#define INVENTORY_SLOT_REMAP_ANCHOR "anchor"
diff --git a/code/__DEFINES/inventory/misc.dm b/code/__DEFINES/inventory/misc.dm
index 8bbf978e80ae..175330159241 100644
--- a/code/__DEFINES/inventory/misc.dm
+++ b/code/__DEFINES/inventory/misc.dm
@@ -1,4 +1,5 @@
// proc: dropped() on /obj/item
// todo: this should be in procs.dm and the names need to be changed probably
+// todo: comsig instead?
/// relocated; return false
#define ITEM_RELOCATED_BY_DROPPED -1
diff --git a/code/__DEFINES/inventory/procs.dm b/code/__DEFINES/inventory/procs.dm
index ee68a510c377..a49a6ff2253d 100644
--- a/code/__DEFINES/inventory/procs.dm
+++ b/code/__DEFINES/inventory/procs.dm
@@ -1,4 +1,8 @@
-//! flags for inventory ops
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* Inventory Operation Flags *//
+
/// force; implies INV_OP_IGNORE_DELAY and INV_OP_IGNORE_REACHABILITY
#define INV_OP_FORCE (1<<0)
/// components that intercept to relocate should refrain - usually used with force
@@ -9,7 +13,7 @@
#define INV_OP_SUPPRESS_WARNING (1<<3)
/// do not run logic like checking if you should drop something when something's unequipped
#define INV_OP_NO_LOGIC (1<<4)
-/// do not updatei cons
+/// do not update icons
#define INV_OP_NO_UPDATE_ICONS (1<<5)
/// hint: we are directly dropping to ground/off omb
#define INV_OP_DIRECTLY_DROPPING (1<<6)
@@ -45,7 +49,21 @@
/// no sound, warnings, etc, entirely
#define INV_OP_SILENT (INV_OP_SUPPRESS_SOUND | INV_OP_SUPPRESS_WARNING)
-// todo: INV_OP_RECRUSE
+// todo: INV_OP_RECURSE for nested / worn-over pieces
+
+//* Inventory Return Flags *//
+
+/// Failed
+///
+/// * Yes, on a failure, we just return null.
+/// * This is to fail truthy checks, and be a valid switch() return.
+#define INV_RETURN_FAILED null
+/// Success
+#define INV_RETURN_SUCCESS "success"
+/// Success, but was relocated instead of going to where it should go.
+#define INV_RETURN_RELOCATED "relocated"
+/// Success, and was deleted for it
+#define INV_RETURN_DELETED "deleted"
//! return values from can_equip_conflict_check
/// yes
diff --git a/code/__DEFINES/mobs/hands.dm b/code/__DEFINES/mobs/hands.dm
new file mode 100644
index 000000000000..1462719aaec1
--- /dev/null
+++ b/code/__DEFINES/mobs/hands.dm
@@ -0,0 +1,12 @@
+//* hand manipulation levels *//
+
+/// do surgery / precise tooling / precision work
+#define HAND_MANIPULATION_PRECISE 4
+/// use simple keyboards, manipulate a small switch, rotate objects, etc
+#define HAND_MANIPULATION_GENERAL 3
+/// pick up an item roughly, pull a switch, etc
+#define HAND_MANIPULATION_MOVE 2
+/// just hit your hand against something
+#define HAND_MANIPULATION_DULL 1
+/// level at which someone just can't use a hand at all
+#define HAND_MANIPULATION_NONE 0
diff --git a/code/__DEFINES/mobs/overlays.dm b/code/__DEFINES/mobs/overlays.dm
new file mode 100644
index 000000000000..709c6f7aec98
--- /dev/null
+++ b/code/__DEFINES/mobs/overlays.dm
@@ -0,0 +1,103 @@
+//* Human Overlays Indexes *//
+// These are used as the layers for the icons, as well as indexes in a list that holds onto them.
+// Technically the layers used are all -100+layer to make them FLOAT_LAYER overlays.
+
+/// Mutations like fat, and lasereyes
+#define MUTATIONS_LAYER 1
+/// Skin things added by a call on species
+#define SKIN_LAYER 2
+/// Bloodied hands/feet/anything else
+#define BLOOD_LAYER 3
+/// Injury overlay sprites like open wounds
+#define DAMAGE_LAYER 4
+/// Overlays for open surgical sites
+#define SURGERY_LAYER 5
+/// Underwear/bras/etc
+#define UNDERWEAR_LAYER 6
+/// Shoe-slot item (when set to be under uniform via verb)
+#define SHOES_LAYER_ALT 7
+/// Uniform-slot item
+#define UNIFORM_LAYER 8
+/// ID-slot item
+#define ID_LAYER 9
+/// Shoe-slot item
+#define SHOES_LAYER 10
+/// Glove-slot item
+#define GLOVES_LAYER 11
+/// Belt-slot item
+#define BELT_LAYER 12
+/// Suit-slot item
+#define SUIT_LAYER 13
+/// Some species have tails to render
+#define TAIL_LAYER 14
+/// Eye-slot item
+#define GLASSES_LAYER 15
+/// Belt-slot item (when set to be above suit via verb)
+#define BELT_LAYER_ALT 16
+/// Suit storage-slot item
+#define SUIT_STORE_LAYER 17
+/// Back-slot item
+#define BACK_LAYER 18
+/// The human's hair
+#define HAIR_LAYER 19
+/// Both ear-slot items (combined image)
+#define EARS_LAYER 20
+/// Mob's eyes (used for glowing eyes)
+#define EYES_LAYER 21
+/// Mask-slot item
+#define FACEMASK_LAYER 22
+/// Head-slot item
+#define HEAD_LAYER 23
+/// Handcuffs, if the human is handcuffed, in a secret inv slot
+#define HANDCUFF_LAYER 24
+/// Same as handcuffs, for legcuffs
+#define LEGCUFF_LAYER 25
+/// Hand layers
+#define WORN_LAYER_HELD(index) (26 + index)
+/// Wing overlay layer.
+#define WING_LAYER 45
+/// Tail alt. overlay layer for fixing overlay issues.
+#define TAIL_LAYER_ALT 46
+/// Effects drawn by modifiers
+#define MODIFIER_EFFECTS_LAYER 47
+/// 'Mob on fire' overlay layer
+#define FIRE_LAYER 48
+/// 'Mob submerged' overlay layer
+#define MOB_WATER_LAYER 49
+/// 'Aimed at' overlay layer
+#define TARGETED_LAYER 50
+//! the offset used
+#define BODY_LAYER -100
+
+//* Human Overlay Keys *//
+// These are the actual keys used in overlays_standing.
+
+#define WORN_KEY_MUTATIONS "mutations"
+#define WORN_KEY_SKIN "skin"
+#define WORN_KEY_BLOOD "blood"
+#define WORN_KEY_DAMAGE "damage"
+#define WORN_KEY_SURGERY "surgery"
+#define WORN_KEY_UNDERWEAR "underwear"
+#define WORN_KEY_UNIFORM "uniform"
+#define WORN_KEY_ID "id"
+#define WORN_KEY_SHOES "shoes"
+#define WORN_KEY_GLOVES "gloves"
+#define WORN_KEY_BELT "belt"
+#define WORN_KEY_SUIT "suit"
+#define WORN_KEY_TAIL "tail"
+#define WORN_KEY_GLASSES "glasses"
+#define WORN_KEY_SUITSTORE "suitstore"
+#define WORN_KEY_BACK "back"
+#define WORN_KEY_HAIR "hair"
+#define WORN_KEY_EARS "ears"
+#define WORN_KEY_EYES "eyes"
+#define WORN_KEY_FACEMASK "facemask"
+#define WORN_KEY_HEAD "head"
+#define WORN_KEY_HANDCUFF "handcuff"
+#define WORN_KEY_LEGCUFF "legcuff"
+#define WORN_KEY_HELD(index) "held[index]"
+#define WORN_KEY_WING "wing"
+#define WORN_KEY_MODIFIERS "modifiers"
+#define WORN_KEY_FIRE "fire"
+#define WORN_KEY_WATER "water"
+#define WORN_KEY_TARGETED "targeted"
diff --git a/code/__DEFINES/mobs/rendering.dm b/code/__DEFINES/mobs/rendering.dm
index 0b854a1f52e7..339ee2151bbd 100644
--- a/code/__DEFINES/mobs/rendering.dm
+++ b/code/__DEFINES/mobs/rendering.dm
@@ -13,8 +13,7 @@
#define HUMAN_OVERLAY_UNDERWEAR "underwear"
#define HUMAN_OVERLAY_FIRE "fire"
#define HUMAN_OVERLAY_LIQUID "liquid"
-#define HUMAN_OVERLAY_RHAND "rhand"
-#define HUMAN_OVERLAY_LHAND "lhand"
+#define HUMAN_OVERLAY_HAND(INDEX) "hand-[INDEX]"
// todo: sprite accessories list system
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index d36121bd886e..e31d3a0b9884 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -34,15 +34,25 @@
#define QDELETED(X) (!X || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
-//Qdel helper macros.
+//* Qdel helper macros. *//
+
+/// qdel something in a specific amount of time. returns a timer ID.
#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE)
+/// qdel something in a specific amount of real (wall) time. returns a timer ID.
#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
+/// qdel's something and nulls it out
#define QDEL_NULL(item) qdel(item); item = null
+/// qdel's all the elements in a list and then nulls the list out.
#define QDEL_NULL_LIST QDEL_LIST_NULL
+/// qdel's all the elements in a list and then nulls the list out.
#define QDEL_LIST_NULL(x) if(x) { for(var/y in x) { qdel(y) } ; x = null }
+/// qdels the elements in a list and proceed to cut the list. in an asosciative list, this will qdelete the keys.
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
+/// QDEL_LIST in a specific amount of time
#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE)
+/// qdel's both the keys and the values of an associative list, and then cut the list.
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
+/// qdel()'s the value associations of an associative list, and then cut the list.
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly.
diff --git a/code/__DEFINES/rendering/ui_style.dm b/code/__DEFINES/rendering/ui_style.dm
index 80db51ba2c63..a4988efa32f4 100644
--- a/code/__DEFINES/rendering/ui_style.dm
+++ b/code/__DEFINES/rendering/ui_style.dm
@@ -5,7 +5,6 @@
#define UI_STYLE_ORANGE "Orange"
#define UI_STYLE_OLD "old"
#define UI_STYLE_WHITE "White"
-#define UI_STYLE_OLD_NOBORDER "old-noborder"
#define UI_STYLE_MINIMALIST "minimalist"
#define UI_STYLE_HOLOGRAM "Hologram"
diff --git a/code/__DEFINES/screen.dm b/code/__DEFINES/screen.dm
index 2d0f4b08802a..a3c302aa4143 100644
--- a/code/__DEFINES/screen.dm
+++ b/code/__DEFINES/screen.dm
@@ -1,3 +1,5 @@
+//* All static screen_loc of UI objects are in here! *//
+
/*
These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
@@ -17,29 +19,37 @@
//! BYOND will 100% allow you to.
//! DO NOT DO THIS.
-#define ui_entire_screen "LEFT,BOTTOM to RIGHT,TOP"
-
-//Lower left, persistant menu
-#define ui_inventory "LEFT:6,BOTTOM:5"
-
-//Lower center, persistant menu
-#define ui_sstore1 "LEFT+2:10,BOTTOM:5"
-#define ui_id "LEFT+3:12,BOTTOM:5"
-#define ui_belt "LEFT+4:14,BOTTOM:5"
-#define ui_back "CENTER-2:14,BOTTOM:5"
-#define ui_rhand "CENTER-1:16,BOTTOM:5"
-#define ui_lhand "CENTER:16,BOTTOM:5"
-#define ui_equip "CENTER-1:16,BOTTOM+1:5"
-#define ui_swaphand1 "CENTER-1:16,BOTTOM+1:5"
-#define ui_swaphand2 "CENTER:16,BOTTOM+1:5"
-#define ui_storage1 "CENTER+1:16,BOTTOM:5"
-#define ui_storage2 "CENTER+2:16,BOTTOM:5"
+//* General HUD positions *//
+//* These should be as widescreen-agnostic as possible. *//
+
+/// Fill screen
+#define SCREEN_LOC_FULLSCREEN "LEFT,BOTTOM to RIGHT,TOP"
+
+//* Mob HUD positions *//
+//* These should be widescreen-agnostic and use anchorings *//
+//* to the sides of the screen / center. *//
+
+//* Mob HUD - Inventory *//
+
+/// screen loc for a hand index
+#define SCREEN_LOC_MOB_HUD_INVENTORY_HAND(HAND) "CENTER[index % 2? "" : "-1"]:16,BOTTOM[index < 2? "" : "+[(round(index / 2) - 1)]"]:5"
+/// screen loc for hand swap button for a given number of hands
+#define SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(TOTAL_HANDS) "CENTER-1:28,BOTTOM+[ceil(TOTAL_HANDS - 2 / 2)]:5"
+/// screen loc for hand swap button for a given number of hands
+#define SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(TOTAL_HANDS) "CENTER-1:16,BOTTOM+[ceil(TOTAL_HANDS - 2 / 2)]:5"
+/// the bottom-left drawer position of inventory HUD
+#define SCREEN_LOC_MOB_HUD_INVENTORY_DRAWER "LEFT:6,BOTTOM:5"
+/// slot alignment for drawer-anchor
+#define SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_DRAWER_ALIGNED(MAIN_AXIS, CROSS_AXIS) "LEFT+[CROSS_AXIS]:[6 + (CROSS_AXIS * 2)],BOTTOM+[MAIN_AXIS]:[5 + (MAIN_AXIS * 2)]"
+/// slot alignment for hand-anchor
+#define SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_HANDS_ALIGNED(MAIN_AXIS, CROSS_AXIS) "CENTER-1:[16 + (MAIN_AXIS > 0 ? (32 * (MAIN_AXIS + 1)) : (32 * MAIN_AXIS))],BOTTOM+[CROSS_AXIS]:[5 + (CROSS_AXIS * 2)]"
+
+//! < legacy stuff below > !//
+
+/// Hands
+
#define ui_smallquad "RIGHT-4:18,BOTTOM:4"
-///aliens
-#define ui_alien_head "CENTER-3:12,BOTTOM:5"
-///aliens
-#define ui_alien_oclothing "CENTER-2:14,BOTTOM:5"
///borgs
#define ui_inv1 "CENTER-1,BOTTOM:5"
///borgs
@@ -50,10 +60,6 @@
#define ui_borg_store "CENTER+2,BOTTOM:5"
///borgs
#define ui_borg_inventory "CENTER-2,BOTTOM:5"
-///monkey
-#define ui_monkey_mask "LEFT+4:14,BOTTOM:5"
-///monkey
-#define ui_monkey_back "LEFT+5:14,BOTTOM:5"
///same height as humans, hugging the right border
#define ui_construct_health "RIGHT:00,CENTER:15"
#define ui_construct_purge "RIGHT:00,CENTER-1:15"
@@ -132,20 +138,6 @@
//#define ui_wiz_instability_display "RIGHT-2:28,CENTER-3:15"
#define ui_wiz_instability_display "RIGHT-1:28,TOP-2:27"
-//Pop-up inventory
-#define ui_shoes "LEFT+1:8,BOTTOM:5"
-
-#define ui_iclothing "LEFT:6,BOTTOM+1:7"
-#define ui_oclothing "LEFT+1:8,BOTTOM+1:7"
-#define ui_gloves "LEFT+2:10,BOTTOM+1:7"
-
-#define ui_glasses "LEFT:6,BOTTOM+2:9"
-#define ui_mask "LEFT+1:8,BOTTOM+2:9"
-#define ui_l_ear "LEFT+2:10,BOTTOM+2:9"
-#define ui_r_ear "LEFT+2:10,BOTTOM+3:11"
-
-#define ui_head "LEFT+1:8,BOTTOM+3:11"
-
//Intent small buttons
#define ui_help_small "RIGHT-3:8,BOTTOM:1"
#define ui_disarm_small "RIGHT-3:15,BOTTOM:18"
@@ -154,17 +146,9 @@
//#define ui_swapbutton "6:-16,1:5" //Unused
-//#define ui_headset "BOTTOM,8"
-#define ui_hand "CENTER-1:14,BOTTOM:5"
-#define ui_hstore1 "CENTER-2,CENTER-2"
-//#define ui_resist "RIGHT+1,BOTTOM-1"
#define ui_sleep "RIGHT+1,TOP-13"
#define ui_rest "RIGHT+1,TOP-14"
-
-#define ui_iarrowleft "BOTTOM-1,RIGHT-4"
-#define ui_iarrowright "BOTTOM-1,RIGHT-2"
-
#define ui_spell_master "RIGHT-1:16,TOP-1:16"
#define ui_genetic_master "RIGHT-1:16,TOP-2:16"
#define ui_ability_master "RIGHT-1:16,TOP-3:16"
diff --git a/code/__HELPERS/lists/unsorted/pack_2d_flat_list.dm b/code/__HELPERS/lists/unsorted/pack_2d_flat_list.dm
new file mode 100644
index 000000000000..e8a08ce9a9ec
--- /dev/null
+++ b/code/__HELPERS/lists/unsorted/pack_2d_flat_list.dm
@@ -0,0 +1,38 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * puts something into the smallest inner list of a list of lists.
+ *
+ * three `list`'s in one sentence, yippee!
+ *
+ * * `inserting` can be a single element or a list of elements
+ *
+ * @return
+ * * outer_list - outer list reference
+ * * inserting - an element or a list of elements to insert
+ * * put_at_end - pack into end, rather than start of list
+ * * length_limit - max length to pack lists to
+ * * out_packed - if exists, packed elements get put here
+ * * out_leftovers - if exists, elements that couldn't go in due to length limit go in here
+ */
+/proc/pack_2d_flat_list(list/outer_list, list/inserting, put_at_end = TRUE, length_limit, list/out_packed, list/out_leftovers)
+ // todo: optimize algorithm
+ inserting = islist(inserting) ? inserting.Copy() : list(inserting)
+ for(var/i in 1 to length(inserting))
+ var/elem = inserting[i]
+ var/list/smallest
+ var/smallest_length = INFINITY
+ for(var/j in 1 to length(outer_list))
+ var/their_length = length(outer_list[j])
+ if(their_length < smallest_length && their_length < length_limit)
+ smallest = outer_list[j]
+ if(!smallest)
+ out_leftovers?.Add(inserting.Copy(i))
+ out_packed?.Add(inserting.Copy(1, i))
+ return
+ if(put_at_end)
+ smallest.Add(elem)
+ else
+ smallest.Insert(1, elem)
+ out_packed?.Add(inserting)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 33a67bc10f00..96cb0f30f932 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -406,7 +406,8 @@
var/list/creatures = list()
var/list/namecounts = list()
for(var/mob/M in mobs)
- if(isobserver(M) && ghostfollow && M.client.is_under_stealthmin() && M.get_preference_toggle(/datum/game_preference_toggle/admin/stealth_hides_ghost))
+ // todo: stealthmin will **break** when they're logged out. we don't want this! it's a hard tell.
+ if(isobserver(M) && ghostfollow && M.client?.is_under_stealthmin() && M.get_preference_toggle(/datum/game_preference_toggle/admin/stealth_hides_ghost))
continue
var/name = M.name
if (name in names)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index a7fbbe9abaa5..44f7944a2b98 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -40,15 +40,17 @@ SUBSYSTEM_DEF(statpanels)
// grab victim
var/client/player = currentrun[length(currentrun)]
--currentrun.len
- // check if we're even on the js one
- if(player.tgui_stat.byond_stat_active)
- continue
// check if ready
- if(!player.tgui_stat.ready)
+ // this is not a client initialized check, it's a "exists and ready" check.
+ // we intentionally don't wait for full init
+ if(!player.tgui_stat?.ready)
continue
- // check listed turf
+ // check listed turf, even if we're on JS stat
if(player.tgui_stat.byond_stat_turf && !player.list_turf_check(player.tgui_stat.byond_stat_turf))
player.unlist_turf()
+ // check if we're even on the js one
+ if(player.tgui_stat.byond_stat_active)
+ continue
// are they an admin?
var/is_admin = !!player.holder
// grab their mob data
diff --git a/code/datums/ability.dm b/code/datums/ability.dm
index ef0c7726b307..8ea070728154 100644
--- a/code/datums/ability.dm
+++ b/code/datums/ability.dm
@@ -273,7 +273,7 @@
return FALSE
if((ability_check_flags & ABILITY_CHECK_STANDING) && IS_PRONE(owner))
return FALSE
- if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(owner.has_free_hand()))
+ if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(!owner.are_usable_hands_full()))
return FALSE
if((ability_check_flags & ABILITY_CHECK_RESTING) && !IS_PRONE(owner))
return FALSE
@@ -293,7 +293,7 @@
return "You cannot do that while unconscious."
if((ability_check_flags & ABILITY_CHECK_STANDING) && owner.lying)
return "You cannot do that while on the ground."
- if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(owner.has_free_hand()))
+ if((ability_check_flags & ABILITY_CHECK_FREE_HAND) && !(!owner.are_usable_hands_full()))
return "You cannot do that without a free hand."
if((ability_check_flags & ABILITY_CHECK_RESTING) && !owner.lying)
return "You must be lying down to do that."
diff --git a/code/datums/components/items/wielding.dm b/code/datums/components/items/wielding.dm
index 881d8828bcfb..de96fcbb4c37 100644
--- a/code/datums/components/items/wielding.dm
+++ b/code/datums/components/items/wielding.dm
@@ -45,7 +45,7 @@
/datum/component/wielding/proc/wield(mob/wielder)
if(src.wielder)
return
- var/possible = wielder.get_number_of_hands()
+ var/possible = wielder.get_nominal_hand_count()
var/wanted = hands - 1
if(possible < wanted)
return
diff --git a/code/datums/components/riding/riding_filter.dm b/code/datums/components/riding/riding_filter.dm
index 8ab7fcbf0116..adb3518c9fa4 100644
--- a/code/datums/components/riding/riding_filter.dm
+++ b/code/datums/components/riding/riding_filter.dm
@@ -210,7 +210,7 @@
ASSERT(islist(offhands))
var/amount_needed = rider_offhands_needed(rider, semantic)
if(!offhand_requirements_are_rigid)
- amount_needed = min(amount_needed, rider.get_number_of_hands())
+ amount_needed = min(amount_needed, rider.get_nominal_hand_count())
if(!amount_needed)
return TRUE
for(var/i in 1 to amount_needed)
diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm
index 32ec7f0bdae5..50c24f7ed3ef 100644
--- a/code/datums/event_args/clickchain.dm
+++ b/code/datums/event_args/clickchain.dm
@@ -11,6 +11,10 @@
var/intent
/// optional: click params
var/list/params
+ /// optional: hand index, if any
+ var/hand_index
+ /// with item, if any
+ var/obj/item/using
/// optional: target atom
var/atom/target
diff --git a/code/datums/event_args/event_args.dm b/code/datums/event_args/event_args.dm
new file mode 100644
index 000000000000..d7481f2535f5
--- /dev/null
+++ b/code/datums/event_args/event_args.dm
@@ -0,0 +1,2 @@
+/datum/event_args
+ abstract_type = /datum/event_args
diff --git a/code/datums/unarmed_attack.dm b/code/datums/unarmed_attack.dm
index d79226557072..bf61721472c9 100644
--- a/code/datums/unarmed_attack.dm
+++ b/code/datums/unarmed_attack.dm
@@ -91,16 +91,18 @@ GLOBAL_LIST_EMPTY(unarmed_attack_cache)
target.visible_message("[target] looks momentarily disoriented.", "You see stars.")
target.apply_effect(attack_damage*2, EYE_BLUR, armour)
if(BP_L_ARM, BP_L_HAND)
- if (target.l_hand)
+ var/obj/item/knocked_away = target.get_left_held_item()
+ if (knocked_away)
// Disarm left hand
//Urist McAssistant dropped the macguffin with a scream just sounds odd.
- target.visible_message("\The [target.l_hand] was knocked right out of [target]'s grasp!")
- target.drop_left_held_item()
+ target.visible_message("\The [knocked_away] was knocked right out of [target]'s grasp!")
+ target.drop_item_to_ground(knocked_away)
if(BP_R_ARM, BP_R_HAND)
- if (target.r_hand)
+ var/obj/item/knocked_away = target.get_left_held_item()
+ if (knocked_away)
// Disarm right hand
- target.visible_message("\The [target.r_hand] was knocked right out of [target]'s grasp!")
- target.drop_right_held_item()
+ target.visible_message("\The [knocked_away] was knocked right out of [target]'s grasp!")
+ target.drop_item_to_ground(knocked_away)
if(BP_TORSO)
if(!target.lying)
var/turf/T = get_step(get_turf(target), get_dir(get_turf(user), get_turf(target)))
diff --git a/code/datums/weakref.dm b/code/datums/weakref.dm
index e4e244587a44..617cc0c3bca1 100644
--- a/code/datums/weakref.dm
+++ b/code/datums/weakref.dm
@@ -1,4 +1,9 @@
+/**
+ * * Returns a /datum/weakref if input is a datum, and not undergoing GC
+ * * Returns null otherwise.
+ */
/proc/WEAKREF(datum/input)
+ RETURN_TYPE(/datum/weakref)
if(istype(input) && !QDELETED(input))
if(istype(input, /datum/weakref))
return input
diff --git a/code/game/atoms/_atom.dm b/code/game/atoms/_atom.dm
index 5e8c9590a917..47b903cde761 100644
--- a/code/game/atoms/_atom.dm
+++ b/code/game/atoms/_atom.dm
@@ -913,16 +913,12 @@
// base layer being null isn't
layer = base_layer + 0.001 * relative_layer
+// todo: deprecate this
/atom/proc/hud_layerise()
plane = INVENTORY_PLANE
set_base_layer(HUD_LAYER_ITEM)
// appearance_flags |= NO_CLIENT_COLOR
-/atom/proc/hud_unlayerise()
- plane = initial(plane)
- set_base_layer(initial(layer))
- // appearance_flags &= ~(NO_CLIENT_COLOR)
-
/atom/proc/reset_plane_and_layer()
plane = initial(plane)
set_base_layer(initial(layer))
diff --git a/code/game/click/click.dm b/code/game/click/click.dm
index 9d84d31a54fe..e8062a73f741 100644
--- a/code/game/click/click.dm
+++ b/code/game/click/click.dm
@@ -1,7 +1,3 @@
-/*
- Click code cleanup
- ~Sayu
-*/
/*
Before anything else, defer these calls to a per-mobtype handler. This allows us to
@@ -17,22 +13,18 @@
if(!(atom_flags & ATOM_INITIALIZED))
to_chat(usr, SPAN_WARNING("[type] initialization failure. Click dropped. Contact a coder or admin."))
return
- if(src)
- SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr)
- usr.ClickOn(src, params)
+ SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr)
+ usr.ClickOn(src, params)
/atom/DblClick(var/location, var/control, var/params)
if(!(atom_flags & ATOM_INITIALIZED))
to_chat(usr, SPAN_WARNING("[type] initialization failure. Click dropped. Contact a coder or admin."))
return
- if(src)
- usr.DblClickOn(src, params)
+ usr.DblClickOn(src, params)
/atom/MouseWheel(delta_x,delta_y,location,control,params)
usr.MouseWheelOn(src, delta_x, delta_y, params)
-
-
/**
* click handling entrypoint
*
diff --git a/code/game/click/context.dm b/code/game/click/context.dm
index e9ab863450c5..72d679aa17f7 100644
--- a/code/game/click/context.dm
+++ b/code/game/click/context.dm
@@ -31,8 +31,6 @@
*/
/atom/proc/context_menu(datum/event_args/actor/e_args)
set waitfor = FALSE
- // admin proccall support
- E_ARGS_WRAP_USER_TO_ACTOR(e_args)
// todo: dynamically rebuild menu based on distance?
var/client/receiving = e_args.initiator.client
if(isnull(receiving))
diff --git a/code/game/click/mobs.dm b/code/game/click/mobs.dm
index 3f0f13fb0bb3..ab66aae11d88 100644
--- a/code/game/click/mobs.dm
+++ b/code/game/click/mobs.dm
@@ -125,3 +125,17 @@
/mob/proc/melee_attack_finalize(atom/target, datum/event_args/actor/clickchain/clickchain, datum/unarmed_attack/style, clickchain_flags, target_zone, mult)
return NONE
+
+/**
+ * construct default event args for what we're doing to a target
+ */
+/mob/proc/default_clickchain_event_args(atom/target, unarmed = FALSE)
+ var/datum/event_args/actor/clickchain/constructed = new
+ constructed.initiator = src
+ constructed.performer = src
+ constructed.target = target
+ constructed.params = list()
+ constructed.intent = a_intent
+ constructed.hand_index = active_hand
+ if(!unarmed)
+ constructed.using = get_active_held_item()
diff --git a/code/game/click/other_mobs.dm b/code/game/click/other_mobs.dm
index 55a50d5cb69d..a18e5297afd2 100644
--- a/code/game/click/other_mobs.dm
+++ b/code/game/click/other_mobs.dm
@@ -33,7 +33,7 @@
/atom/proc/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
// todo: remove
if(isnull(e_args))
- e_args = new(user)
+ e_args = user.default_clickchain_event_args(src, TRUE)
// end
if(on_attack_hand(e_args))
return TRUE
diff --git a/code/game/content/factions/orion/iwl/guns.dm b/code/game/content/factions/orion/iwl/guns.dm
index 046d1c9f3b50..4b8531d49350 100644
--- a/code/game/content/factions/orion/iwl/guns.dm
+++ b/code/game/content/factions/orion/iwl/guns.dm
@@ -19,7 +19,7 @@
/obj/item/gun/ballistic/automatic/k25/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/k25/update_icon_state()
. = ..()
diff --git a/code/game/gamemodes/changeling/generic_equip_procs.dm b/code/game/gamemodes/changeling/generic_equip_procs.dm
index b734ecec36bd..600dbfba8bd4 100644
--- a/code/game/gamemodes/changeling/generic_equip_procs.dm
+++ b/code/game/gamemodes/changeling/generic_equip_procs.dm
@@ -243,7 +243,7 @@
var/mob/living/carbon/human/M = src
- if(M.hands_full()) //Make sure our hands aren't full.
+ if(M.are_usable_hands_full()) //Make sure our hands aren't full.
to_chat(src, SPAN_WARNING("Our hands are full. Drop something first."))
return 0
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 13fc46b27a09..4c5775b16454 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -20,15 +20,13 @@
return ..()
if(!isliving(user))
return ..()
- var/mob/living/L = user
- var/zone = (L.hand ? "l_arm":"r_arm")
if(ishuman(user))
var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/affecting = H.get_organ(zone)
+ var/obj/item/organ/external/affecting = H.get_hand_organ(held_index)
to_chat(user, "An inexplicable force rips through your [affecting.name], tearing the sword from your grasp!")
//random amount of damage between half of the blade's force and the full force of the blade.
- H.apply_damage(rand(damage_force/2, damage_force), DAMAGE_TYPE_BRUTE, zone, 0, sharp=1, edge=1)
+ H.apply_damage(rand(damage_force/2, damage_force), DAMAGE_TYPE_BRUTE, held_index % 2? BP_L_HAND : BP_R_HAND, 0, sharp=1, edge=1)
H.afflict_paralyze(20 * 5)
else if(!istype(user, /mob/living/simple_mob/construct))
to_chat(user, "An inexplicable force rips through you, tearing the sword from your grasp!")
diff --git a/code/game/gamemodes/technomancer/spell_objs.dm b/code/game/gamemodes/technomancer/spell_objs.dm
index 436fee7af441..2e1b00e7822c 100644
--- a/code/game/gamemodes/technomancer/spell_objs.dm
+++ b/code/game/gamemodes/technomancer/spell_objs.dm
@@ -176,23 +176,32 @@
// Parameters: 0
// Description: Terrible code to check if a scepter is in the offhand, returns 1 if yes.
/obj/item/spell/proc/check_for_scepter()
- if(!src || !owner) return 0
- if(owner.r_hand == src)
- if(istype(owner.l_hand, /obj/item/scepter))
- return 1
- else
- if(istype(owner.r_hand, /obj/item/scepter))
- return 1
- return 0
+ if(isnull(owner))
+ return FALSE
+ var/our_index = owner.get_held_index(src)
+ if(!our_index)
+ return FALSE
+ for(var/i in 1 to length(owner.inventory.held_items))
+ if(i == our_index)
+ continue
+ if(!istype(owner.inventory.held_items[i], /obj/item/scepter))
+ continue
+ return TRUE
+ return FALSE
// Proc: get_other_hand()
// Parameters: 1 (I - item being compared to determine what the offhand is)
// Description: Helper for Aspect spells.
/mob/living/carbon/human/proc/get_other_hand(var/obj/item/I)
- if(r_hand == I)
- return l_hand
- else
- return r_hand
+ var/our_index = get_held_index(src)
+ if(!our_index)
+ return FALSE
+ for(var/i in 1 to length(inventory.held_items))
+ if(i == our_index)
+ continue
+ if(isnull(inventory.held_items[i]))
+ continue
+ return inventory.held_items[i]
// Proc: attack_self()
// Parameters: 1 (user - the Technomancer that invoked this proc)
@@ -263,6 +272,9 @@
if(S.run_checks())
S.on_innate_cast(src)
+ // todo: shitcode, doesn't properly support multihanding
+ var/obj/item/l_hand = get_left_held_item()
+ var/obj/item/r_hand = get_right_held_item()
if(l_hand && r_hand) //Make sure our hands aren't full.
if(istype(r_hand, /obj/item/spell)) //If they are full, perhaps we can still be useful.
var/obj/item/spell/r_spell = r_hand
diff --git a/code/game/gamemodes/technomancer/spells/apportation.dm b/code/game/gamemodes/technomancer/spells/apportation.dm
index 998ba0f432f9..356f37350353 100644
--- a/code/game/gamemodes/technomancer/spells/apportation.dm
+++ b/code/game/gamemodes/technomancer/spells/apportation.dm
@@ -73,6 +73,5 @@
G.state = GRAB_PASSIVE
G.icon_state = "grabbed1"
- G.synch()
qdel(src)
diff --git a/code/game/gamemodes/technomancer/spells/gambit.dm b/code/game/gamemodes/technomancer/spells/gambit.dm
index 32fc16212bd1..b5908fbb9c6b 100644
--- a/code/game/gamemodes/technomancer/spells/gambit.dm
+++ b/code/game/gamemodes/technomancer/spells/gambit.dm
@@ -95,7 +95,7 @@
if(is_ally(H)) // Don't get scared by our apprentice.
continue
- for(var/obj/item/I in list(H.l_hand, H.r_hand))
+ for(var/obj/item/I in H.get_held_items())
// Guns are scary.
if(istype(I, /obj/item/gun)) // Toy guns will count as well but oh well.
hostile_mobs++
diff --git a/code/game/objects/effects/debris/cleanable/humans.dm b/code/game/objects/effects/debris/cleanable/humans.dm
index f5c9662d3533..4a50bb4cc4a4 100644
--- a/code/game/objects/effects/debris/cleanable/humans.dm
+++ b/code/game/objects/effects/debris/cleanable/humans.dm
@@ -1,5 +1,4 @@
-
/obj/effect/debris/cleanable/mucus
name = "mucus"
desc = "Disgusting mucus."
diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/items-interaction.dm
index 6a7ec7d7eec9..f5a298b4ed1e 100644
--- a/code/game/objects/items-interaction.dm
+++ b/code/game/objects/items-interaction.dm
@@ -51,16 +51,17 @@
user.action_feedback(SPAN_WARNING("You can't do that right now."), src)
return
- // todo: rewrite this part iin hand rewrite
if (hasorgans(user))
var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name[H.hand? "l_hand" : "r_hand"]
+ var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
+ if (H.active_hand % 2)
+ temp = H.organs_by_name["l_hand"]
+ if(temp && !temp.is_usable())
+ to_chat(user, "You try to move your [temp.name], but cannot!")
+ return
if(!temp)
to_chat(user, "You try to use your hand, but realize it is no longer attached!")
return
- if(!temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
var/old_loc = src.loc
var/obj/item/actually_picked_up = src
@@ -88,7 +89,7 @@
if(isnull(actually_picked_up))
to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?"))
return
- if(!user.put_in_hands(actually_picked_up))
+ if(!user.put_in_hands(actually_picked_up, user.active_hand))
if(has_to_drop_to_ground_on_fail)
actually_picked_up.forceMove(user.drop_location())
return
@@ -107,19 +108,20 @@
if(user.restrained())
return // don't.
// todo: restraint levels, e.g. handcuffs vs straightjacket
+ // todo: this needs to check for user / actor indirection bullshit (e.g. someone does the clickdragging while controlling another mob)
if(!user.is_in_inventory(src))
// not being held
if(!isturf(loc)) // yea nah
return ..()
if(user.Adjacent(src))
// check for equip
- if(istype(over, /atom/movable/screen/inventory/hand))
- var/atom/movable/screen/inventory/hand/H = over
- user.put_in_hand(src, H.index)
+ if(istype(over, /atom/movable/screen/actor_hud/inventory/plate/hand))
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/H = over
+ user.put_in_hand(src, H.hand_index)
return CLICKCHAIN_DO_NOT_PROPAGATE
- else if(istype(over, /atom/movable/screen/inventory/slot))
- var/atom/movable/screen/inventory/slot/S = over
- user.equip_to_slot_if_possible(src, S.slot_id)
+ else if(istype(over, /atom/movable/screen/actor_hud/inventory/plate/slot))
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/S = over
+ user.equip_to_slot_if_possible(src, S.inventory_slot_id)
return CLICKCHAIN_DO_NOT_PROPAGATE
// check for slide
if(Adjacent(over) && user.CanSlideItem(src, over) && (istype(over, /obj/structure/table/rack) || istype(over, /obj/structure/table) || istype(over, /turf)))
@@ -137,13 +139,13 @@
return CLICKCHAIN_DO_NOT_PROPAGATE
else
// being held, check for attempt unequip
- if(istype(over, /atom/movable/screen/inventory/hand))
- var/atom/movable/screen/inventory/hand/H = over
- user.put_in_hand(src, H.index)
+ if(istype(over, /atom/movable/screen/actor_hud/inventory/plate/hand))
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/H = over
+ user.put_in_hand(src, H.hand_index)
return CLICKCHAIN_DO_NOT_PROPAGATE
- else if(istype(over, /atom/movable/screen/inventory/slot))
- var/atom/movable/screen/inventory/slot/S = over
- user.equip_to_slot_if_possible(src, S.slot_id)
+ else if(istype(over, /atom/movable/screen/actor_hud/inventory/plate/slot))
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/S = over
+ user.equip_to_slot_if_possible(src, S.inventory_slot_id)
return CLICKCHAIN_DO_NOT_PROPAGATE
else if(istype(over, /turf))
user.drop_item_to_ground(src)
@@ -185,6 +187,8 @@
/obj/item/proc/attack_self(mob/user, datum/event_args/actor/actor)
// todo: this should realistically be SHOULD_NOT_OVERRIDE but there's a massive number of overrides (some unnecessary), so this is for a later date
// SHOULD_NOT_OVERRIDE(TRUE) // may be re-evaluated later
+ if(isnull(actor))
+ actor = new /datum/event_args/actor(user)
SEND_SIGNAL(src, COMSIG_ITEM_ACTIVATE_INHAND, actor)
if(on_attack_self(actor))
return TRUE
diff --git a/code/game/objects/items-inventory-hooks.dm b/code/game/objects/items-inventory-hooks.dm
new file mode 100644
index 000000000000..c8ff5b4d889e
--- /dev/null
+++ b/code/game/objects/items-inventory-hooks.dm
@@ -0,0 +1,31 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// doMove hook to ensure proper functionality when inv procs aren't called
+/obj/item/doMove(atom/destination)
+ if(worn_slot && !worn_hook_suppressed)
+ // inventory handling
+ if(destination == worn_inside)
+ return ..()
+ var/mob/M = worn_mob()
+ if(!ismob(M))
+ worn_slot = null
+ worn_hook_suppressed = FALSE
+ stack_trace("item forcemove inv hook called without a mob as loc??")
+ M.temporarily_remove_from_inventory(src, INV_OP_FORCE)
+ return ..()
+
+// todo: this is fucking awful
+/obj/item/Move(atom/newloc, direct, glide_size_override)
+ if(!worn_slot)
+ return ..()
+ var/mob/M = worn_mob()
+ if(istype(M))
+ M.temporarily_remove_from_inventory(src, INV_OP_FORCE)
+ else
+ stack_trace("item Move inv hook called without a mob as loc??")
+ worn_slot = null
+ . = ..()
+ if(!. || (loc == M))
+ // kick them out
+ forceMove(M.drop_location())
diff --git a/code/modules/mob/inventory/rendering.dm b/code/game/objects/items-inventory-rendering.dm
similarity index 94%
rename from code/modules/mob/inventory/rendering.dm
rename to code/game/objects/items-inventory-rendering.dm
index 25f9983f65ec..d28e70515cc5 100644
--- a/code/modules/mob/inventory/rendering.dm
+++ b/code/game/objects/items-inventory-rendering.dm
@@ -1,5 +1,10 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/**
* Item rendering system
+ * Procs in here can be called and overridden as needed, but you should know what you're doing
+ * if you choose to do so!
*
* * IF YOU ONLY CARE ABOUT MAKING A NEW ITEM OR ARE CONVERTING AN ITEM, JUST READ THIS!!
*
@@ -240,6 +245,47 @@
CONSTRUCT_BODYTYPES(worn_bodytypes_invisible)
CONSTRUCT_BODYTYPES(worn_bodytypes_fallback)
+/**
+ * update our worn icon if we can
+ */
+/obj/item/proc/update_worn_icon()
+ if(!worn_slot)
+ return // acceptable
+ var/mob/M = worn_mob()
+ ASSERT(M) // not acceptable
+ if(held_index)
+ M.update_inv_hand(held_index)
+ return
+ switch(worn_slot)
+ if(SLOT_ID_BACK)
+ M.update_inv_back()
+ if(SLOT_ID_BELT)
+ M.update_inv_belt()
+ if(SLOT_ID_GLASSES)
+ M.update_inv_glasses()
+ if(SLOT_ID_GLOVES)
+ M.update_inv_gloves()
+ if(SLOT_ID_HANDCUFFED)
+ M.update_inv_handcuffed()
+ if(SLOT_ID_HANDS)
+ CRASH("why did we go here when we should have short-circuited at the held_index check?")
+ if(SLOT_ID_HEAD)
+ M.update_inv_head()
+ if(SLOT_ID_LEFT_EAR, SLOT_ID_RIGHT_EAR)
+ M.update_inv_ears()
+ if(SLOT_ID_MASK)
+ M.update_inv_wear_mask()
+ if(SLOT_ID_SHOES)
+ M.update_inv_shoes()
+ if(SLOT_ID_SUIT)
+ M.update_inv_wear_suit()
+ if(SLOT_ID_SUIT_STORAGE)
+ M.update_inv_s_store()
+ if(SLOT_ID_UNIFORM)
+ M.update_inv_w_uniform()
+ if(SLOT_ID_WORN_ID)
+ M.update_inv_wear_id()
+
/**
* Renders either a list, or a single image or mutable appearance of what we should be applied to a mob with.
*
diff --git a/code/modules/mob/inventory/items.dm b/code/game/objects/items-inventory.dm
similarity index 76%
rename from code/modules/mob/inventory/items.dm
rename to code/game/objects/items-inventory.dm
index fd57ebf81385..fb41848ddf32 100644
--- a/code/modules/mob/inventory/items.dm
+++ b/code/game/objects/items-inventory.dm
@@ -1,27 +1,7 @@
-/obj/item
- /// currently equipped slot id
- var/worn_slot
- /**
- * current item we fitted over
- * ! DANGER: While this is more or less bug-free for "won't lose the item when you unequip/won't get stuck", we
- * ! do not promise anything for functionality - this is a SNOWFLAKE SYSTEM.
- */
- var/obj/item/worn_over
- /**
- * current item we're fitted in.
- */
- var/obj/item/worn_inside
- /// suppress auto inventory hooks in forceMove
- var/worn_hook_suppressed = FALSE
-
-/obj/item/Destroy()
- if(worn_slot && !worn_hook_suppressed)
- var/mob/M = worn_mob()
- if(!ismob(M))
- stack_trace("invalid current equipped slot [worn_slot] on an item not on a mob.")
- return ..()
- M.temporarily_remove_from_inventory(src, INV_OP_FORCE | INV_OP_DELETING)
- return ..()
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Hooks *//
/**
* called when an item is equipped to inventory or picked up
@@ -49,16 +29,8 @@
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot, flags)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, src, slot, flags)
- if(!(flags & INV_OP_IS_ACCESSORY))
- // todo: shouldn't be in here
- hud_layerise()
- // todo: shouldn't be in here
- if(user)
- user.position_hud_item(src, slot)
- user.client?.screen |= src
if((slot != SLOT_ID_HANDS) && equip_sound)
playsound(src, equip_sound, 30, ignore_walls = FALSE)
- user.update_inv_hands()
/**
@@ -85,12 +57,6 @@
SEND_SIGNAL(src, COMSIG_ITEM_UNEQUIPPED, user, slot, flags)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_UNEQUIPPED, src, slot, flags)
- if(!(flags & INV_OP_IS_ACCESSORY))
- // todo: shouldn't be in here
- hud_unlayerise()
- // todo: shouldn't be in here
- screen_loc = null
- user?.client?.screen -= src
if(!(flags & INV_OP_DIRECTLY_DROPPING) && (slot != SLOT_ID_HANDS) && unequip_sound)
playsound(src, unequip_sound, 30, ignore_walls = FALSE)
@@ -107,6 +73,7 @@
// unset things
item_flags &= ~ITEM_IN_INVENTORY
+ vis_flags &= ~(VIS_INHERIT_LAYER | VIS_INHERIT_PLANE)
// clear carry
if(isliving(user))
var/mob/living/L = user
@@ -125,7 +92,6 @@
user.unregister_shieldcall(shieldcall)
//! LEGACY
- hud_unlayerise()
if(!(flags & INV_OP_SUPPRESS_SOUND) && isturf(newLoc) && !(. & COMPONENT_ITEM_DROPPED_SUPPRESS_SOUND))
playsound(src, drop_sound, 30, ignore_walls = FALSE)
// user?.update_equipment_speed_mods()
@@ -153,10 +119,12 @@
// set things
item_flags |= ITEM_IN_INVENTORY
+ vis_flags |= (VIS_INHERIT_LAYER | VIS_INHERIT_PLANE)
// we load the component here as it hooks equipped,
// so loading it here means it can still handle the equipped signal.
if(passive_parry)
LoadComponent(/datum/component/passive_parry, passive_parry)
+
// load action buttons
register_item_actions(user)
// register carry
@@ -174,7 +142,6 @@
//! LEGACY
reset_pixel_offsets()
- hud_layerise()
// todo: should this be here
transform = null
if(isturf(oldLoc) && !(flags & (INV_OP_SILENT | INV_OP_DIRECTLY_EQUIPPING)))
@@ -185,54 +152,79 @@
SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user, flags, oldLoc)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_PICKUP, src, flags, oldLoc)
+//* Access *//
+
+/**
+ * get the mob we're equipped on
+ */
+/obj/item/proc/worn_mob()
+ RETURN_TYPE(/mob)
+ return worn_inside?.worn_mob() || (worn_slot? loc : null)
+
/**
- * update our worn icon if we can
+ * checks if we're in inventory. if so, returns mob we're in
+ * **hands count**
+ */
+/obj/item/proc/is_in_inventory(include_hands)
+ return (worn_slot && ((worn_slot != SLOT_ID_HANDS) || include_hands)) && worn_mob()
+
+/**
+ * checks if we're held in hand
+ *
+ * if so, returns mob we're in
*/
-/obj/item/proc/update_worn_icon()
+/obj/item/proc/is_held()
+ return (worn_slot == SLOT_ID_HANDS)? worn_mob() : null
+
+/**
+ * checks if we're worn. if so, return mob we're in
+ *
+ * note: this is not the same as is_in_inventory, we check if it's a clothing/worn slot in this case!
+ */
+/obj/item/proc/is_being_worn()
if(!worn_slot)
- return // acceptable
- var/mob/M = worn_mob()
- ASSERT(M) // not acceptable
- switch(worn_slot)
- if(SLOT_ID_BACK)
- M.update_inv_back()
- if(SLOT_ID_BELT)
- M.update_inv_belt()
- if(SLOT_ID_GLASSES)
- M.update_inv_glasses()
- if(SLOT_ID_GLOVES)
- M.update_inv_gloves()
- if(SLOT_ID_HANDCUFFED)
- M.update_inv_handcuffed()
- if(SLOT_ID_HANDS)
- M.update_inv_hands()
- if(SLOT_ID_HEAD)
- M.update_inv_head()
- if(SLOT_ID_LEFT_EAR, SLOT_ID_RIGHT_EAR)
- M.update_inv_ears()
- if(SLOT_ID_MASK)
- M.update_inv_wear_mask()
- if(SLOT_ID_SHOES)
- M.update_inv_shoes()
- if(SLOT_ID_SUIT)
- M.update_inv_wear_suit()
- if(SLOT_ID_SUIT_STORAGE)
- M.update_inv_s_store()
- if(SLOT_ID_UNIFORM)
- M.update_inv_w_uniform()
- if(SLOT_ID_WORN_ID)
- M.update_inv_wear_id()
+ return FALSE
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(worn_slot)
+ return slot_meta.inventory_slot_flags & INV_SLOT_CONSIDERED_WORN
+
+//* Stripping *//
/**
- * returns either an item or a list
- * get_equipped_items() uses this so accessories are included
+ * get strip menu options by href key associated to name.
*/
-/obj/item/proc/_inv_return_attached()
- if(worn_over)
- return list(src) + worn_over._inv_return_attached()
- else
- return src
+/obj/item/proc/strip_menu_options(mob/user)
+ RETURN_TYPE(/list)
+ return list()
+
+/**
+ * strip menu act
+ *
+ * adjacency is pre-checked.
+ * return TRUE to refresh
+ */
+/obj/item/proc/strip_menu_act(mob/user, action)
+ return FALSE
+/**
+ * standard do after for interacting from strip menu
+ */
+/obj/item/proc/strip_menu_standard_do_after(mob/user, delay)
+ . = FALSE
+ var/slot = worn_slot
+ if(!slot)
+ CRASH("no worn slot")
+ var/mob/M = worn_mob()
+ if(!M)
+ CRASH("no worn mob")
+ if(!M.strip_interaction_prechecks(user))
+ return
+ if(!do_after(user, delay, M, DO_AFTER_IGNORE_ACTIVE_ITEM))
+ return
+ if(slot != worn_slot || M != worn_mob())
+ return
+ return TRUE
+
+//* Checks *//
// todo: item should get final say for "soft" aka not-literal-var-overwrite conflicts.
/**
@@ -302,7 +294,7 @@
return TRUE
/**
- * automatically uneqiup if we're missing beltlink
+ * automatically unequip if we're missing beltlink
*/
/obj/item/proc/reconsider_beltlink()
var/mob/M = loc
@@ -314,6 +306,16 @@
M.drop_item_to_ground(src, INV_OP_SILENT)
return
+//* Speed / Carry Weight *//
+
+/**
+ * get the slowdown we incur when we're worn
+ */
+/obj/item/proc/get_equipment_speed_mod()
+ return slowdown
+
+//* ADVANCED: Wear-Over System *//
+
/**
* checks if we can fit over something
*
@@ -338,105 +340,18 @@
*/
/obj/item/proc/equip_on_worn_over_remove(mob/M, slot, mob/user, obj/item/I, flags)
-/**
- * get the mob we're equipped on
- */
-/obj/item/proc/worn_mob()
- RETURN_TYPE(/mob)
- return worn_inside?.worn_mob() || (worn_slot? loc : null)
-
-// doMove hook to ensure proper functionality when inv procs aren't called
-/obj/item/doMove(atom/destination)
- if(worn_slot && !worn_hook_suppressed)
- // inventory handling
- if(destination == worn_inside)
- return ..()
- var/mob/M = worn_mob()
- if(!ismob(M))
- worn_slot = null
- worn_hook_suppressed = FALSE
- stack_trace("item forcemove inv hook called without a mob as loc??")
- M.temporarily_remove_from_inventory(src, INV_OP_FORCE)
- return ..()
-
-// todo: this is fucking awful
-/obj/item/Move(atom/newloc, direct, glide_size_override)
- if(!worn_slot)
- return ..()
- var/mob/M = worn_mob()
- if(istype(M))
- M.temporarily_remove_from_inventory(src, INV_OP_FORCE)
- else
- stack_trace("item Move inv hook called without a mob as loc??")
- worn_slot = null
- . = ..()
- if(!. || (loc == M))
- // kick them out
- forceMove(M.drop_location())
-
-/**
- * checks if we're in inventory. if so, returns mob we're in
- *
- * **hands count**
- */
-/obj/item/proc/is_in_inventory(include_hands)
- return (worn_slot && ((worn_slot != SLOT_ID_HANDS) || include_hands)) && worn_mob()
+//* ADVANCED: Compound Objects *//
/**
- * checks if we're held in hand
- *
- * if so, returns mob we're in
- *
- * @return the mob holding us
- */
-/obj/item/proc/is_held()
- return (worn_slot == SLOT_ID_HANDS)? worn_mob() : null
-
-/**
- * checks if we're worn. if so, return mob we're in
- *
- * note: this is not the same as is_in_inventory, we check if it's a clothing/worn slot in this case!
- */
-/obj/item/proc/is_being_worn()
- if(!worn_slot)
- return FALSE
- var/datum/inventory_slot/slot_meta = resolve_inventory_slot(worn_slot)
- return slot_meta.inventory_slot_flags & INV_SLOT_CONSIDERED_WORN
-
-/**
- * get strip menu options by href key associated to name.
- */
-/obj/item/proc/strip_menu_options(mob/user)
- RETURN_TYPE(/list)
- return list()
-
-/**
- * strip menu act
- *
- * adjacency is pre-checked.
- * return TRUE to refresh
- */
-/obj/item/proc/strip_menu_act(mob/user, action)
- return FALSE
-
-/**
- * standard do after for interacting from strip menu
+ * returns either an item or a list
+ * get_equipped_items() uses this so accessories are included
+ * anything this returns is considered to be in the same slot.
*/
-/obj/item/proc/strip_menu_standard_do_after(mob/user, delay)
- . = FALSE
- var/slot = worn_slot
- if(!slot)
- CRASH("no worn slot")
- var/mob/M = worn_mob()
- if(!M)
- CRASH("no worn mob")
- if(!M.strip_interaction_prechecks(user))
- return
- if(!do_after(user, delay, M, DO_AFTER_IGNORE_ACTIVE_ITEM))
- return
- if(slot != worn_slot || M != worn_mob())
- return
- return TRUE
+/obj/item/proc/inv_slot_attached()
+ if(worn_over)
+ return list(src) + worn_over.inv_slot_attached()
+ else
+ return src
//* Shieldcall registration
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 04dcd042874b..e4a3ed586c63 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -28,6 +28,18 @@
var/item_action_mobility_flags = MOBILITY_CAN_HOLD | MOBILITY_CAN_USE
//* Combat *//
+ /// Amount of damage we do on melee.
+ var/damage_force = 0
+ /// armor flag for melee attacks
+ var/damage_flag = ARMOR_MELEE
+ /// damage tier
+ var/damage_tier = MELEE_TIER_MEDIUM
+ /// damage_mode bitfield - see [code/__DEFINES/combat/damage.dm]
+ var/damage_mode = NONE
+ /// DAMAGE_TYPE_* enum
+ ///
+ /// * This is the primary damage type this object does on usage as a melee / thrown weapon.
+ var/damage_type = DAMAGE_TYPE_BRUTE
/// passive parry data / frame
///
/// * anonymous typepath is allowed
@@ -38,6 +50,10 @@
/// if this is changed, the component needs to be remade.
var/passive_parry
+ //* Economy
+ /// economic category for items
+ var/economic_category_item = ECONOMIC_CATEGORY_ITEM_DEFAULT
+
//* Flags *//
/// Item flags.
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
@@ -68,9 +84,27 @@
/// These flags are listed in [code/__DEFINES/_flags/interaction_flags.dm]
var/interaction_flags_item = INTERACT_ITEM_ATTACK_SELF
- //? Economy
- /// economic category for items
- var/economic_category_item = ECONOMIC_CATEGORY_ITEM_DEFAULT
+ //* Inventory *//
+ /// currently equipped slot id
+ ///
+ /// todo: `worn_slot_or_index`
+ var/worn_slot
+ /// current hand index, if held in hand
+ ///
+ /// todo: `worn_slot_or_index`
+ var/held_index
+ /**
+ * current item we fitted over
+ * ! DANGER: While this is more or less bug-free for "won't lose the item when you unequip/won't get stuck", we
+ * ! do not promise anything for functionality - this is a SNOWFLAKE SYSTEM.
+ */
+ var/obj/item/worn_over
+ /**
+ * current item we're fitted in.
+ */
+ var/obj/item/worn_inside
+ /// suppress auto inventory hooks in forceMove
+ var/worn_hook_suppressed = FALSE
//* Environmentals *//
/// Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags.
@@ -102,20 +136,6 @@
/// This affects multiplicative movespeed.
var/slowdown = 0
- //? Combat
- /// Amount of damage we do on melee.
- var/damage_force = 0
- /// armor flag for melee attacks
- var/damage_flag = ARMOR_MELEE
- /// damage tier
- var/damage_tier = MELEE_TIER_MEDIUM
- /// damage_mode bitfield - see [code/__DEFINES/combat/damage.dm]
- var/damage_mode = NONE
- /// DAMAGE_TYPE_* enum
- ///
- /// * This is the primary damage type this object does on usage as a melee / thrown weapon.
- var/damage_type = DAMAGE_TYPE_BRUTE
-
//* Storage *//
/// storage cost for volumetric storage
/// null to default to weight class
@@ -199,6 +219,16 @@
else
embed_chance = max(5, round(damage_force/(w_class*3)))
+/obj/item/Destroy()
+ // run inventory hooks
+ if(worn_slot && !worn_hook_suppressed)
+ var/mob/M = worn_mob()
+ if(!ismob(M))
+ stack_trace("invalid current equipped slot [worn_slot] on an item not on a mob.")
+ return ..()
+ M.temporarily_remove_from_inventory(src, INV_OP_FORCE | INV_OP_DELETING)
+ return ..()
+
/// Check if target is reasonable for us to operate on.
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside)))
@@ -207,36 +237,15 @@
return TRUE
/obj/item/proc/update_twohanding()
- update_held_icon()
+ update_worn_icon()
/obj/item/proc/is_held_twohanded(mob/living/M)
- var/check_hand
- if(M.l_hand == src && !M.r_hand)
- check_hand = BP_R_HAND //item in left hand, check right hand
- else if(M.r_hand == src && !M.l_hand)
- check_hand = BP_L_HAND //item in right hand, check left hand
- else
- return FALSE
-
- //would check is_broken() and is_malfunctioning() here too but is_malfunctioning()
- //is probabilistic so we can't do that and it would be unfair to just check one.
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/hand = H.organs_by_name[check_hand]
- if(istype(hand) && hand.is_usable())
- return TRUE
+ for(var/i in M.get_usable_hand_indices())
+ if(!isnull(M.inventory?.held_items[i]))
+ continue
+ return TRUE
return FALSE
-
-///Checks if the item is being held by a mob, and if so, updates the held icons
-/obj/item/proc/update_held_icon()
- if(isliving(src.loc))
- var/mob/living/M = src.loc
- if(M.l_hand == src)
- M.update_inv_l_hand()
- else if(M.r_hand == src)
- M.update_inv_r_hand()
-
/obj/item/legacy_ex_act(severity)
switch(severity)
if(1.0)
diff --git a/code/game/objects/items/bells.dm b/code/game/objects/items/bells.dm
index 39ebae855cbc..8443e530eb05 100644
--- a/code/game/objects/items/bells.dm
+++ b/code/game/objects/items/bells.dm
@@ -67,13 +67,6 @@
/obj/item/deskbell/proc/check_ability(mob/user)
if (ishuman(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(H,"You try to move your [temp.name], but cannot!")
- return 0
return 1
else
to_chat(user,"You are not able to ring [src].")
diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm
index de984ca7587b..c6f15451bbbe 100644
--- a/code/game/objects/items/devices/communicator/phone.dm
+++ b/code/game/objects/items/devices/communicator/phone.dm
@@ -76,7 +76,7 @@
listening_objects |= src
var/atom/movable/screen/blackness = new() //Makes a black screen, so the candidate can't see what's going on before actually 'connecting' to the communicator.
- blackness.screen_loc = ui_entire_screen
+ blackness.screen_loc = SCREEN_LOC_FULLSCREEN
blackness.icon = 'icons/effects/effects.dmi'
blackness.icon_state = "1"
blackness.mouse_opacity = 2 //Can't see anything!
diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm
index 4e05b9fb44b1..3f3d980a0b8a 100644
--- a/code/game/objects/items/devices/defib.dm
+++ b/code/game/objects/items/devices/defib.dm
@@ -223,9 +223,9 @@
make_announcement("beeps, \"Unit is re-energized.\"", "notice")
playsound(src, 'sound/machines/defib_ready.ogg', 50, 0)
-/obj/item/shockpaddles/update_held_icon()
+/obj/item/shockpaddles/update_worn_icon()
var/mob/living/M = loc
- if(istype(M) && M.is_holding(src) && !M.hands_full())
+ if(istype(M) && M.is_holding(src) && !M.are_usable_hands_full())
wielded = 1
name = "[initial(name)] (wielded)"
else
@@ -241,7 +241,7 @@
icon_state = "defibpaddles[wielded]_cooldown"
/obj/item/shockpaddles/proc/can_use(mob/user, mob/M)
- update_held_icon()
+ update_worn_icon()
if(busy)
return 0
if(!check_charge(chargecost))
diff --git a/code/game/objects/items/devices/tvcamera.dm b/code/game/objects/items/devices/tvcamera.dm
index 2c788615cf67..4c555c52e162 100644
--- a/code/game/objects/items/devices/tvcamera.dm
+++ b/code/game/objects/items/devices/tvcamera.dm
@@ -91,8 +91,4 @@
else
icon_state = "camcorder"
item_state = "camcorder"
- var/mob/living/carbon/human/H = loc
- if(istype(H))
- H.update_inv_r_hand()
- H.update_inv_l_hand()
- H.update_inv_belt()
+ update_worn_icon()
diff --git a/code/game/objects/items/melee/types/misc.dm b/code/game/objects/items/melee/types/misc.dm
index e4656ce06955..89637a6aa540 100644
--- a/code/game/objects/items/melee/types/misc.dm
+++ b/code/game/objects/items/melee/types/misc.dm
@@ -43,10 +43,7 @@
addblends = icon_state + "_a"
item_state = icon_state
update_icon()
- if(ishuman(src.loc))
- var/mob/living/carbon/human/H = src.loc
- H.update_inv_l_hand(0)
- H.update_inv_r_hand()
+ update_worn_icon()
// Randomizes color
/obj/item/melee/umbrella/random/Initialize(mapload)
@@ -268,10 +265,7 @@
else
set_light(0)
- var/mob/M = loc
- if(istype(M))
- M.update_inv_l_hand()
- M.update_inv_r_hand()
+ update_worn_icon()
/obj/item/melee/ashlander/elder/proc/activate(mob/living/user)
to_chat(user, "You ignite the [src]'s sacred flame.")
@@ -506,10 +500,7 @@
else
set_light(0)
- var/mob/M = loc
- if(istype(M))
- M.update_inv_l_hand()
- M.update_inv_r_hand()
+ update_worn_icon()
/obj/item/melee/thermalcutter/proc/activate(var/mob/M)
var/turf/T = get_turf(src)
diff --git a/code/game/objects/items/melee/types/transforming.dm b/code/game/objects/items/melee/types/transforming.dm
index 837f697309d9..940112738ae3 100644
--- a/code/game/objects/items/melee/types/transforming.dm
+++ b/code/game/objects/items/melee/types/transforming.dm
@@ -128,8 +128,6 @@
* actor can be /datum/event_args/actor or a single mob.
*/
/obj/item/melee/transforming/proc/on_activate(datum/event_args/actor/actor, silent)
- E_ARGS_WRAP_USER_TO_ACTOR(actor)
-
damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force))
damage_tier = VALUE_OR_DEFAULT(active_damage_tier, initial(damage_tier))
damage_mode = VALUE_OR_DEFAULT(active_damage_mode, initial(damage_mode))
@@ -154,8 +152,6 @@
* actor can be /datum/event_args/actor or a single mob.
*/
/obj/item/melee/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent)
- E_ARGS_WRAP_USER_TO_ACTOR(actor)
-
damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force))
damage_tier = VALUE_OR_DEFAULT(inactive_damage_tier, initial(damage_tier))
damage_mode = VALUE_OR_DEFAULT(inactive_damage_mode, initial(damage_mode))
diff --git a/code/game/objects/items/offhand.dm b/code/game/objects/items/offhand.dm
index e19c0fa67f0a..b6206497aca3 100644
--- a/code/game/objects/items/offhand.dm
+++ b/code/game/objects/items/offhand.dm
@@ -28,15 +28,15 @@
* @params
* - type - the type of the offhand
* - index - hand index; null for any
- * - flags - inv flags
+ * - inv_op_flags - inv flags
* - ... - the rest of the args are passed into New() of the offhand.
*/
-/mob/proc/allocate_offhand(type, index, flags, ...)
+/mob/proc/allocate_offhand(type, index, inv_op_flags, ...)
RETURN_TYPE(/obj/item/offhand)
var/obj/item/offhand/O = new type(arglist(list(src) + args.Copy(4)))
if(index)
- if(put_in_hand_or_del(O, index, flags))
+ if(put_in_hands_or_del(O, inv_op_flags, index))
return O
else
- if(put_in_hands_or_del(O, flags))
+ if(put_in_hands_or_del(O, inv_op_flags))
return O
diff --git a/code/game/objects/items/shield/types/transforming.dm b/code/game/objects/items/shield/types/transforming.dm
index d60a18805403..af589fbe73d6 100644
--- a/code/game/objects/items/shield/types/transforming.dm
+++ b/code/game/objects/items/shield/types/transforming.dm
@@ -81,8 +81,6 @@
* actor can be /datum/event_args/actor or a single mob.
*/
/obj/item/shield/transforming/proc/on_activate(datum/event_args/actor/actor, silent)
- E_ARGS_WRAP_USER_TO_ACTOR(actor)
-
damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force))
set_weight_class(VALUE_OR_DEFAULT(active_weight_class, initial(w_class)))
@@ -97,8 +95,6 @@
* actor can be /datum/event_args/actor or a single mob.
*/
/obj/item/shield/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent)
- E_ARGS_WRAP_USER_TO_ACTOR(actor)
-
damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force))
set_weight_class(VALUE_OR_DEFAULT(inactive_weight_class, initial(w_class)))
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 13cedf6faf38..abd4f53a4475 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -384,8 +384,8 @@
if (M != user)
user.visible_message("[user] starts to apply \the [src] to [M]'s [limb].", "You start to apply \the [src] to [M]'s [limb].", "You hear something being wrapped.")
else
- if(( !H.hand && (affecting.organ_tag in list(BP_R_ARM, BP_R_HAND)) || \
- H.hand && (affecting.organ_tag in list(BP_L_ARM, BP_L_HAND)) ))
+ if(( !(H.active_hand % 2) && (affecting.organ_tag in list(BP_R_ARM, BP_R_HAND)) || \
+ (H.active_hand % 2) && (affecting.organ_tag in list(BP_L_ARM, BP_L_HAND)) ))
to_chat(user, "You can't apply a splint to the arm you're using!")
return
user.visible_message("[user] starts to apply \the [src] to their [limb].", "You start to apply \the [src] to your [limb].", "You hear something being wrapped.")
diff --git a/code/game/objects/items/tools/switchtool.dm b/code/game/objects/items/tools/switchtool.dm
index d2f4da5bdcf5..23786d32d925 100644
--- a/code/game/objects/items/tools/switchtool.dm
+++ b/code/game/objects/items/tools/switchtool.dm
@@ -161,7 +161,7 @@
to_chat(user, "\The [src] doesn't have any available modules!")
return
var/obj/item/choice
- choice = show_radial_menu(user, src, options)
+ choice = show_radial_menu(user, user, options)
if(deploy(choice))
to_chat(user, "You deploy \the [deployed].")
return TRUE
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index d283f298acb8..a2eced2bcc8e 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -232,11 +232,7 @@
else
set_light(0)
-// icon_state = welding ? "[icon_state]1" : "[initial(icon_state)]"
- var/mob/M = loc
- if(istype(M))
- M.update_inv_l_hand()
- M.update_inv_r_hand()
+ update_worn_icon()
//Sets the welding state of the welding tool. If you see W.welding = 1 anywhere, please change it to W.setWelding(1)
//so that the welding tool updates accordingly
@@ -749,10 +745,7 @@
/obj/item/weldingtool/electric/crystal/update_icon()
icon_state = welding ? "crystal_welder_on" : "crystal_welder"
item_state = welding ? "crystal_tool_lit" : "crystal_tool"
- var/mob/M = loc
- if(istype(M))
- M.update_inv_l_hand()
- M.update_inv_r_hand()
+ update_worn_icon()
/obj/item/weldingtool/electric/crystal/attack_self(mob/user, datum/event_args/actor/actor)
var/mob/living/carbon/human/H = user
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index fedbe0fcac66..568d38b4b2fd 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -281,13 +281,8 @@
else
activate(user)
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
+ update_worn_icon()
add_fingerprint(user)
- return
/obj/item/toy/sword/proc/activate(mob/living/user)
if(active)
@@ -323,7 +318,6 @@
return ..()
/obj/item/toy/sword/update_icon()
- . = ..()
var/mutable_appearance/blade_overlay = mutable_appearance(icon, "[icon_state]_blade")
blade_overlay.color = color
if(rainbow)
@@ -333,10 +327,8 @@
cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
if(active)
add_overlay(blade_overlay)
- if(istype(usr,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = usr
- H.update_inv_l_hand()
- H.update_inv_r_hand()
+ . = ..()
+ update_worn_icon()
/obj/item/toy/sword/AltClick(mob/living/user)
if(!colorable) //checks if is not colorable
diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm
index db7a09ac8c9d..531918adab24 100644
--- a/code/game/objects/items/weapons/cigs_lighters.dm
+++ b/code/game/objects/items/weapons/cigs_lighters.dm
@@ -132,11 +132,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
else
icon_state = "[initial(icon_state)]_burnt"
item_state = "[initial(item_state)]_burnt"
- if(ismob(loc))
- var/mob/living/M = loc
- M.update_inv_wear_mask(0)
- M.update_inv_l_hand(0)
- M.update_inv_r_hand(1)
+ update_worn_icon()
..()
/obj/item/clothing/mask/smokable/examine(mob/user, dist)
@@ -203,9 +199,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
lit = 0
icon_state = initial(icon_state)
item_state = initial(item_state)
- M.update_inv_wear_mask(0)
- M.update_inv_l_hand(0)
- M.update_inv_r_hand(1)
+ update_worn_icon()
smoketime = 0
reagents.clear_reagents()
name = "empty [initial(name)]"
@@ -418,10 +412,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
/obj/item/clothing/mask/smokable/cigarette/cigar/attackby(obj/item/W as obj, mob/user as mob)
..()
-
- user.update_inv_wear_mask(0)
- user.update_inv_l_hand(0)
- user.update_inv_r_hand(1)
+ update_worn_icon()
/obj/item/cigbutt/imp
icon_state = "cigimpbutt"
@@ -496,9 +487,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
else if(istype(W, /obj/item/assembly/igniter))
light("[user] fiddles with [W], and manages to light their [name] with the power of science.")
- user.update_inv_wear_mask(0)
- user.update_inv_l_hand(0)
- user.update_inv_r_hand(1)
+ update_worn_icon()
/obj/item/clothing/mask/smokable/pipe/cobpipe
name = "corn cob pipe"
@@ -629,7 +618,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
else
to_chat(user, "You burn yourself while lighting the lighter.")
var/mob/living/carbon/human/H = ishuman(user)? user : null
- if (user.get_held_item_of_index(1) == src)
+ if (user.get_held_index(1) == src)
H?.apply_damage(2,DAMAGE_TYPE_BURN,"l_hand")
else
H?.apply_damage(2,DAMAGE_TYPE_BURN,"r_hand")
diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm
index 174dcc0ab47a..0e620077d756 100644
--- a/code/game/objects/items/weapons/grenades/chem_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm
@@ -25,6 +25,9 @@
return ..()
/obj/item/grenade/chem_grenade/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
if(!stage || stage==1)
if(detonator)
// detonator.loc=src.loc
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index e59858133bc5..2779b2e35027 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -103,7 +103,7 @@
/obj/item/handcuffs/equipped(mob/living/user, slot, accessory)
. = ..()
if(slot == SLOT_ID_HANDCUFFED)
- user.drop_all_held_items()
+ user.drop_held_items()
user.stop_pulling()
/* grimdark code that's disabled for code quality reasons - readd later if we care
diff --git a/code/game/objects/items/weapons/implants/neuralbasic.dm b/code/game/objects/items/weapons/implants/neuralbasic.dm
index 30d050a315c2..5b97874c9825 100644
--- a/code/game/objects/items/weapons/implants/neuralbasic.dm
+++ b/code/game/objects/items/weapons/implants/neuralbasic.dm
@@ -16,7 +16,7 @@
my_brain.implant_assist(target_state)
if(H.isSynthetic() && H.get_FBP_type() != FBP_CYBORG) //If this on an FBP, it's just an extra inefficient attachment to whatever their brain is.
robotic_brain = TRUE
- if(my_brain && my_brain.can_assist())
+ if(istype(my_brain) && my_brain.can_assist())
START_PROCESSING(SSobj, src)
/obj/item/implant/neural/Destroy()
diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm
index 43bd5ce7ea66..027a90f6b4d9 100644
--- a/code/game/objects/items/weapons/material/twohanded.dm
+++ b/code/game/objects/items/weapons/material/twohanded.dm
@@ -27,12 +27,11 @@
attack_sound = "swing_hit"
drop_sound = 'sound/items/drop/sword.ogg'
pickup_sound = 'sound/items/pickup/sword.ogg'
-
passive_parry = /datum/passive_parry/melee{
parry_chance_melee = 15;
}
-/obj/item/material/twohanded/update_held_icon()
+/obj/item/material/twohanded/update_worn_icon()
var/mob/living/M = loc
if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M))
wielded = 1
@@ -62,7 +61,7 @@
..()
if(wielded)
spawn(0)
- update_held_icon()
+ update_worn_icon()
/*
* Fireaxe
@@ -84,9 +83,9 @@
pickup_sound = 'sound/items/pickup/axe.ogg'
heavy = TRUE
-/obj/item/material/twohanded/fireaxe/update_held_icon()
+/obj/item/material/twohanded/fireaxe/update_worn_icon()
var/mob/living/M = loc
- if(istype(M) && M.can_wield_item(src) && M.is_holding(src) && !M.hands_full())
+ if(istype(M) && M.can_wield_item(src) && M.is_holding(src) && !M.are_usable_hands_full())
wielded = 1
pry = 1
name = "[base_name] (wielded)"
diff --git a/code/game/objects/items/weapons/other.dm b/code/game/objects/items/weapons/other.dm
index f64549e3c578..ec6784e465bd 100644
--- a/code/game/objects/items/weapons/other.dm
+++ b/code/game/objects/items/weapons/other.dm
@@ -160,14 +160,9 @@
damage_force = 3
attack_verb = list("hit", "poked", "prodded")
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
+ update_worn_icon()
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
add_fingerprint(user)
- return TRUE
/obj/item/cane/crutch
name ="crutch"
diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm
index e74db1c884e0..5930b1830a5f 100644
--- a/code/game/objects/items/weapons/swords_axes_etc.dm
+++ b/code/game/objects/items/weapons/swords_axes_etc.dm
@@ -83,10 +83,7 @@
set_weight_class(WEIGHT_CLASS_SMALL)
damage_force = off_force //not so robust now
attack_verb = list("poked", "jabbed")
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
+ update_worn_icon()
playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1)
add_fingerprint(user)
if(blood_overlay && blood_DNA && (blood_DNA.len >= 1)) //updates blood overlay, if any
diff --git a/code/game/objects/items/weapons/weldbackpack.dm b/code/game/objects/items/weapons/weldbackpack.dm
index d36f968da2eb..b787670da3a2 100644
--- a/code/game/objects/items/weapons/weldbackpack.dm
+++ b/code/game/objects/items/weapons/weldbackpack.dm
@@ -38,7 +38,7 @@
var/mob/living/carbon/human/H = user
- if(H.hands_full()) //Make sure our hands aren't full.
+ if(H.are_usable_hands_full()) //Make sure our hands aren't full.
to_chat(H, "Your hands are full. Drop something first.")
return 0
diff --git a/code/game/objects/structures/crates_lockers/__closet.dm b/code/game/objects/structures/crates_lockers/__closet.dm
index d3597b1c215e..7cad5fa08288 100644
--- a/code/game/objects/structures/crates_lockers/__closet.dm
+++ b/code/game/objects/structures/crates_lockers/__closet.dm
@@ -25,6 +25,7 @@
var/breakout_time = 2 //2 minutes by default
breakout_sound = 'sound/effects/grillehit.ogg' //Sound that plays while breaking out
+ // todo: why the fuck is this in terms of mob defines?? this is stupid.
var/storage_capacity = 2 * MOB_MEDIUM //This is so that someone can't pack hundreds of items in a locker/crate
//then open it in a populated area to crash clients.
var/storage_cost = 40 //How much space this closet takes up if it's stuffed in another closet
@@ -52,6 +53,8 @@
//! legacy
/// override attackby and anything else closet-like
var/not_actually_a_closet = FALSE
+ /// was made at mapload
+ var/was_made_at_mapload
//! end
/obj/structure/closet/Initialize(mapload, singleton/closet_appearance/use_closet_appearance)
@@ -61,6 +64,7 @@
if(!isnull(use_closet_appearance))
src.closet_appearance = use_closet_appearance
legacy_spawn_contents()
+ was_made_at_mapload = mapload
/*
if(secure)
lockerelectronics = new(src)
@@ -79,6 +83,8 @@
icon = app.icon
color = null
update_icon()
+ if(was_made_at_mapload && !opened)
+ take_contents()
/obj/structure/closet/proc/update_icon_old()
if(!opened)
diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm
index 67e93490a3a9..6e80937fc473 100644
--- a/code/game/objects/structures/extinguisher.dm
+++ b/code/game/objects/structures/extinguisher.dm
@@ -48,16 +48,10 @@
/obj/structure/extinguisher_cabinet/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(isrobot(user))
return
- if (ishuman(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
+ if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL))
+ return
if(has_extinguisher)
- user.put_in_hands(has_extinguisher)
+ user.put_in_hands_or_drop(has_extinguisher)
to_chat(user, "You take [has_extinguisher] from [src].")
has_extinguisher = null
opened = 1
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index df8e0c650080..b799c570bf51 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -51,6 +51,7 @@
deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
return
return ..()
+
/obj/structure/statue/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
add_fingerprint(user)
user.visible_message("[user] rubs some dust off from the [name]'s surface.", \
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 79fe2c891e59..e9574cada346 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -241,10 +241,8 @@
if(iscarbon(O))
var/mob/living/carbon/M = O
- if(M.r_hand)
- M.r_hand.clean_blood()
- if(M.l_hand)
- M.l_hand.clean_blood()
+ for(var/obj/item/I as anything in M.get_held_items())
+ I.clean_blood()
if(M.back)
if(M.back.clean_blood())
M.update_inv_back(0)
@@ -389,14 +387,8 @@
thing.update_icon()
/obj/structure/sink/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
- if (ishuman(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
+ if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL))
+ return
if(isrobot(user) || isAI(user))
return
diff --git a/code/game/rendering/actor_huds/README.md b/code/game/rendering/actor_huds/README.md
new file mode 100644
index 000000000000..f38fcfd4bb4f
--- /dev/null
+++ b/code/game/rendering/actor_huds/README.md
@@ -0,0 +1,5 @@
+# Actor HUDs
+
+Actor HUDs serve to allow a player to control a character.
+
+Instead of certain HUDs being on the mob, they're on the client, and instead register to a mob's functions to update as needed.
diff --git a/code/game/rendering/actor_huds/actor_hud-screen_object.dm b/code/game/rendering/actor_huds/actor_hud-screen_object.dm
new file mode 100644
index 000000000000..eebdde11a452
--- /dev/null
+++ b/code/game/rendering/actor_huds/actor_hud-screen_object.dm
@@ -0,0 +1,23 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * The screen objects for actor HUDs
+ */
+/atom/movable/screen/actor_hud
+
+ /// our owning actor hud
+ var/datum/actor_hud/inventory/hud
+
+/atom/movable/screen/actor_hud/Initialize(mapload, datum/actor_hud/inventory/hud)
+ . = ..()
+ src.hud = hud
+ // todo: cache this and don't keep grabbing it?
+ sync_to_preferences(hud.holder.owner?.legacy_get_hud_preferences() || GLOB.default_hud_preferences)
+
+/atom/movable/screen/actor_hud/Destroy()
+ hud = null
+ return ..()
+
+/atom/movable/screen/actor_hud/check_allowed(mob/user)
+ return ..() && hud.actor == user
diff --git a/code/game/rendering/actor_huds/actor_hud.dm b/code/game/rendering/actor_huds/actor_hud.dm
new file mode 100644
index 000000000000..c9bca3642b9d
--- /dev/null
+++ b/code/game/rendering/actor_huds/actor_hud.dm
@@ -0,0 +1,118 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * actor huds are:
+ *
+ * * used by one (1) client
+ * * bound to one (1) mob
+ * * renders the state of that mob if needed
+ * * renders the state of the client's intent / will otherwise
+ *
+ * Add/remove screen/image procs are **stateless**.
+ * `screens()` and `images()` gather everything up.
+ * This is to save some CPU / memory as it's rare to need everything rather
+ * than just 'patch' the client's render.
+ */
+/datum/actor_hud
+ /// the mob we're bound to right now
+ ///
+ /// * this is our actor
+ var/mob/actor
+ /// the client we're made for
+ ///
+ /// * this is our controller
+ var/client/owner
+ /// our holder
+ ///
+ /// * just a collection of actor huds
+ var/datum/actor_hud_holder/holder
+
+/datum/actor_hud/New(datum/actor_hud_holder/holder)
+ src.holder = holder
+ src.owner = holder.owner
+
+/datum/actor_hud/Destroy()
+ unbind_from_mob()
+ return ..()
+
+/datum/actor_hud/proc/bind_to_mob(mob/target)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(actor == target)
+ return TRUE
+ else if(actor)
+ unbind_from_mob()
+ actor = target
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(bound_actor_deleted))
+ on_mob_bound(target)
+ return TRUE
+
+/datum/actor_hud/proc/unbind_from_mob()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!actor)
+ return
+ UnregisterSignal(actor, COMSIG_PARENT_QDELETING)
+ var/mob/old = actor
+ actor = null
+ on_mob_unbound(old)
+
+/datum/actor_hud/proc/bound_actor_deleted(datum/source)
+ ASSERT(source == actor)
+ unbind_from_mob()
+
+/datum/actor_hud/proc/on_mob_bound(mob/target)
+ return
+
+/datum/actor_hud/proc/on_mob_unbound(mob/target)
+ return
+
+/**
+ * syncs hud
+ */
+/datum/actor_hud/proc/sync_to_preferences(datum/hud_preferences/preference_set)
+ for(var/atom/movable/screen/screen_object in screens())
+ screen_object.sync_to_preferences(preference_set)
+
+/**
+ * returns all screens we should apply to a client
+ */
+/datum/actor_hud/proc/screens()
+ return list()
+
+/**
+ * returns all images we should apply to a client
+ */
+/datum/actor_hud/proc/images()
+ return list()
+
+/**
+ * wrapper; use this instead of directly editing client variables.
+ *
+ * * arg can be a list or a single object
+ */
+/datum/actor_hud/proc/add_screen(atom/movable/what)
+ owner.screen += what
+
+/**
+ * wrapper; use this instead of directly editing client variables.
+ *
+ * * arg can be a list or a single object
+ */
+/datum/actor_hud/proc/remove_screen(atom/movable/what)
+ owner.screen -= what
+
+/**
+ * wrapper; use this instead of directly editing client variables.
+ *
+ * * arg can be a list or a single object
+ */
+/datum/actor_hud/proc/add_image(image/what)
+ owner.images += what
+
+/**
+ * wrapper; use this instead of directly editing client variables.
+ *
+ * * arg can be a list or a single object
+ */
+/datum/actor_hud/proc/remove_image(image/what)
+ owner.images -= what
diff --git a/code/game/rendering/actor_huds/actor_hud_holder.dm b/code/game/rendering/actor_huds/actor_hud_holder.dm
new file mode 100644
index 000000000000..6fccda558907
--- /dev/null
+++ b/code/game/rendering/actor_huds/actor_hud_holder.dm
@@ -0,0 +1,71 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * A holder for actor HUDs on a client.
+ *
+ * This is used for stuff like inventory.
+ */
+/datum/actor_hud_holder
+ /// owning client
+ var/client/owner
+
+ /// inventory hud
+ var/datum/actor_hud/inventory/inventory
+
+/datum/actor_hud_holder/New(client/C)
+ // set owner
+ owner = C
+ // create huds
+ inventory = new(src)
+
+/datum/actor_hud_holder/Destroy()
+ // destroy huds
+ QDEL_NULL(inventory)
+ // teardown owner
+ owner = null
+ // do rest
+ return ..()
+
+/**
+ * reset every hud to a mob
+ */
+/datum/actor_hud_holder/proc/bind_all_to_mob(mob/target)
+ inventory.bind_to_mob(target)
+
+/**
+ * syncs hud preferences
+ */
+/datum/actor_hud_holder/proc/sync_all_to_preferences(datum/hud_preferences/preference_set)
+ inventory?.sync_to_preferences(preference_set)
+
+/**
+ * get all screens
+ */
+/datum/actor_hud_holder/proc/screens()
+ . = list()
+ for(var/datum/actor_hud/hud as anything in all_huds())
+ . += hud.screens()
+
+/**
+ * get all screens
+ */
+/datum/actor_hud_holder/proc/images()
+ . = list()
+ for(var/datum/actor_hud/hud as anything in all_huds())
+ . += hud.images()
+
+/**
+ * get all huds
+ */
+/datum/actor_hud_holder/proc/all_huds()
+ return list(
+ inventory,
+ )
+
+/**
+ * apply everything to our client
+ */
+/datum/actor_hud_holder/proc/reassert_onto_owner()
+ owner.images |= images()
+ owner.screen |= screens()
diff --git a/code/game/rendering/actor_huds/huds/inventory.dm b/code/game/rendering/actor_huds/huds/inventory.dm
new file mode 100644
index 000000000000..0a813e708f23
--- /dev/null
+++ b/code/game/rendering/actor_huds/huds/inventory.dm
@@ -0,0 +1,513 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Inventory slots specifically, not hands.
+ */
+/datum/actor_hud/inventory
+ /// owning inventory
+ var/datum/inventory/host
+
+ /// hidden classes, associated to list of reasons
+ var/list/hidden_classes = list(
+ (INVENTORY_HUD_CLASS_DRAWER) = list(
+ INVENTORY_HUD_HIDE_SOURCE_DRAWER,
+ ),
+ )
+
+ /// keyed slot id to screen object
+ var/list/atom/movable/screen/actor_hud/inventory/plate/slot/slots
+ /// ordered hand objects
+ var/list/atom/movable/screen/actor_hud/inventory/plate/hand/hands
+
+ /// drawer object
+ var/atom/movable/screen/actor_hud/inventory/drawer/button_drawer
+ /// swap hand object
+ var/atom/movable/screen/actor_hud/inventory/swap_hand/button_swap_hand
+ /// equip object
+ var/atom/movable/screen/actor_hud/inventory/equip_hand/button_equip_hand
+
+/datum/actor_hud/inventory/on_mob_bound(mob/target)
+ // we don't have a hook for 'on inventory init',
+ // so we can't init it lazily; we init it immediately.
+ target.init_inventory()
+ if(target.inventory)
+ bind_to_inventory(target.inventory)
+ return ..()
+
+/datum/actor_hud/inventory/on_mob_unbound(mob/target)
+ if(target.inventory)
+ unbind_from_inventory(target.inventory)
+ return ..()
+
+/datum/actor_hud/inventory/proc/bind_to_inventory(datum/inventory/inventory)
+ ASSERT(!host)
+ host = inventory
+ LAZYADD(inventory.huds_using, src)
+ rebuild(inventory.build_inventory_slots_with_remappings(), length(inventory.held_items))
+ for(var/i in 1 to length(inventory.held_items))
+ if(!inventory.held_items[i])
+ continue
+ add_item(inventory.held_items[i], i)
+ for(var/slot_id in inventory.owner.get_inventory_slot_ids())
+ var/obj/item/item_in_slot = inventory.owner.item_by_slot_id(slot_id)
+ if(!item_in_slot)
+ continue
+ add_item(item_in_slot, resolve_inventory_slot(slot_id))
+ if(inventory.owner.active_hand)
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/active_hand_plate = hands[inventory.owner.active_hand]
+ active_hand_plate.add_overlay("[active_hand_plate.icon_state]-active")
+
+/datum/actor_hud/inventory/proc/unbind_from_inventory(datum/inventory/inventory)
+ for(var/i in 1 to length(inventory.held_items))
+ if(!inventory.held_items[i])
+ continue
+ remove_item(inventory.held_items[i], i)
+ for(var/slot_id in inventory.owner.get_inventory_slot_ids())
+ var/obj/item/item_in_slot = inventory.owner.item_by_slot_id(slot_id)
+ if(!item_in_slot)
+ continue
+ remove_item(item_in_slot, resolve_inventory_slot(slot_id))
+ cleanup()
+ LAZYREMOVE(inventory.huds_using, src)
+ host = null
+
+/datum/actor_hud/inventory/screens()
+ . = ..()
+ // slots
+ . += all_slot_screen_objects(hidden_classes, TRUE)
+ // hands
+ . += all_hand_screen_objects()
+ // buttons
+ . += all_button_screen_objects()
+
+/**
+ * destroy everything
+ */
+/datum/actor_hud/inventory/proc/cleanup()
+ // slots
+ var/list/atom/movable/screen/actor_hud/inventory/plate/slot/slot_objects = all_slot_screen_objects()
+ remove_screen(slot_objects)
+ QDEL_LIST(slot_objects)
+ slots = null
+
+ // hands
+ var/list/atom/movable/screen/actor_hud/inventory/plate/hand/hand_objects = all_hand_screen_objects()
+ remove_screen(hand_objects)
+ QDEL_LIST(hand_objects)
+ hands = null
+
+ // buttons
+ var/list/atom/movable/screen/actor_hud/inventory/button_objects = all_button_screen_objects()
+ remove_screen(button_objects)
+ QDEL_NULL(button_objects)
+
+/**
+ * Accepts a list with keys as slot IDs, and values as null or a list of
+ * INVENTORY_SLOT_REMAP_*'s.
+ */
+/datum/actor_hud/inventory/proc/rebuild(list/inventory_slots_with_mappings = host.build_inventory_slots_with_remappings(), number_of_hands = host.get_hand_count())
+ cleanup()
+
+ // buttons
+ add_screen((button_swap_hand = new(null, src, number_of_hands)))
+ add_screen((button_equip_hand = new(null, src, number_of_hands)))
+ add_screen((button_drawer = new(null, src)))
+
+ // slots
+ rebuild_slots(inventory_slots_with_mappings)
+
+ // hands
+ rebuild_hands(number_of_hands)
+
+/**
+ * Rebuilds our slots. Doesn't rebuild anything else. Doesn't wipe old objects.
+ */
+/datum/actor_hud/inventory/proc/rebuild_slots(list/inventory_slots_with_mappings)
+ QDEL_LIST_ASSOC_VAL(slots)
+ LAZYINITLIST(slots)
+ for(var/slot_id in inventory_slots_with_mappings)
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ if(!slot)
+ stack_trace("failed to fetch slot during hud rebuild: [slot_id]")
+ continue
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object = new /atom/movable/screen/actor_hud/inventory/plate/slot(null, src, slot, inventory_slots_with_mappings[slot_id] || list())
+ if(!hidden_classes[slot_object.inventory_hud_class])
+ add_screen(slot_object)
+ slots[slot_id] = slot_object
+
+ // here is where we basically pull a CSS flexbox.
+
+ var/list/atom/movable/screen/actor_hud/inventory/plate/slot/place_anywhere = list()
+
+ var/list/cross_axis_for_drawer = list()
+ var/list/cross_axis_for_hands_left = list()
+ var/list/cross_axis_for_hands_right = list()
+
+ for(var/id in slots)
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object = slots[id]
+ var/list/inject_into
+
+ switch(slot_object.inventory_hud_anchor)
+ if(INVENTORY_HUD_ANCHOR_AUTOMATIC)
+ place_anywhere += slot_object
+ if(INVENTORY_HUD_ANCHOR_TO_DRAWER)
+ var/requested_cross_axis = clamp(slot_object.inventory_hud_cross_axis, 0, 4) + 1 // 1 to 5
+ if(length(cross_axis_for_drawer) < requested_cross_axis)
+ for(var/i in length(cross_axis_for_drawer) + 1 to requested_cross_axis)
+ cross_axis_for_drawer[++cross_axis_for_drawer.len] = list()
+ inject_into = cross_axis_for_drawer[requested_cross_axis]
+ if(INVENTORY_HUD_ANCHOR_TO_HANDS)
+ var/list/relevant_cross_axis = slot_object.inventory_hud_main_axis > 0 ? cross_axis_for_hands_right : cross_axis_for_hands_left
+ var/requested_cross_axis = clamp(slot_object.inventory_hud_cross_axis, 0, 4) + 1 // 1 to 5
+ if(length(relevant_cross_axis) < requested_cross_axis)
+ for(var/i in length(relevant_cross_axis) + 1 to requested_cross_axis)
+ relevant_cross_axis[++relevant_cross_axis.len] = list()
+ inject_into = relevant_cross_axis[requested_cross_axis]
+
+ BINARY_INSERT(slot_object, inject_into, /atom/movable/screen/actor_hud/inventory/plate/slot, slot_object, inventory_hud_main_axis, COMPARE_KEY)
+
+ if(length(place_anywhere))
+ var/list/cram_into_bottom_of_drawer = list()
+ for(var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object as anything in place_anywhere)
+ // intelligent detection; cluster based on class
+ switch(slot_object.inventory_hud_class)
+ if(INVENTORY_HUD_CLASS_ALWAYS)
+ cram_into_bottom_of_drawer += slot_object
+ if(INVENTORY_HUD_CLASS_DRAWER)
+ cram_into_bottom_of_drawer += slot_object
+ pack_2d_flat_list(cross_axis_for_drawer, cram_into_bottom_of_drawer, FALSE)
+
+ var/list/atom/movable/screen/actor_hud/inventory/plate/slot/aligned = list()
+
+ for(var/cross_axis in 1 to length(cross_axis_for_drawer))
+ var/list/cross_axis_list = cross_axis_for_drawer[cross_axis]
+ var/main_axis_bias = cross_axis == 1 ? 1 : 0
+ for(var/main_axis in 1 to length(cross_axis_list))
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/aligning = cross_axis_list[main_axis]
+ aligning.inventory_hud_cross_axis = cross_axis - 1
+ aligning.inventory_hud_main_axis = main_axis - 1 + main_axis_bias
+ aligned += aligning
+ for(var/cross_axis in 1 to length(cross_axis_for_hands_left))
+ var/list/cross_axis_list = cross_axis_for_hands_left[cross_axis]
+ var/add_for_inverse = -(length(cross_axis_list) + 1)
+ for(var/main_axis in 1 to length(cross_axis_list))
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/aligning = cross_axis_list[main_axis]
+ aligning.inventory_hud_cross_axis = cross_axis - 1
+ aligning.inventory_hud_main_axis = add_for_inverse + main_axis
+ aligned += aligning
+ for(var/cross_axis in 1 to length(cross_axis_for_hands_right))
+ var/list/cross_axis_list = cross_axis_for_hands_right[cross_axis]
+ for(var/main_axis in 1 to length(cross_axis_list))
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/aligning = cross_axis_list[main_axis]
+ aligning.inventory_hud_cross_axis = cross_axis - 1
+ aligning.inventory_hud_main_axis = main_axis
+ aligned += aligning
+
+ for(var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object as anything in aligned)
+ switch(slot_object.inventory_hud_anchor)
+ if(INVENTORY_HUD_ANCHOR_TO_DRAWER)
+ slot_object.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_DRAWER_ALIGNED(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis)
+ if(INVENTORY_HUD_ANCHOR_TO_HANDS)
+ slot_object.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_HANDS_ALIGNED(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis)
+
+/**
+ * Rebuilds our hands. Doesn't rebuild anything else. Doesn't wipe old objects.
+ */
+/datum/actor_hud/inventory/proc/rebuild_hands(number_of_hands)
+ LAZYINITLIST(hands)
+ if(length(hands) < number_of_hands)
+ var/old_length = length(hands)
+ hands.len = number_of_hands
+ for(var/i in old_length + 1 to number_of_hands)
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/hand_object = new(null, src, i)
+ add_screen(hand_object)
+ hands[i] = hand_object
+ else if(length(hands) > number_of_hands)
+ for(var/i in number_of_hands + 1 to length(hands))
+ if(!hands[i])
+ continue
+ remove_screen(hands[i])
+ qdel(hands[i])
+ hands.len = number_of_hands
+
+ button_equip_hand?.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(number_of_hands)
+ button_swap_hand?.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(number_of_hands)
+
+/**
+ * @params
+ * * filter_by_class - a singular, or a list, of inventory hud classes to filter by
+ * * inverse - get everything that isn't in the filter, instead of everything that is
+ */
+/datum/actor_hud/inventory/proc/all_slot_screen_objects(filter_by_class, inverse = FALSE)
+ RETURN_TYPE(/list)
+ . = list()
+ if(filter_by_class)
+ inverse = !!inverse
+ if(islist(filter_by_class))
+ for(var/id in slots)
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object = slots[id]
+ if((slot_object.inventory_hud_class in filter_by_class) != inverse)
+ . += slot_object
+ else
+ for(var/id in slots)
+ var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object = slots[id]
+ if((slot_object.inventory_hud_class == filter_by_class) != inverse)
+ . += slot_object
+ else
+ for(var/id in slots)
+ . += slots[id]
+
+/datum/actor_hud/inventory/proc/all_hand_screen_objects()
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/atom/movable/object in hands)
+ . += object
+
+/datum/actor_hud/inventory/proc/all_button_screen_objects()
+ RETURN_TYPE(/list)
+ . = list()
+ if(button_swap_hand)
+ . += button_swap_hand
+ if(button_equip_hand)
+ . += button_equip_hand
+ if(button_drawer)
+ . += button_drawer
+
+/datum/actor_hud/inventory/proc/toggle_hidden_class(class, source)
+ var/list/atom/movable/screen/actor_hud/inventory/affected
+ var/something_changed
+ if(class in hidden_classes)
+ LAZYREMOVE(hidden_classes[class], source)
+ if(!length(hidden_classes[class]))
+ affected = all_slot_screen_objects(class)
+ add_screen(affected)
+ hidden_classes -= class
+ something_changed = TRUE
+ else
+ if(!hidden_classes[class])
+ affected = all_slot_screen_objects(class)
+ remove_screen(affected)
+ something_changed = TRUE
+ LAZYADD(hidden_classes[class], class)
+ if(something_changed)
+ switch(class)
+ if(INVENTORY_HUD_CLASS_DRAWER)
+ button_drawer?.update_icon()
+
+/datum/actor_hud/inventory/proc/add_hidden_class(class, source)
+ if(class in hidden_classes)
+ return
+ toggle_hidden_class(class, source)
+
+/datum/actor_hud/inventory/proc/remove_hidden_class(class, source)
+ if(!(class in hidden_classes))
+ return
+ toggle_hidden_class(class, source)
+
+//* Hooks *//
+
+/datum/actor_hud/inventory/proc/add_item(obj/item/item, datum/inventory_slot/slot_or_index)
+ var/atom/movable/screen/actor_hud/inventory/plate/screen_obj = isnum(slot_or_index) ? hands[slot_or_index] : slots[slot_or_index.id]
+ screen_obj.bind_item(item)
+
+/datum/actor_hud/inventory/proc/remove_item(obj/item/item, datum/inventory_slot/slot_or_index)
+ var/atom/movable/screen/actor_hud/inventory/plate/screen_obj = isnum(slot_or_index) ? hands[slot_or_index] : slots[slot_or_index.id]
+ screen_obj.unbind_item(item)
+
+/datum/actor_hud/inventory/proc/move_item(obj/item/item, datum/inventory_slot/from_slot_or_index, datum/inventory_slot/to_slot_or_index)
+ var/atom/movable/screen/actor_hud/inventory/plate/old_screen_obj = isnum(from_slot_or_index) ? hands[from_slot_or_index] : slots[from_slot_or_index.id]
+ var/atom/movable/screen/actor_hud/inventory/plate/new_screen_obj = isnum(to_slot_or_index) ? hands[to_slot_or_index] : slots[to_slot_or_index.id]
+ old_screen_obj.unbind_item(item)
+ new_screen_obj.bind_item(item)
+
+/datum/actor_hud/inventory/proc/swap_active_hand(from_index, to_index)
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/old_hand = hands[from_index]
+ var/atom/movable/screen/actor_hud/inventory/plate/hand/new_hand = hands[to_index]
+
+ old_hand.cut_overlay("[old_hand.icon_state]-active")
+ new_hand.add_overlay("[new_hand.icon_state]-active")
+
+//* Inventory Screen Objects *//
+
+/**
+ * Base type of inventory screen objects.
+ */
+/atom/movable/screen/actor_hud/inventory
+ name = "inventory"
+ icon = 'icons/screen/hud/midnight/inventory.dmi'
+ plane = INVENTORY_PLANE
+ layer = INVENTORY_PLATE_LAYER
+
+/atom/movable/screen/actor_hud/inventory/on_click(mob/user, list/params)
+ var/obj/item/held = user.get_active_held_item()
+ handle_inventory_click(user, held)
+
+/atom/movable/screen/actor_hud/inventory/sync_to_preferences(datum/hud_preferences/preference_set)
+ sync_style(preference_set.hud_style, preference_set.hud_alpha, preference_set.hud_color)
+
+/atom/movable/screen/actor_hud/inventory/proc/sync_style(datum/hud_style/style, style_alpha, style_color)
+ alpha = style_alpha
+ color = style_color
+
+/**
+ * handle an inventory operation
+ *
+ * @params
+ * * user - clicking user; not necessarily the inventory's owner
+ * * with_item - specifically attempting to swap an inventory object with an item, or interact with it with an item.
+ */
+/atom/movable/screen/actor_hud/inventory/proc/handle_inventory_click(mob/user, obj/item/with_item)
+ return
+
+/**
+ * Base type of item-holding screen objects
+ */
+/atom/movable/screen/actor_hud/inventory/plate
+
+/atom/movable/screen/actor_hud/inventory/plate/Destroy()
+ if(length(vis_contents) != 0)
+ vis_contents.len = 0
+ return ..()
+
+/atom/movable/screen/actor_hud/inventory/plate/proc/bind_item(obj/item/item)
+ vis_contents += item
+
+/atom/movable/screen/actor_hud/inventory/plate/proc/unbind_item(obj/item/item)
+ vis_contents -= item
+
+/**
+ * Slot screen objects
+ *
+ * * Stores remappings so we don't have to do it separately
+ * * Stores calculated screen_loc so we don't have to recalculate unless slots are mutated.
+ */
+/atom/movable/screen/actor_hud/inventory/plate/slot
+ /// our inventory slot id
+ var/inventory_slot_id
+ /// our (potentially remapped) class
+ var/inventory_hud_class = INVENTORY_HUD_CLASS_ALWAYS
+ /// our (potentially remapped) main axis
+ var/inventory_hud_main_axis = 0
+ /// our (potentially remapped) cross axis
+ var/inventory_hud_cross_axis = 0
+ /// our (potentially remapped) anchor
+ var/inventory_hud_anchor = INVENTORY_HUD_ANCHOR_AUTOMATIC
+
+/atom/movable/screen/actor_hud/inventory/plate/slot/Initialize(mapload, datum/actor_hud/inventory/hud, datum/inventory_slot/slot, list/slot_remappings)
+ . = ..()
+ inventory_slot_id = slot.id
+ icon_state = slot.inventory_hud_icon_state
+ inventory_hud_class = slot_remappings[INVENTORY_SLOT_REMAP_CLASS] || slot.inventory_hud_class
+ inventory_hud_main_axis = slot_remappings[INVENTORY_SLOT_REMAP_MAIN_AXIS] || slot.inventory_hud_main_axis
+ inventory_hud_cross_axis = slot_remappings[INVENTORY_SLOT_REMAP_CROSS_AXIS] || slot.inventory_hud_cross_axis
+ inventory_hud_anchor = slot_remappings[INVENTORY_SLOT_REMAP_ANCHOR] || slot.inventory_hud_anchor
+ name = slot_remappings[INVENTORY_SLOT_REMAP_NAME] || slot.display_name || slot.name
+
+/atom/movable/screen/actor_hud/inventory/plate/slot/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.inventory_icons_slot
+
+/atom/movable/screen/actor_hud/inventory/plate/slot/handle_inventory_click(mob/user, obj/item/with_item)
+ var/obj/item/in_slot = user.item_by_slot_id(inventory_slot_id)
+ if(with_item)
+ if(in_slot)
+ with_item.melee_interaction_chain(in_slot, user, NONE, list())
+ else
+ user.equip_to_slot_if_possible(with_item, inventory_slot_id, NONE, user)
+ else
+ in_slot?.attack_hand(user, new /datum/event_args/actor/clickchain(user))
+
+/**
+ * Hand screen objects
+ */
+/atom/movable/screen/actor_hud/inventory/plate/hand
+ /// target hand index
+ var/hand_index
+ /// should we have handcuffed overlay?
+ var/handcuffed = FALSE
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/Initialize(mapload, datum/inventory/host, hand_index)
+ . = ..()
+ src.hand_index = hand_index
+ sync_index(hand_index)
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.inventory_icons
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/handle_inventory_click(mob/user, obj/item/with_item)
+ hud.owner.swap_hand(hand_index)
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/proc/sync_index(index = hand_index)
+ screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND(index)
+ name = "[index % 2? "left" : "right"] hand[index > 2? " #[index]" : ""]"
+ icon_state = "hand-[index % 2? "left" : "right"]"
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/proc/set_handcuffed(state)
+ if(state == handcuffed)
+ return
+ handcuffed = state
+ update_icon()
+
+/atom/movable/screen/actor_hud/inventory/plate/hand/update_overlays()
+ . = ..()
+ if(handcuffed)
+ . += image('icons/mob/screen_gen.dmi', "[hand_index % 2 ? "r_hand" : "l_hand"]_hud_handcuffs")
+
+/**
+ * Button: 'swap hand'
+ */
+/atom/movable/screen/actor_hud/inventory/drawer
+ name = "drawer"
+ icon_state = "drawer"
+ screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_DRAWER
+
+/atom/movable/screen/actor_hud/inventory/drawer/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.inventory_icons
+
+/atom/movable/screen/actor_hud/inventory/drawer/on_click(mob/user, list/params)
+ // todo: remote control
+ hud.toggle_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_DRAWER)
+
+/atom/movable/screen/actor_hud/inventory/drawer/update_icon_state()
+ icon_state = "[(INVENTORY_HUD_CLASS_DRAWER in hud.hidden_classes) ? "drawer" : "drawer-active"]"
+ return ..()
+
+/**
+ * Button: 'swap hand'
+ */
+/atom/movable/screen/actor_hud/inventory/swap_hand
+ name = "swap active hand"
+ icon_state = "hand-swap"
+/atom/movable/screen/actor_hud/inventory/swap_hand/Initialize(mapload, datum/inventory/host, hand_count)
+ . = ..()
+ screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(hand_count)
+
+/atom/movable/screen/actor_hud/inventory/swap_hand/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.inventory_icons_wide
+
+/atom/movable/screen/actor_hud/inventory/swap_hand/on_click(mob/user, list/params)
+ // todo: remote control
+ user.swap_hand()
+
+/**
+ * Button: 'auto equip'
+ */
+/atom/movable/screen/actor_hud/inventory/equip_hand
+ name = "equip held item"
+ icon_state = "button-equip"
+
+/atom/movable/screen/actor_hud/inventory/equip_hand/Initialize(mapload, datum/inventory/host, hand_count)
+ . = ..()
+ screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(hand_count)
+
+/atom/movable/screen/actor_hud/inventory/equip_hand/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.inventory_icons
+
+/atom/movable/screen/actor_hud/inventory/equip_hand/on_click(mob/user, list/params)
+ // todo: remote control
+ user.attempt_smart_equip(user.get_active_held_item())
diff --git a/code/game/rendering/client.dm b/code/game/rendering/client.dm
index 6891410a47ad..3d1e85194ce8 100644
--- a/code/game/rendering/client.dm
+++ b/code/game/rendering/client.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
//? clickcatcher
/**
diff --git a/code/game/rendering/hud_preferences.dm b/code/game/rendering/hud_preferences.dm
new file mode 100644
index 000000000000..6b75e5c3cb6d
--- /dev/null
+++ b/code/game/rendering/hud_preferences.dm
@@ -0,0 +1,30 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+GLOBAL_DATUM_INIT(default_hud_preferences, /datum/hud_preferences, new /datum/hud_preferences/default)
+
+/**
+ * A set of preferences for how to render the game's HUDs.
+ */
+/datum/hud_preferences
+ /// desired hud style - set at base of sync_client
+ var/datum/hud_style/hud_style
+ /// desired hud color - set at base of sync_client
+ var/hud_color
+ /// desired hud alpha - set at base of sync_client
+ var/hud_alpha
+
+/datum/hud_preferences/default
+ hud_style = new /datum/hud_style/midnight // yes, this doesn't use the global cached variant. sue me.
+ hud_color = "#ffffff"
+ hud_alpha = 200
+
+/**
+ * todo: remove
+ */
+/client/proc/legacy_get_hud_preferences()
+ var/datum/hud_preferences/creating = new
+ creating.hud_style = legacy_find_hud_style_by_name(preferences.get_entry(/datum/game_preference_entry/dropdown/hud_style)) || GLOB.hud_styles[/datum/hud_style/midnight::id]
+ creating.hud_color = preferences.get_entry(/datum/game_preference_entry/simple_color/hud_color)
+ creating.hud_alpha = preferences.get_entry(/datum/game_preference_entry/number/hud_alpha)
+ return creating
diff --git a/code/game/rendering/hud_style.dm b/code/game/rendering/hud_style.dm
new file mode 100644
index 000000000000..58be182cf796
--- /dev/null
+++ b/code/game/rendering/hud_style.dm
@@ -0,0 +1,110 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+GLOBAL_LIST_INIT(hud_styles, init_hud_styles())
+
+/proc/init_hud_styles()
+ . = list()
+ for(var/datum/hud_style/path as anything in subtypesof(/datum/hud_style))
+ if(initial(path.abstract_type) == path)
+ continue
+ .[initial(path.id)] = new path
+
+/proc/legacy_find_hud_style_by_name(name)
+ for(var/id in GLOB.hud_styles)
+ var/datum/hud_style/style = GLOB.hud_styles[id]
+ if(lowertext(style.name) == lowertext(name) || lowertext(style.id) == lowertext(name))
+ return style
+
+/**
+ * # HUD Style
+ *
+ * Holds data on HUD styles
+ *
+ * * default values are on /datum/hud_style
+ *
+ * ## Icons
+ *
+ * ### inventory.dmi
+ *
+ * Mandatory:
+ *
+ * * button-equip: used as equip button
+ *
+ * * hand-left: self explanatory
+ * * hand-left-active: self explanatory
+ * * hand-right: self explanatory
+ * * hand-right-active: self explanatory
+ *
+ * * drawer: inactive inventory drawer
+ * * drawer-active: active inventory drawer
+ *
+ * ### inventory-slot.dmi
+ *
+ * Mandatory:
+ *
+ * * : used as default slot render
+ *
+ * Optional:
+ *
+ * * : states are matched to inventory slot by BYOND to render.
+ *
+ * ### inventory-wide.dmi
+ *
+ * * hand-swap: swap hands button
+ */
+/datum/hud_style
+ abstract_type = /datum/hud_style
+ /// style name
+ var/name = "Unknown"
+ /// style uid
+ var/id
+
+ /// inventory icons
+ var/inventory_icons = 'icons/screen/hud/midnight/inventory.dmi'
+ /// inventory icons: slots
+ var/inventory_icons_slot = 'icons/screen/hud/midnight/inventory-slot.dmi'
+ /// inventory icons: big
+ var/inventory_icons_wide = 'icons/screen/hud/midnight/inventory-wide.dmi'
+
+/**
+ * midnight style just inherits defaults
+ */
+/datum/hud_style/midnight
+ name = "Midnight"
+ id = "midnight"
+
+/datum/hud_style/orange
+ name = "Orange"
+ id = "orange"
+ inventory_icons = 'icons/screen/hud/orange/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/orange/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/orange/inventory-wide.dmi'
+
+/datum/hud_style/old
+ name = "Retro"
+ id = "old"
+ inventory_icons = 'icons/screen/hud/old/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/old/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/old/inventory-wide.dmi'
+
+/datum/hud_style/white
+ name = "White"
+ id = "white"
+ inventory_icons = 'icons/screen/hud/white/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/white/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/white/inventory-wide.dmi'
+
+/datum/hud_style/minimalist
+ name = "Minimalist"
+ id = "minimalist"
+ inventory_icons = 'icons/screen/hud/minimalist/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/minimalist/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/minimalist/inventory-wide.dmi'
+
+/datum/hud_style/hologram
+ name = "Holographic"
+ id = "hologram"
+ inventory_icons = 'icons/screen/hud/hologram/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/hologram/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/hologram/inventory-wide.dmi'
diff --git a/code/game/rendering/legacy/ghost.dm b/code/game/rendering/legacy/ghost.dm
index 8b290167fd2c..aaba2cbb6247 100644
--- a/code/game/rendering/legacy/ghost.dm
+++ b/code/game/rendering/legacy/ghost.dm
@@ -108,47 +108,47 @@
var/atom/movable/screen/using
using = new /atom/movable/screen/ghost/returntomenu()
using.screen_loc = ui_ghost_returntomenu
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/jumptomob()
using.screen_loc = ui_ghost_jumptomob
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/orbit()
using.screen_loc = ui_ghost_orbit
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/reenter_corpse()
using.screen_loc = ui_ghost_reenter_corpse
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/teleport()
using.screen_loc = ui_ghost_teleport
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/pai()
using.screen_loc = ui_ghost_pai
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/up()
using.screen_loc = ui_ghost_updown
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/down()
using.screen_loc = ui_ghost_updown
- using.hud = src
+ using.hud_legacy = src
adding += using
using = new /atom/movable/screen/ghost/spawners
using.screen_loc = ui_ghost_spawners
- using.hud = src
+ using.hud_legacy = src
adding += using
if(mymob.client && apply_to_client)
diff --git a/code/game/rendering/legacy/hud.dm b/code/game/rendering/legacy/hud.dm
index cbf71f14ecc7..09c1cd39ec1f 100644
--- a/code/game/rendering/legacy/hud.dm
+++ b/code/game/rendering/legacy/hud.dm
@@ -18,7 +18,7 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
var/atom/movable/screen/holomap
/atom/movable/screen/global_screen
- screen_loc = ui_entire_screen
+ screen_loc = SCREEN_LOC_FULLSCREEN
plane = FULLSCREEN_PLANE
mouse_opacity = 0
@@ -105,8 +105,6 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
var/atom/movable/screen/wiz_energy_display
var/atom/movable/screen/blobpwrdisplay
var/atom/movable/screen/blobhealthdisplay
- var/atom/movable/screen/r_hand_hud_object
- var/atom/movable/screen/l_hand_hud_object
var/atom/movable/screen/action_intent
var/atom/movable/screen/move_intent
@@ -120,11 +118,6 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
var/list/miniobjs
var/list/atom/movable/screen/hotkeybuttons
- /// screen_loc's of slots, by slot id. hands are not slots.
- var/list/slot_info = list()
- /// screen_loc's of hands, by index - index is associative NUMBER AS TEXT.
- var/list/hand_info = list()
-
// pending hardsync
var/icon/ui_style
var/ui_color
@@ -148,8 +141,6 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
wiz_energy_display = null
blobpwrdisplay = null
blobhealthdisplay = null
- r_hand_hud_object = null
- l_hand_hud_object = null
action_intent = null
move_intent = null
adding = null
@@ -162,92 +153,6 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
QDEL_LIST(static_inventory)
-/datum/hud/proc/hidden_inventory_update()
- if(!mymob) return
- if(ishuman(mymob))
- var/mob/living/carbon/human/H = mymob
- for(var/gear_slot in H.species.hud.gear)
- var/list/hud_data = H.species.hud.gear[gear_slot]
- if(inventory_shown && hud_shown)
- switch(hud_data["slot"])
- if(SLOT_ID_HEAD)
- if(H.head) H.head.screen_loc = hud_data["loc"]
- if(SLOT_ID_SHOES)
- if(H.shoes) H.shoes.screen_loc = hud_data["loc"]
- if(SLOT_ID_LEFT_EAR)
- if(H.l_ear) H.l_ear.screen_loc = hud_data["loc"]
- if(SLOT_ID_RIGHT_EAR)
- if(H.r_ear) H.r_ear.screen_loc = hud_data["loc"]
- if(SLOT_ID_GLOVES)
- if(H.gloves) H.gloves.screen_loc = hud_data["loc"]
- if(SLOT_ID_GLASSES)
- if(H.glasses) H.glasses.screen_loc = hud_data["loc"]
- if(SLOT_ID_UNIFORM)
- if(H.w_uniform) H.w_uniform.screen_loc = hud_data["loc"]
- if(SLOT_ID_SUIT)
- if(H.wear_suit) H.wear_suit.screen_loc = hud_data["loc"]
- if(SLOT_ID_MASK)
- if(H.wear_mask) H.wear_mask.screen_loc = hud_data["loc"]
- else
- switch(hud_data["slot"])
- if(SLOT_ID_HEAD)
- if(H.head) H.head.screen_loc = null
- if(SLOT_ID_SHOES)
- if(H.shoes) H.shoes.screen_loc = null
- if(SLOT_ID_LEFT_EAR)
- if(H.l_ear) H.l_ear.screen_loc = null
- if(SLOT_ID_RIGHT_EAR)
- if(H.r_ear) H.r_ear.screen_loc = null
- if(SLOT_ID_GLOVES)
- if(H.gloves) H.gloves.screen_loc = null
- if(SLOT_ID_GLASSES)
- if(H.glasses) H.glasses.screen_loc = null
- if(SLOT_ID_UNIFORM)
- if(H.w_uniform) H.w_uniform.screen_loc = null
- if(SLOT_ID_SUIT)
- if(H.wear_suit) H.wear_suit.screen_loc = null
- if(SLOT_ID_MASK)
- if(H.wear_mask) H.wear_mask.screen_loc = null
-
-
-/datum/hud/proc/persistant_inventory_update()
- if(!mymob)
- return
-
- if(ishuman(mymob))
- var/mob/living/carbon/human/H = mymob
- for(var/gear_slot in H.species.hud.gear)
- var/list/hud_data = H.species.hud.gear[gear_slot]
- if(hud_shown)
- switch(hud_data["slot"])
- if(SLOT_ID_SUIT_STORAGE)
- if(H.s_store) H.s_store.screen_loc = hud_data["loc"]
- if(SLOT_ID_WORN_ID)
- if(H.wear_id) H.wear_id.screen_loc = hud_data["loc"]
- if(SLOT_ID_BELT)
- if(H.belt) H.belt.screen_loc = hud_data["loc"]
- if(SLOT_ID_BACK)
- if(H.back) H.back.screen_loc = hud_data["loc"]
- if(SLOT_ID_LEFT_POCKET)
- if(H.l_store) H.l_store.screen_loc = hud_data["loc"]
- if(SLOT_ID_RIGHT_POCKET)
- if(H.r_store) H.r_store.screen_loc = hud_data["loc"]
- else
- switch(hud_data["slot"])
- if(SLOT_ID_SUIT_STORAGE)
- if(H.s_store) H.s_store.screen_loc = null
- if(SLOT_ID_WORN_ID)
- if(H.wear_id) H.wear_id.screen_loc = null
- if(SLOT_ID_BELT)
- if(H.belt) H.belt.screen_loc = null
- if(SLOT_ID_BACK)
- if(H.back) H.back.screen_loc = null
- if(SLOT_ID_LEFT_POCKET)
- if(H.l_store) H.l_store.screen_loc = null
- if(SLOT_ID_RIGHT_POCKET)
- if(H.r_store) H.r_store.screen_loc = null
-
-
/datum/hud/proc/instantiate()
if(!ismob(mymob))
return 0
@@ -323,8 +228,6 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
//Due to some poor coding some things need special treatment:
//These ones are a part of 'adding', 'other' or 'hotkeybuttons' but we want them to stay
if(!full)
- src.client.screen += src.hud_used.l_hand_hud_object //we want the hands to be visible
- src.client.screen += src.hud_used.r_hand_hud_object //we want the hands to be visible
src.client.screen += src.hud_used.action_intent //we want the intent swticher visible
src.hud_used.action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is.
else
@@ -335,6 +238,9 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
//These ones are not a part of 'adding', 'other' or 'hotkeybuttons' but we want them gone.
src.client.screen -= src.zone_sel //zone_sel is a mob variable for some reason.
+ client.actor_huds.inventory?.remove_hidden_class(INVENTORY_HUD_CLASS_ALWAYS, INVENTORY_HUD_HIDE_SOURCE_F12)
+ client.actor_huds.inventory?.remove_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_F12)
+
else
hud_used.hud_shown = 1
if(src.hud_used.adding)
@@ -353,8 +259,8 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
src.hud_used.action_intent.screen_loc = ui_acti //Restore intent selection to the original position
src.client.screen += src.zone_sel //This one is a special snowflake
- hud_used.hidden_inventory_update()
- hud_used.persistant_inventory_update()
+ client.actor_huds.inventory?.add_hidden_class(INVENTORY_HUD_CLASS_ALWAYS, INVENTORY_HUD_HIDE_SOURCE_F12)
+ client.actor_huds.inventory?.add_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_F12)
//Similar to button_pressed_F12() but keeps zone_sel, gun_setting_icon, and healths.
/mob/proc/toggle_zoom_hud()
@@ -379,6 +285,9 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
src.client.screen -= src.hud_used.other_important
src.client.screen -= src.internals
src.client.screen += src.hud_used.action_intent //we want the intent swticher visible
+
+ client.actor_huds.inventory?.remove_hidden_class(INVENTORY_HUD_CLASS_ALWAYS, INVENTORY_HUD_HIDE_SOURCE_F12)
+ client.actor_huds.inventory?.remove_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_F12)
else
hud_used.hud_shown = 1
if(src.hud_used.adding)
@@ -393,5 +302,5 @@ GLOBAL_DATUM_INIT(global_hud, /datum/global_hud, new)
src.client.screen |= src.internals
src.hud_used.action_intent.screen_loc = ui_acti //Restore intent selection to the original position
- hud_used.hidden_inventory_update()
- hud_used.persistant_inventory_update()
+ client.actor_huds.inventory?.add_hidden_class(INVENTORY_HUD_CLASS_ALWAYS, INVENTORY_HUD_HIDE_SOURCE_F12)
+ client.actor_huds.inventory?.add_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_F12)
diff --git a/code/game/rendering/legacy/hud_object.dm b/code/game/rendering/legacy/hud_object.dm
index cc3620e8f813..121a8e11266f 100644
--- a/code/game/rendering/legacy/hud_object.dm
+++ b/code/game/rendering/legacy/hud_object.dm
@@ -2,15 +2,15 @@
/atom/movable/screen/hud/Initialize(mapload, datum/hud/master)
. = ..()
- hud = master
+ hud_legacy = master
sync_to_hud()
/atom/movable/screen/hud/proc/sync_to_hud()
- if(!hud)
+ if(!hud_legacy)
return
- icon = hud.ui_style
- color = hud.ui_color
- alpha = hud.ui_alpha
+ icon = hud_legacy.ui_style
+ color = hud_legacy.ui_color
+ alpha = hud_legacy.ui_alpha
/atom/movable/screen/hud/Click(location, control, params)
SEND_SIGNAL(src, COMSIG_CLICK, location, control, params)
diff --git a/code/game/rendering/legacy/human.dm b/code/game/rendering/legacy/human.dm
index b1158856be78..a36e4035acd4 100644
--- a/code/game/rendering/legacy/human.dm
+++ b/code/game/rendering/legacy/human.dm
@@ -14,126 +14,84 @@
src.adding = list()
src.other = list()
src.hotkeybuttons = list() //These can be disabled for hotkey users
- slot_info = list()
- hand_info = list()
var/list/hud_elements = list()
var/atom/movable/screen/using
- var/atom/movable/screen/inventory/slot/inv_box
-
- // Draw the various inventory equipment slots.
- var/has_hidden_gear
- for(var/gear_slot in hud_data.gear)
-
- inv_box = new /atom/movable/screen/inventory/slot()
- inv_box.icon = ui_style
- inv_box.color = ui_color
- inv_box.alpha = ui_alpha
-
- var/list/slot_data = hud_data.gear[gear_slot]
- inv_box.name = gear_slot
- inv_box.screen_loc = slot_data["loc"]
- inv_box.slot_id = slot_data["slot"]
- inv_box.icon_state = slot_data["state"]
- slot_info["[inv_box.slot_id]"] = inv_box.screen_loc
-
- if(slot_data["dir"])
- inv_box.setDir(slot_data["dir"])
-
- if(slot_data["toggle"])
- src.other += inv_box
- has_hidden_gear = 1
- else
- src.adding += inv_box
-
- if(has_hidden_gear)
- using = new /atom/movable/screen()
- using.name = "toggle"
- using.icon = ui_style
- using.icon_state = "other"
- using.screen_loc = ui_inventory
- using.hud_layerise()
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
// Draw the attack intent dialogue.
- if(hud_data.has_a_intent)
-
- using = new /atom/movable/screen()
- using.name = "act_intent"
- using.icon = ui_style
- using.icon_state = "intent_"+mymob.a_intent
- using.screen_loc = ui_acti
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
- action_intent = using
-
- hud_elements |= using
-
- //intent small hud objects
- var/icon/ico
-
- ico = new(ui_style, "black")
- ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
- ico.DrawBox(rgb(255,255,255,1),1,ico.Height()/2,ico.Width()/2,ico.Height())
- using = new /atom/movable/screen()
- using.name = INTENT_HELP
- using.icon = ico
- using.screen_loc = ui_acti
- using.alpha = ui_alpha
- using.layer = HUD_LAYER_ITEM //These sit on the intent box
- src.adding += using
- help_intent = using
-
- ico = new(ui_style, "black")
- ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
- ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,ico.Height()/2,ico.Width(),ico.Height())
- using = new /atom/movable/screen()
- using.name = INTENT_DISARM
- using.icon = ico
- using.screen_loc = ui_acti
- using.alpha = ui_alpha
- using.layer = HUD_LAYER_ITEM
- src.adding += using
- disarm_intent = using
-
- ico = new(ui_style, "black")
- ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
- ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,1,ico.Width(),ico.Height()/2)
- using = new /atom/movable/screen()
- using.name = INTENT_GRAB
- using.icon = ico
- using.screen_loc = ui_acti
- using.alpha = ui_alpha
- using.layer = HUD_LAYER_ITEM
- src.adding += using
- grab_intent = using
-
- ico = new(ui_style, "black")
- ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
- ico.DrawBox(rgb(255,255,255,1),1,1,ico.Width()/2,ico.Height()/2)
- using = new /atom/movable/screen()
- using.name = INTENT_HARM
- using.icon = ico
- using.screen_loc = ui_acti
- using.alpha = ui_alpha
- using.layer = HUD_LAYER_ITEM
- src.adding += using
- hurt_intent = using
- //end intent small hud objects
-
- if(hud_data.has_m_intent)
- using = new /atom/movable/screen()
- using.name = "mov_intent"
- using.icon = ui_style
- using.icon_state = (mymob.m_intent == "run" ? "running" : "walking")
- using.screen_loc = ui_movi
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
- move_intent = using
+ using = new /atom/movable/screen()
+ using.name = "act_intent"
+ using.icon = ui_style
+ using.icon_state = "intent_"+mymob.a_intent
+ using.screen_loc = ui_acti
+ using.color = ui_color
+ using.alpha = ui_alpha
+ src.adding += using
+ action_intent = using
+
+ hud_elements |= using
+
+ //intent small hud objects
+ var/icon/ico
+
+ ico = new(ui_style, "black")
+ ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
+ ico.DrawBox(rgb(255,255,255,1),1,ico.Height()/2,ico.Width()/2,ico.Height())
+ using = new /atom/movable/screen()
+ using.name = INTENT_HELP
+ using.icon = ico
+ using.screen_loc = ui_acti
+ using.alpha = ui_alpha
+ using.layer = HUD_LAYER_ITEM //These sit on the intent box
+ src.adding += using
+ help_intent = using
+
+ ico = new(ui_style, "black")
+ ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
+ ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,ico.Height()/2,ico.Width(),ico.Height())
+ using = new /atom/movable/screen()
+ using.name = INTENT_DISARM
+ using.icon = ico
+ using.screen_loc = ui_acti
+ using.alpha = ui_alpha
+ using.layer = HUD_LAYER_ITEM
+ src.adding += using
+ disarm_intent = using
+
+ ico = new(ui_style, "black")
+ ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
+ ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,1,ico.Width(),ico.Height()/2)
+ using = new /atom/movable/screen()
+ using.name = INTENT_GRAB
+ using.icon = ico
+ using.screen_loc = ui_acti
+ using.alpha = ui_alpha
+ using.layer = HUD_LAYER_ITEM
+ src.adding += using
+ grab_intent = using
+
+ ico = new(ui_style, "black")
+ ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
+ ico.DrawBox(rgb(255,255,255,1),1,1,ico.Width()/2,ico.Height()/2)
+ using = new /atom/movable/screen()
+ using.name = INTENT_HARM
+ using.icon = ico
+ using.screen_loc = ui_acti
+ using.alpha = ui_alpha
+ using.layer = HUD_LAYER_ITEM
+ src.adding += using
+ hurt_intent = using
+ //end intent small hud objects
+
+ using = new /atom/movable/screen()
+ using.name = "mov_intent"
+ using.icon = ui_style
+ using.icon_state = (mymob.m_intent == "run" ? "running" : "walking")
+ using.screen_loc = ui_movi
+ using.color = ui_color
+ using.alpha = ui_alpha
+ src.adding += using
+ move_intent = using
if(hud_data.has_drop)
using = new /atom/movable/screen()
@@ -145,65 +103,6 @@
using.alpha = ui_alpha
src.hotkeybuttons += using
- if(hud_data.has_hands)
-
- using = new /atom/movable/screen()
- using.name = "equip"
- using.icon = ui_style
- using.icon_state = "act_equip"
- using.screen_loc = ui_equip
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
-
- var/atom/movable/screen/inventory/hand/right/right_hand = new
- right_hand.index = 2
- using = right_hand
- using.hud = src
- using.name = "r_hand"
- using.icon = ui_style
- using.icon_state = "r_hand_inactive"
- if(!target.hand) //This being 0 or null means the right hand is in use
- using.icon_state = "r_hand_active"
- using.screen_loc = ui_rhand
- using.color = ui_color
- using.alpha = ui_alpha
- src.r_hand_hud_object = using
- src.adding += using
- hand_info["2"] = using.screen_loc
-
- var/atom/movable/screen/inventory/hand/left/left_hand = new
- left_hand.index = 1
- using = left_hand
- using.hud = src
- using.name = "l_hand"
- using.icon = ui_style
- using.icon_state = "l_hand_inactive"
- if(target.hand) //This being 1 means the left hand is in use
- using.icon_state = "l_hand_active"
- using.screen_loc = ui_lhand
- using.color = ui_color
- using.alpha = ui_alpha
- src.l_hand_hud_object = using
- src.adding += using
- hand_info["1"] = using.screen_loc
-
- using = new /atom/movable/screen/inventory/swap_hands
- using.icon = ui_style
- using.icon_state = "hand1"
- using.screen_loc = ui_swaphand1
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
-
- using = new /atom/movable/screen/inventory/swap_hands
- using.icon = ui_style
- using.icon_state = "hand2"
- using.screen_loc = ui_swaphand2
- using.color = ui_color
- using.alpha = ui_alpha
- src.adding += using
-
if(hud_data.has_resist)
using = new /atom/movable/screen()
using.name = "resist"
diff --git a/code/game/rendering/legacy/intents/throwing.dm b/code/game/rendering/legacy/intents/throwing.dm
index a66842211956..8ce693cfef7f 100644
--- a/code/game/rendering/legacy/intents/throwing.dm
+++ b/code/game/rendering/legacy/intents/throwing.dm
@@ -10,10 +10,10 @@
/atom/movable/screen/hud/throwmode/shift_clicked(mob/user)
user.toggle_throw_mode(TRUE)
-/atom/movable/screen/hud/update_icon_state()
+/atom/movable/screen/hud/throwmode/update_icon_state()
. = ..()
remove_filter("overhand", FALSE)
- switch(hud?.mymob?.in_throw_mode)
+ switch(hud_legacy?.mymob?.in_throw_mode)
if(THROW_MODE_ON)
icon_state = "act_throw_on"
if(THROW_MODE_OFF)
diff --git a/code/game/rendering/legacy/inventory/inventory.dm b/code/game/rendering/legacy/inventory/inventory.dm
deleted file mode 100644
index 70c331aa297f..000000000000
--- a/code/game/rendering/legacy/inventory/inventory.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/atom/movable/screen/inventory
- name = "inv box"
-
-/atom/movable/screen/inventory/proc/check_inventory_usage(mob/user)
- if(!user.canClick())
- return FALSE
- if(!CHECK_MOBILITY(user, MOBILITY_CAN_STORAGE) || user.stat)
- return FALSE
- return TRUE
-
-/atom/movable/screen/inventory/slot
- /// the ID of this slot
- var/slot_id
-
-/atom/movable/screen/inventory/slot/Click()
- if(!check_inventory_usage(usr))
- return
-
- usr.attack_ui(slot_id)
-
-// Hand slots are special to handle the handcuffs overlay
-/atom/movable/screen/inventory/hand
- /// hand index
- var/index
- /// are we the left hand
- var/is_left_hand = FALSE
- /// current handcuffed overlay
- var/image/handcuff_overlay
-
-/atom/movable/screen/inventory/hand/Click()
- usr.activate_hand_of_index(index)
-
-/atom/movable/screen/inventory/hand/update_icon()
- ..()
- if(!hud)
- return
- if(!handcuff_overlay)
- handcuff_overlay = image(
- "icon" = 'icons/mob/screen_gen.dmi',
- "icon_state" = "[is_left_hand? "l_hand" : "r_hand"]_hud_handcuffs"
- )
- cut_overlays()
- if(iscarbon(hud?.mymob))
- var/mob/living/carbon/C = hud.mymob
- if(C.handcuffed)
- add_overlay(handcuff_overlay)
-
-/atom/movable/screen/inventory/hand/left
- name = "l_hand"
- is_left_hand = TRUE
-
-/atom/movable/screen/inventory/hand/right
- name = "r_hand"
- is_left_hand = FALSE
-
-/atom/movable/screen/inventory/swap_hands
- name = "swap hands"
-
-/atom/movable/screen/inventory/swap_hands/Click()
- usr.swap_hand()
diff --git a/code/game/rendering/mob.dm b/code/game/rendering/mob.dm
index 70a30824f565..bfcd07a224dd 100644
--- a/code/game/rendering/mob.dm
+++ b/code/game/rendering/mob.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
// todo: rendering handling/init/destruction should be on mob and client
// mob side should handle mob state
// client side should handle apply/remove/switch.
@@ -39,3 +42,11 @@
*/
/mob/proc/dispose_rendering()
wipe_fullscreens()
+
+/**
+ * updates rendering on hud style or other appearance change
+ */
+/mob/proc/resync_rendering()
+ if(!client)
+ return
+ client.actor_huds.sync_all_to_preferences(client.legacy_get_hud_preferences())
diff --git a/code/game/rendering/parallax/parallax.dm b/code/game/rendering/parallax/parallax.dm
index dbd27ae762e0..cc3aa7c51b3f 100644
--- a/code/game/rendering/parallax/parallax.dm
+++ b/code/game/rendering/parallax/parallax.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/**
* Holds parallax information.
*/
diff --git a/code/game/rendering/parallax/parallax_holder.dm b/code/game/rendering/parallax/parallax_holder.dm
index e27a152e78bd..01dca8b4c189 100644
--- a/code/game/rendering/parallax/parallax_holder.dm
+++ b/code/game/rendering/parallax/parallax_holder.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/**
* # Parallax holders
*
diff --git a/code/game/rendering/parallax/parallax_object.dm b/code/game/rendering/parallax/parallax_object.dm
index b998484e9fee..05a225ac10fe 100644
--- a/code/game/rendering/parallax/parallax_object.dm
+++ b/code/game/rendering/parallax/parallax_object.dm
@@ -1,3 +1,5 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
/atom/movable/screen/parallax_layer
icon = 'icons/screen/parallax/parallax.dmi'
diff --git a/code/game/rendering/parallax/types/space.dm b/code/game/rendering/parallax/types/space.dm
index 767890f71b6a..fd4518033678 100644
--- a/code/game/rendering/parallax/types/space.dm
+++ b/code/game/rendering/parallax/types/space.dm
@@ -35,15 +35,3 @@
/atom/movable/screen/parallax_layer/space/random/asteroids
icon_state = "asteroids"
-
-// /atom/movable/screen/parallax_layer/space/planet
-// icon_state = "planet"
-// blend_mode = BLEND_OVERLAY
-// absolute = TRUE //Status of seperation
-// speed = 3
-// layer = 30
-// dynamic_self_tile = FALSE
-
-// /atom/movable/screen/parallax_layer/space/planet/ShouldSee(client/C, atom/location)
-// var/turf/T = get_turf(location)
-// return ..() && T && is_station_level(T.z)
diff --git a/code/game/rendering/perspectives/darksight.dm b/code/game/rendering/perspectives/darksight.dm
index cbaa62e51553..d38faf5b79df 100644
--- a/code/game/rendering/perspectives/darksight.dm
+++ b/code/game/rendering/perspectives/darksight.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/atom/movable/screen/darksight_fov
icon = SOFT_DARKSIGHT_15X15_ICON
icon_state = "full-square"
diff --git a/code/game/rendering/perspectives/perspective.dm b/code/game/rendering/perspectives/perspective.dm
index 242f66240cf2..c27e15af7566 100644
--- a/code/game/rendering/perspectives/perspective.dm
+++ b/code/game/rendering/perspectives/perspective.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/**
* MOB PERRSPECTIVE SYSTEM
*
@@ -46,6 +49,15 @@
* however, perspectives are designed to force synchronization of the vars it does trample,
* because there's no better way to do it (because those vars are, semantically, only relevant to our perspective),
* while screen/images can be used for embedded maps/hud/etc.
+ *
+ * ## Use Case
+ *
+ * Perspectives should for the management of an atom's semantic world-view.
+ * This is what they can / can not see around them.
+ * This should not be used for things like inventory HUDs and action buttons.
+ * Those are "internal" viewing / a mob's internal state.
+ *
+ * Basically, these are for what you can see, if you looked through someone's eyes.
*/
/datum/perspective
/// eye - where visual calcs go from
diff --git a/code/game/rendering/perspectives/vision.dm b/code/game/rendering/perspectives/vision.dm
index 88392753ec4d..be4a5e04e0d0 100644
--- a/code/game/rendering/perspectives/vision.dm
+++ b/code/game/rendering/perspectives/vision.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
GLOBAL_LIST_EMPTY(cached_vision_holders)
/proc/cached_vision_holder(datum/vision/path_or_instance)
diff --git a/code/game/rendering/plane_masters/plane_holder.dm b/code/game/rendering/plane_masters/plane_holder.dm
index fcdc77f1b4e5..9e932546137d 100644
--- a/code/game/rendering/plane_masters/plane_holder.dm
+++ b/code/game/rendering/plane_masters/plane_holder.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
/datum/plane_holder
/// plane masters by type
var/list/masters
diff --git a/code/game/rendering/plane_masters/plane_master.dm b/code/game/rendering/plane_masters/plane_master.dm
index 93acb24cf66d..f97de16a0b0f 100644
--- a/code/game/rendering/plane_masters/plane_master.dm
+++ b/code/game/rendering/plane_masters/plane_master.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
/atom/movable/screen/plane_master
icon = null
icon_state = null
diff --git a/code/game/rendering/plane_masters/plane_render.dm b/code/game/rendering/plane_masters/plane_render.dm
index 6cc1ca351c09..68d021eea30c 100644
--- a/code/game/rendering/plane_masters/plane_render.dm
+++ b/code/game/rendering/plane_masters/plane_render.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
/**
* lazy man's render plates, used for specific usecases.
*/
diff --git a/code/game/rendering/screen.dm b/code/game/rendering/screen.dm
index b379d64f7323..8a035b91b981 100644
--- a/code/game/rendering/screen.dm
+++ b/code/game/rendering/screen.dm
@@ -1,482 +1,46 @@
-/*
- Screen objects
- Todo: improve/re-implement
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
- Screen objects are only used for the hud and should not appear anywhere "in-game".
- They are used with the client/screen list and the screen_loc var.
- For more information, see the byond documentation on the screen_loc and screen vars.
-*/
/atom/movable/screen
- name = ""
- icon = 'icons/mob/screen1.dmi'
appearance_flags = PIXEL_SCALE | NO_CLIENT_COLOR
- layer = HUD_LAYER_BASE
- plane = HUD_PLANE
atom_colouration_system = FALSE
+ plane = HUD_PLANE
+ layer = HUD_LAYER_BASE
var/obj/master = null //A reference to the object in the slot. Grabs or items, generally.
- var/datum/hud/hud = null // A reference to the owner HUD, if any.
+ var/datum/hud/hud_legacy = null // A reference to the owner HUD, if any.
+
+/**
+ * called to resync to a hud_style datum
+ */
+/atom/movable/screen/proc/sync_to_preferences(datum/hud_preferences/preference_set)
+ return
/atom/movable/screen/Destroy()
master = null
return ..()
-/atom/movable/screen/text
- icon = null
- icon_state = null
- mouse_opacity = 0
- screen_loc = "CENTER-7,CENTER-7"
- maptext_height = 480
- maptext_width = 480
+//* Default Click Handling *//
+//* At this point in time, all new screen objects should be able to self-sanitize their inputs. *//
+//* This is to prevent security holes from happening when we eventually add the ability to *//
+//* observe another player's POV, including their full UI. *//
-/atom/movable/screen/grab
- name = "grab"
+/atom/movable/screen/Click(location, control, params)
+ if(!check_allowed(usr))
+ return
+ on_click(usr, params2list(params))
-/atom/movable/screen/grab/Click()
- var/obj/item/grab/G = master
- G.s_click(src)
- return 1
+/atom/movable/screen/DblClick(location, control, params)
+ if(!check_allowed(usr))
+ return
+ on_doubleclick(usr, params2list(params))
-/atom/movable/screen/grab/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
+/atom/movable/screen/proc/on_click(mob/user, list/params)
return
-/atom/movable/screen/grab/attackby()
+/atom/movable/screen/proc/on_doubleclick(mob/user, list/params)
return
-/atom/movable/screen/zone_sel
- name = "damage zone"
- icon_state = "zone_sel"
- screen_loc = ui_zonesel
- var/selecting = BP_TORSO
-
-/atom/movable/screen/zone_sel/Click(location, control,params)
- var/list/PL = params2list(params)
- var/icon_x = text2num(PL["icon-x"])
- var/icon_y = text2num(PL["icon-y"])
- var/old_selecting = selecting //We're only going to update_icon() if there's been a change
-
- switch(icon_y)
- if(1 to 3) //Feet
- switch(icon_x)
- if(10 to 15)
- selecting = BP_R_FOOT
- if(17 to 22)
- selecting = BP_L_FOOT
- else
- return 1
- if(4 to 9) //Legs
- switch(icon_x)
- if(10 to 15)
- selecting = BP_R_LEG
- if(17 to 22)
- selecting = BP_L_LEG
- else
- return 1
- if(10 to 13) //Hands and groin
- switch(icon_x)
- if(8 to 11)
- selecting = BP_R_HAND
- if(12 to 20)
- selecting = BP_GROIN
- if(21 to 24)
- selecting = BP_L_HAND
- else
- return 1
- if(14 to 22) //Chest and arms to shoulders
- switch(icon_x)
- if(8 to 11)
- selecting = BP_R_ARM
- if(12 to 20)
- selecting = BP_TORSO
- if(21 to 24)
- selecting = BP_L_ARM
- else
- return 1
- if(23 to 30) //Head, but we need to check for eye or mouth
- if(icon_x in 12 to 20)
- selecting = BP_HEAD
- switch(icon_y)
- if(23 to 24)
- if(icon_x in 15 to 17)
- selecting = O_MOUTH
- if(26) //Eyeline, eyes are on 15 and 17
- if(icon_x in 14 to 18)
- selecting = O_EYES
- if(25 to 27)
- if(icon_x in 15 to 17)
- selecting = O_EYES
-
- if(old_selecting != selecting)
- update_icon()
- return 1
-
-/atom/movable/screen/zone_sel/proc/set_selected_zone(bodypart)
- var/old_selecting = selecting
- selecting = bodypart
- if(old_selecting != selecting)
- update_icon()
-
-/atom/movable/screen/zone_sel/update_icon()
- cut_overlays()
- add_overlay(image('icons/mob/zone_sel.dmi', "[selecting]"))
-
-/// The UI Button to open the TGUI Crafting Menu
-/atom/movable/screen/craft
- name = "crafting menu"
- icon = 'icons/mob/screen/midnight.dmi'
- icon_state = "craft"
- screen_loc = ui_smallquad
-
-/atom/movable/screen/craft/Click(location, control, params)
- var/datum/component/personal_crafting/C = usr.GetComponent(/datum/component/personal_crafting)
- C?.ui_interact(usr)
-
-/atom/movable/screen/Click(location, control, params)
- ..() //Why the FUCK was this not called before
- if(!usr)
- return TRUE
- switch(name)
- if("toggle")
- if(usr.hud_used.inventory_shown)
- usr.hud_used.inventory_shown = 0
- usr.client.screen -= usr.hud_used.other
- else
- usr.hud_used.inventory_shown = 1
- usr.client.screen += usr.hud_used.other
-
- usr.hud_used.hidden_inventory_update()
-
- if("equip")
- if (istype(usr.loc,/obj/vehicle/sealed/mecha)) // stops inventory actions in a mech
- return 1
- if(ishuman(usr))
- var/mob/living/carbon/human/H = usr
- H.quick_equip()
-
- if("resist")
- if(isliving(usr))
- var/mob/living/L = usr
- L.resist()
-
- if("mov_intent")
- // todo: reworks
- if(isliving(usr))
- if(iscarbon(usr))
- var/mob/living/carbon/C = usr
- if(C.legcuffed)
- to_chat(C, "You are legcuffed! You cannot run until you get [C.legcuffed] removed!")
- C.m_intent = "walk" //Just incase
- C.hud_used.move_intent.icon_state = "walking"
- return 1
- var/mob/living/L = usr
- L.toggle_move_intent()
- if("m_intent")
- if(!usr.m_int)
- switch(usr.m_intent)
- if("run")
- usr.m_int = "13,14"
- if("walk")
- usr.m_int = "14,14"
- if("face")
- usr.m_int = "15,14"
- else
- usr.m_int = null
- if("walk")
- usr.m_intent = "walk"
- usr.m_int = "14,14"
- if("face")
- usr.m_intent = "face"
- usr.m_int = "15,14"
- if("run")
- usr.m_intent = "run"
- usr.m_int = "13,14"
- if("Reset Machine")
- usr.unset_machine()
- if("internal")
- if(iscarbon(usr))
- var/mob/living/carbon/C = usr
- if(CHECK_MOBILITY(C, MOBILITY_CAN_USE))
- if(C.internal)
- C.internal = null
- to_chat(C, "No longer running on internals.")
- if(C.internals)
- C.internals.icon_state = "internal0"
- else
-
- var/no_mask
- if(!(C.wear_mask && C.wear_mask.clothing_flags & ALLOWINTERNALS))
- var/mob/living/carbon/human/H = C
- if(!(H.head && H.head.clothing_flags & ALLOWINTERNALS))
- no_mask = 1
-
- if(no_mask)
- to_chat(C, "You are not wearing a suitable mask or helmet.")
- return 1
- else
- // groan. lazy time.
- // location name
- var/list/locnames = list()
- // tank ref, can include nulls! FIRST VALID TANK FROM THIS IS CHOSEN.
- var/list/tanks = list()
- // first, hand
- locnames += "in your hand"
- tanks += C.get_active_held_item()
- // yes, the above can result in duplicates.
- // snowflake rig handling, second highest priority
- if(istype(C.back, /obj/item/hardsuit))
- var/obj/item/hardsuit/R = C.back
- if(R.air_supply && R?.is_activated())
- locnames += "in your hardsuit"
- tanks += R.air_supply
- // now, slots
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- // suit storage
- locnames += "on your suit"
- tanks += H.s_store
- // right/left hands
- locnames += "in your right hand"
- tanks += H.r_hand
- locnames += "in your left hand"
- tanks += H.l_hand
- // pockets
- locnames += "in your left pocket"
- tanks += H.l_store
- locnames += "in your right pocket"
- tanks += H.r_store
- // belt
- locnames += "on your belt"
- tanks += H.belt
- // back
- locnames += "on your back"
- tanks += H.back
- else
- // right/left hands
- locnames += "in your right hand"
- tanks += C.r_hand
- locnames += "in your left hand"
- tanks += C.l_hand
- // back
- locnames += "on your back"
- tanks += C.back
- // no more hugbox and stupid "smart" checks. take the first one we can find and use it. they can use active hand to override if needed.
- for(var/index = 1 to length(tanks))
- if(!istype(tanks[index], /obj/item/tank))
- continue
- C.internal = tanks[index]
- to_chat(C, "You are now running on internals from [tanks[index]] [locnames[index]]")
- if(C.internals)
- C.internals.icon_state = "internal1"
- return
- to_chat(C, "You don't have an internals tank.")
- return
- if("act_intent")
- usr.a_intent_change(INTENT_HOTKEY_RIGHT)
- if(INTENT_HELP)
- usr.a_intent = INTENT_HELP
- usr.hud_used.action_intent.icon_state = "intent_help"
- if(INTENT_HARM)
- usr.a_intent = INTENT_HARM
- usr.hud_used.action_intent.icon_state = "intent_harm"
- if(INTENT_GRAB)
- usr.a_intent = INTENT_GRAB
- usr.hud_used.action_intent.icon_state = "intent_grab"
- if(INTENT_DISARM)
- usr.a_intent = INTENT_DISARM
- usr.hud_used.action_intent.icon_state = "intent_disarm"
-
- if("pull")
- usr.stop_pulling()
- if("drop")
- if(usr.client)
- usr.client.drop_item()
-
- if("module")
- if(isrobot(usr))
- var/mob/living/silicon/robot/R = usr
-// if(R.module)
-// R.hud_used.toggle_show_robot_modules()
-// return 1
- R.pick_module()
-
- if("inventory")
- if(isrobot(usr))
- var/mob/living/silicon/robot/R = usr
- if(R.module)
- R.hud_used.toggle_show_robot_modules()
- return 1
- else
- to_chat(R, "You haven't selected a module yet.")
-
- if("radio")
- if(issilicon(usr))
- usr:radio_menu()
- if("panel")
- if(issilicon(usr))
- usr:installed_modules()
-
- if("store")
- if(isrobot(usr))
- var/mob/living/silicon/robot/R = usr
- if(R.module)
- R.uneq_active()
- R.hud_used.update_robot_modules_display()
- else
- to_chat(R, "You haven't selected a module yet.")
-
- if("module1")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(1)
-
- if("module2")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(2)
-
- if("module3")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(3)
-
- if("AI Core")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.view_core()
-
- if("Show Camera List")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- var/camera = input(AI) in AI.get_camera_list()
- AI.ai_camera_list(camera)
-
- if("Track With Camera")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- var/target_name = input(AI) in AI.trackable_mobs()
- AI.ai_camera_track(target_name)
-
- if("Toggle Camera Light")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.toggle_camera_light()
-
- if("Crew Monitoring")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.subsystem_crew_monitor()
-
- if("Show Crew Manifest")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.ai_roster()
-
- if("Show Alerts")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.subsystem_alarm_monitor()
-
- if("Announcement")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.ai_announcement()
-
- if("Call Emergency Shuttle")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.ai_call_shuttle()
-
- if("State Laws")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.ai_checklaws()
-
- if("PDA - Send Message")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.aiPDA.cmd_send_pdamesg(usr)
-
- if("PDA - Show Message Log")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.aiPDA.cmd_show_message_log(usr)
-
- if("Take Image")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.take_image()
-
- if("View Images")
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- AI.view_images()
- else
- return attempt_vr(src,"Click_vr",list(location,control,params))
- return 1
-
-//! ## VR FILE MERGE ## !//
-
-/atom/movable/screen/proc/Click_vr(location, control, params)
- if(!usr) return 1
- switch(name)
-
- //Shadekin
- if("darkness")
- var/turf/T = get_turf(usr)
- var/darkness = round(1 - T.get_lumcount(),0.1)
- to_chat(usr,"Darkness: [darkness]")
- if("energy")
- var/mob/living/carbon/human/H = usr
- if(istype(H) && istype(H.species, /datum/species/shadekin))
- to_chat(usr,"Energy: [H.shadekin_get_energy(H)]")
-
- if("danger level")
- var/mob/living/carbon/human/H = usr
- if(istype(H) && istype(H.species, /datum/species/shapeshifter/xenochimera))
- if(H.feral > 50)
- to_chat(usr, "You are currently completely feral.")
- else if(H.feral > 10)
- to_chat(usr, "You are currently crazed and confused.")
- else if(H.feral > 0)
- to_chat(usr, "You are currently acting on instinct.")
- else
- to_chat(usr, "You are currently calm and collected.")
- if(H.feral > 0)
- var/feral_passing = TRUE
- if(H.traumatic_shock > min(60, H.nutrition/10))
- to_chat(usr, "Your pain prevents you from regaining focus.")
- feral_passing = FALSE
- if(H.feral + H.nutrition < 150)
- to_chat(usr, "Your hunger prevents you from regaining focus.")
- feral_passing = FALSE
- if(H.jitteriness >= 100)
- to_chat(usr, "Your jitterness prevents you from regaining focus.")
- feral_passing = FALSE
- if(feral_passing)
- var/turf/T = get_turf(H)
- if(T.get_lumcount() <= 0.1)
- to_chat(usr, "You are slowly calming down in darkness' safety...")
- else
- to_chat(usr, "You are slowly calming down... But safety of darkness is much preferred.")
- else
- if(H.nutrition < 150)
- to_chat(usr, "Your hunger is slowly making you unstable.")
-
- else
- return 0
-
- return 1
-
-
-// Character setup stuff
-/atom/movable/screen/setup_preview
-
- var/datum/preferences/pref
-
-/atom/movable/screen/setup_preview/Destroy()
- pref = null
- return ..()
-
-// Background 'floor'
-/atom/movable/screen/setup_preview/bg
- mouse_over_pointer = MOUSE_HAND_POINTER
-
-/atom/movable/screen/setup_preview/bg/Click(params)
- pref?.bgstate = next_list_item(pref.bgstate, pref.bgstate_options)
- pref?.update_character_previews()
+/atom/movable/screen/proc/check_allowed(mob/user)
+ if(hud_legacy?.mymob && user != hud_legacy.mymob)
+ return FALSE
+ return TRUE
diff --git a/code/game/rendering/screen_legacy.dm b/code/game/rendering/screen_legacy.dm
new file mode 100644
index 000000000000..a91aeee53c50
--- /dev/null
+++ b/code/game/rendering/screen_legacy.dm
@@ -0,0 +1,470 @@
+
+/atom/movable/screen/text
+ icon = null
+ icon_state = null
+ mouse_opacity = 0
+ screen_loc = "CENTER-7,CENTER-7"
+ maptext_height = 480
+ maptext_width = 480
+
+/atom/movable/screen/item_action
+ var/obj/item/owner
+
+/atom/movable/screen/item_action/Destroy()
+ . = ..()
+ owner = null
+
+/atom/movable/screen/item_action/Click()
+ if(!usr || !owner)
+ return 1
+ if(!usr.canClick())
+ return
+
+ if(usr.stat || usr.restrained() || !CHECK_MOBILITY(usr, MOBILITY_CAN_USE))
+ return 1
+
+ if(!(owner in usr))
+ return 1
+
+ owner.ui_action_click()
+ return 1
+
+/atom/movable/screen/grab
+ name = "grab"
+
+/atom/movable/screen/grab/Click()
+ var/obj/item/grab/G = master
+ G.s_click(src)
+ return 1
+
+/atom/movable/screen/grab/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
+ return
+
+/atom/movable/screen/grab/attackby()
+ return
+
+/atom/movable/screen/zone_sel
+ name = "damage zone"
+ icon_state = "zone_sel"
+ screen_loc = ui_zonesel
+ var/selecting = BP_TORSO
+
+/atom/movable/screen/zone_sel/Click(location, control,params)
+ var/list/PL = params2list(params)
+ var/icon_x = text2num(PL["icon-x"])
+ var/icon_y = text2num(PL["icon-y"])
+ var/old_selecting = selecting //We're only going to update_icon() if there's been a change
+
+ switch(icon_y)
+ if(1 to 3) //Feet
+ switch(icon_x)
+ if(10 to 15)
+ selecting = BP_R_FOOT
+ if(17 to 22)
+ selecting = BP_L_FOOT
+ else
+ return 1
+ if(4 to 9) //Legs
+ switch(icon_x)
+ if(10 to 15)
+ selecting = BP_R_LEG
+ if(17 to 22)
+ selecting = BP_L_LEG
+ else
+ return 1
+ if(10 to 13) //Hands and groin
+ switch(icon_x)
+ if(8 to 11)
+ selecting = BP_R_HAND
+ if(12 to 20)
+ selecting = BP_GROIN
+ if(21 to 24)
+ selecting = BP_L_HAND
+ else
+ return 1
+ if(14 to 22) //Chest and arms to shoulders
+ switch(icon_x)
+ if(8 to 11)
+ selecting = BP_R_ARM
+ if(12 to 20)
+ selecting = BP_TORSO
+ if(21 to 24)
+ selecting = BP_L_ARM
+ else
+ return 1
+ if(23 to 30) //Head, but we need to check for eye or mouth
+ if(icon_x in 12 to 20)
+ selecting = BP_HEAD
+ switch(icon_y)
+ if(23 to 24)
+ if(icon_x in 15 to 17)
+ selecting = O_MOUTH
+ if(26) //Eyeline, eyes are on 15 and 17
+ if(icon_x in 14 to 18)
+ selecting = O_EYES
+ if(25 to 27)
+ if(icon_x in 15 to 17)
+ selecting = O_EYES
+
+ if(old_selecting != selecting)
+ update_icon()
+ return 1
+
+/atom/movable/screen/zone_sel/proc/set_selected_zone(bodypart)
+ var/old_selecting = selecting
+ selecting = bodypart
+ if(old_selecting != selecting)
+ update_icon()
+
+/atom/movable/screen/zone_sel/update_icon()
+ cut_overlays()
+ add_overlay(image('icons/mob/zone_sel.dmi', "[selecting]"))
+
+/// The UI Button to open the TGUI Crafting Menu
+/atom/movable/screen/craft
+ name = "crafting menu"
+ icon = 'icons/mob/screen/midnight.dmi'
+ icon_state = "craft"
+ screen_loc = ui_smallquad
+
+/atom/movable/screen/craft/Click(location, control, params)
+ var/datum/component/personal_crafting/C = usr.GetComponent(/datum/component/personal_crafting)
+ C?.ui_interact(usr)
+
+/atom/movable/screen/Click(location, control, params)
+ ..() //Why the FUCK was this not called before
+ if(!usr)
+ return TRUE
+ switch(name)
+ if("resist")
+ if(isliving(usr))
+ var/mob/living/L = usr
+ L.resist()
+
+ if("mov_intent")
+ // todo: reworks
+ if(isliving(usr))
+ if(iscarbon(usr))
+ var/mob/living/carbon/C = usr
+ if(C.legcuffed)
+ to_chat(C, "You are legcuffed! You cannot run until you get [C.legcuffed] removed!")
+ C.m_intent = "walk" //Just incase
+ C.hud_used.move_intent.icon_state = "walking"
+ return 1
+ var/mob/living/L = usr
+ L.toggle_move_intent()
+ if("m_intent")
+ if(!usr.m_int)
+ switch(usr.m_intent)
+ if("run")
+ usr.m_int = "13,14"
+ if("walk")
+ usr.m_int = "14,14"
+ if("face")
+ usr.m_int = "15,14"
+ else
+ usr.m_int = null
+ if("walk")
+ usr.m_intent = "walk"
+ usr.m_int = "14,14"
+ if("face")
+ usr.m_intent = "face"
+ usr.m_int = "15,14"
+ if("run")
+ usr.m_intent = "run"
+ usr.m_int = "13,14"
+ if("Reset Machine")
+ usr.unset_machine()
+ if("internal")
+ if(iscarbon(usr))
+ var/mob/living/carbon/C = usr
+ if(CHECK_MOBILITY(C, MOBILITY_CAN_USE))
+ if(C.internal)
+ C.internal = null
+ to_chat(C, "No longer running on internals.")
+ if(C.internals)
+ C.internals.icon_state = "internal0"
+ else
+
+ var/no_mask
+ if(!(C.wear_mask && C.wear_mask.clothing_flags & ALLOWINTERNALS))
+ var/mob/living/carbon/human/H = C
+ if(!(H.head && H.head.clothing_flags & ALLOWINTERNALS))
+ no_mask = 1
+
+ if(no_mask)
+ to_chat(C, "You are not wearing a suitable mask or helmet.")
+ return 1
+ else
+ // groan. lazy time.
+ // location name
+ var/list/locnames = list()
+ // tank ref, can include nulls! FIRST VALID TANK FROM THIS IS CHOSEN.
+ var/list/tanks = list()
+ // first, hand
+ locnames += "in your hand"
+ tanks += C.get_active_held_item()
+ // yes, the above can result in duplicates.
+ // snowflake rig handling, second highest priority
+ if(istype(C.back, /obj/item/hardsuit))
+ var/obj/item/hardsuit/R = C.back
+ if(R.air_supply && R?.is_activated())
+ locnames += "in your hardsuit"
+ tanks += R.air_supply
+ // now, slots
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ // suit storage
+ locnames += "on your suit"
+ tanks += H.s_store
+ // hands
+ for(var/i in 1 to length(H.inventory?.held_items))
+ tanks += H.inventory?.held_items[i]
+ if(i <= 2)
+ locnames += "in your [i == 1? "left" : "right"] hand"
+ else
+ locnames += "in your [ceil(i / 2)](th) [i % 2? "left" : "right"] hand"
+ // pockets
+ locnames += "in your left pocket"
+ tanks += H.l_store
+ locnames += "in your right pocket"
+ tanks += H.r_store
+ // belt
+ locnames += "on your belt"
+ tanks += H.belt
+ // back
+ locnames += "on your back"
+ tanks += H.back
+ else
+ // hands
+ for(var/i in 1 to length(C.inventory?.held_items))
+ tanks += C.inventory?.held_items[i]
+ if(i <= 2)
+ locnames += "in your [i == 1? "left" : "right"] hand"
+ else
+ locnames += "in your [ceil(i / 2)](th) [i % 2? "left" : "right"] hand"
+ // back
+ locnames += "on your back"
+ tanks += C.back
+ // no more hugbox and stupid "smart" checks. take the first one we can find and use it. they can use active hand to override if needed.
+ for(var/index = 1 to length(tanks))
+ if(!istype(tanks[index], /obj/item/tank))
+ continue
+ C.internal = tanks[index]
+ to_chat(C, "You are now running on internals from [tanks[index]] [locnames[index]]")
+ if(C.internals)
+ C.internals.icon_state = "internal1"
+ return
+ to_chat(C, "You don't have an internals tank.")
+ return
+ if("act_intent")
+ usr.a_intent_change(INTENT_HOTKEY_RIGHT)
+ if(INTENT_HELP)
+ usr.a_intent = INTENT_HELP
+ usr.hud_used.action_intent.icon_state = "intent_help"
+ if(INTENT_HARM)
+ usr.a_intent = INTENT_HARM
+ usr.hud_used.action_intent.icon_state = "intent_harm"
+ if(INTENT_GRAB)
+ usr.a_intent = INTENT_GRAB
+ usr.hud_used.action_intent.icon_state = "intent_grab"
+ if(INTENT_DISARM)
+ usr.a_intent = INTENT_DISARM
+ usr.hud_used.action_intent.icon_state = "intent_disarm"
+
+ if("pull")
+ usr.stop_pulling()
+ if("drop")
+ if(usr.client)
+ usr.client.drop_item()
+
+ if("module")
+ if(isrobot(usr))
+ var/mob/living/silicon/robot/R = usr
+// if(R.module)
+// R.hud_used.toggle_show_robot_modules()
+// return 1
+ R.pick_module()
+
+ if("inventory")
+ if(isrobot(usr))
+ var/mob/living/silicon/robot/R = usr
+ if(R.module)
+ R.hud_used.toggle_show_robot_modules()
+ return 1
+ else
+ to_chat(R, "You haven't selected a module yet.")
+
+ if("radio")
+ if(issilicon(usr))
+ usr:radio_menu()
+ if("panel")
+ if(issilicon(usr))
+ usr:installed_modules()
+
+ if("store")
+ if(isrobot(usr))
+ var/mob/living/silicon/robot/R = usr
+ if(R.module)
+ R.uneq_active()
+ R.hud_used.update_robot_modules_display()
+ else
+ to_chat(R, "You haven't selected a module yet.")
+
+ if("module1")
+ if(istype(usr, /mob/living/silicon/robot))
+ usr:toggle_module(1)
+
+ if("module2")
+ if(istype(usr, /mob/living/silicon/robot))
+ usr:toggle_module(2)
+
+ if("module3")
+ if(istype(usr, /mob/living/silicon/robot))
+ usr:toggle_module(3)
+
+ if("AI Core")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.view_core()
+
+ if("Show Camera List")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ var/camera = input(AI) in AI.get_camera_list()
+ AI.ai_camera_list(camera)
+
+ if("Track With Camera")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ var/target_name = input(AI) in AI.trackable_mobs()
+ AI.ai_camera_track(target_name)
+
+ if("Toggle Camera Light")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.toggle_camera_light()
+
+ if("Crew Monitoring")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.subsystem_crew_monitor()
+
+ if("Show Crew Manifest")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.ai_roster()
+
+ if("Show Alerts")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.subsystem_alarm_monitor()
+
+ if("Announcement")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.ai_announcement()
+
+ if("Call Emergency Shuttle")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.ai_call_shuttle()
+
+ if("State Laws")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.ai_checklaws()
+
+ if("PDA - Send Message")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.aiPDA.cmd_send_pdamesg(usr)
+
+ if("PDA - Show Message Log")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.aiPDA.cmd_show_message_log(usr)
+
+ if("Take Image")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.take_image()
+
+ if("View Images")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ AI.view_images()
+ else
+ return attempt_vr(src,"Click_vr",list(location,control,params))
+ return 1
+
+//! ## VR FILE MERGE ## !//
+
+/atom/movable/screen/proc/Click_vr(location, control, params)
+ if(!usr) return 1
+ switch(name)
+
+ //Shadekin
+ if("darkness")
+ var/turf/T = get_turf(usr)
+ var/darkness = round(1 - T.get_lumcount(),0.1)
+ to_chat(usr,"Darkness: [darkness]")
+ if("energy")
+ var/mob/living/carbon/human/H = usr
+ if(istype(H) && istype(H.species, /datum/species/shadekin))
+ to_chat(usr,"Energy: [H.shadekin_get_energy(H)]")
+
+ if("danger level")
+ var/mob/living/carbon/human/H = usr
+ if(istype(H) && istype(H.species, /datum/species/shapeshifter/xenochimera))
+ if(H.feral > 50)
+ to_chat(usr, "You are currently completely feral.")
+ else if(H.feral > 10)
+ to_chat(usr, "You are currently crazed and confused.")
+ else if(H.feral > 0)
+ to_chat(usr, "You are currently acting on instinct.")
+ else
+ to_chat(usr, "You are currently calm and collected.")
+ if(H.feral > 0)
+ var/feral_passing = TRUE
+ if(H.traumatic_shock > min(60, H.nutrition/10))
+ to_chat(usr, "Your pain prevents you from regaining focus.")
+ feral_passing = FALSE
+ if(H.feral + H.nutrition < 150)
+ to_chat(usr, "Your hunger prevents you from regaining focus.")
+ feral_passing = FALSE
+ if(H.jitteriness >= 100)
+ to_chat(usr, "Your jitterness prevents you from regaining focus.")
+ feral_passing = FALSE
+ if(feral_passing)
+ var/turf/T = get_turf(H)
+ if(T.get_lumcount() <= 0.1)
+ to_chat(usr, "You are slowly calming down in darkness' safety...")
+ else
+ to_chat(usr, "You are slowly calming down... But safety of darkness is much preferred.")
+ else
+ if(H.nutrition < 150)
+ to_chat(usr, "Your hunger is slowly making you unstable.")
+
+ else
+ return 0
+
+ return 1
+
+
+// Character setup stuff
+/atom/movable/screen/setup_preview
+
+ var/datum/preferences/pref
+
+/atom/movable/screen/setup_preview/Destroy()
+ pref = null
+ return ..()
+
+// Background 'floor'
+/atom/movable/screen/setup_preview/bg
+ mouse_over_pointer = MOUSE_HAND_POINTER
+
+/atom/movable/screen/setup_preview/bg/Click(params)
+ pref?.bgstate = next_list_item(pref.bgstate, pref.bgstate_options)
+ pref?.update_character_previews()
diff --git a/code/game/turfs/simulated/wall/wall_attacks.dm b/code/game/turfs/simulated/wall/wall_attacks.dm
index f1e0e50482c2..7d4c775c6a48 100644
--- a/code/game/turfs/simulated/wall/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall/wall_attacks.dm
@@ -75,6 +75,9 @@
return 0
/turf/simulated/wall/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
+ . = ..()
+ if(.)
+ return
add_fingerprint(user)
user.setClickCooldown(user.get_attack_speed())
var/rotting = (locate(/obj/effect/overlay/wallrot) in src)
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index f2a8e237c65e..45bf675ac64d 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -55,7 +55,7 @@
if((MUTATION_CLUMSY in user.mutations) && prob(50))
var/which_hand = "l_hand"
var/mob/living/carbon/human/H = ishuman(user)? user : null
- if(!H?.hand)
+ if(!(H?.active_hand % 2))
which_hand = "r_hand"
triggered(user, which_hand)
user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
@@ -74,9 +74,7 @@
return
if(armed)
if((MUTATION_CLUMSY in user.mutations) && prob(50))
- var/which_hand = "l_hand"
- if(!L.hand)
- which_hand = "r_hand"
+ var/which_hand = user.active_hand % 2? "l_hand" : "r_hand"
triggered(user, which_hand)
user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
"You accidentally trigger [src]!")
@@ -98,7 +96,6 @@
triggered(AM)
..()
-
/obj/item/assembly/mousetrap/on_containing_storage_opening(datum/event_args/actor/actor, datum/object_system/storage/storage)
. = ..()
@@ -108,11 +105,10 @@
if(armed)
finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
"You accidentally trigger [src]!")
- triggered(finder, finder.hand ? "l_hand" : "r_hand")
+ triggered(finder, finder.active_hand % 2? "l_hand" : "r_hand")
return 1 //end the search!
return 0
-
/obj/item/assembly/mousetrap/throw_impacted(atom/movable/AM, datum/thrownthing/TT)
. = ..()
if(!armed)
diff --git a/code/modules/client/client.dm b/code/modules/client/client.dm
index 7c36b27ebb5c..9bbccd20ea69 100644
--- a/code/modules/client/client.dm
+++ b/code/modules/client/client.dm
@@ -136,6 +136,8 @@
//* UI - Map *//
/// Our action drawer
var/datum/action_drawer/action_drawer
+ /// Our actor HUD holder
+ var/datum/actor_hud_holder/actor_huds
////////////////
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 138744a62a74..bb1edf8d647a 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -221,20 +221,18 @@
preferences.on_reconnect()
//? END ?//
- //* Create UI *//
+ //* Create interface UI *//
// todo: move top level menu here, for now it has to be under prefs.
- // Instantiate statpanel
tgui_stat = new(src, SKIN_BROWSER_ID_STAT)
- // Instantiate tgui panel
tgui_panel = new(src, SKIN_BROWSER_ID_CHAT)
// Instantiate cutscene system
spawn(1)
init_cutscene_system()
- // instantiate tooltips
tooltips = new(src)
- // start action drawer
+
+ //* Setup on-map HUDs *//
action_drawer = new(src)
- // make action holder
+ actor_huds = new(src)
action_holder = new /datum/action_holder/client_actor(src)
action_drawer.register_holder(action_holder)
@@ -434,20 +432,19 @@
active_mousedown_item = null
- //* cleanup rendering
- // clear perspective
+ //* Cleanup rendering *//
if(using_perspective)
set_perspective(null)
- // clear HUDs
clear_atom_hud_providers()
- //* cleanup client UI *//
+ //* Cleanup interface UI *//
QDEL_NULL(tgui_stat)
cleanup_cutscene_system()
QDEL_NULL(tgui_panel)
QDEL_NULL(tooltips)
- //* cleanup map UI *//
+ //* Cleanup on-map HUDs *//
+ QDEL_NULL(actor_huds)
QDEL_NULL(action_holder)
QDEL_NULL(action_drawer)
diff --git a/code/modules/client/game_preferences/entries/game.dm b/code/modules/client/game_preferences/entries/game.dm
index e409bbc6037e..1f4caff7eb73 100644
--- a/code/modules/client/game_preferences/entries/game.dm
+++ b/code/modules/client/game_preferences/entries/game.dm
@@ -18,6 +18,7 @@
/datum/game_preference_entry/dropdown/hud_style/on_set(client/user, value, first_init)
. = ..()
user.set_ui_style(value)
+ user.mob.resync_rendering()
/datum/game_preference_entry/simple_color/hud_color
name = "HUD Color"
@@ -31,6 +32,7 @@
/datum/game_preference_entry/simple_color/hud_color/on_set(client/user, value, first_init)
. = ..()
user.set_ui_color(value)
+ user.mob.resync_rendering()
/datum/game_preference_entry/number/hud_alpha
name = "HUD Alpha"
@@ -47,6 +49,7 @@
/datum/game_preference_entry/number/hud_alpha/on_set(client/user, value, first_init)
. = ..()
user.set_ui_alpha(value)
+ user.mob.resync_rendering()
/datum/game_preference_entry/dropdown/tooltip_style
name = "Tooltips Style"
diff --git a/code/modules/client/ui_style.dm b/code/modules/client/ui_style.dm
index c5d18f8c4a01..27eae2fcbaf7 100644
--- a/code/modules/client/ui_style.dm
+++ b/code/modules/client/ui_style.dm
@@ -7,9 +7,17 @@
UI_STYLE_ORANGE = 'icons/mob/screen/orange.dmi',
UI_STYLE_OLD = 'icons/mob/screen/old.dmi',
UI_STYLE_WHITE = 'icons/mob/screen/white.dmi',
- UI_STYLE_OLD_NOBORDER = 'icons/mob/screen/old-noborder.dmi',
UI_STYLE_MINIMALIST = 'icons/mob/screen/minimalist.dmi',
- UI_STYLE_HOLOGRAM = 'icons/mob/screen/holo.dmi'
+ UI_STYLE_HOLOGRAM = 'icons/mob/screen/holo.dmi',
+ )
+
+/var/all_ui_style_ids = list(
+ UI_STYLE_MIDNIGHT = "midnight",
+ UI_STYLE_ORANGE = "orange",
+ UI_STYLE_OLD = "old",
+ UI_STYLE_WHITE = "white",
+ UI_STYLE_MINIMALIST = "minimalist",
+ UI_STYLE_HOLOGRAM = "holo",
)
/var/global/list/all_ui_styles_robot = list(
@@ -17,9 +25,8 @@
UI_STYLE_ORANGE = 'icons/mob/screen1_robot.dmi',
UI_STYLE_OLD = 'icons/mob/screen1_robot.dmi',
UI_STYLE_WHITE = 'icons/mob/screen1_robot.dmi',
- UI_STYLE_OLD_NOBORDER = 'icons/mob/screen1_robot.dmi',
UI_STYLE_MINIMALIST = 'icons/mob/screen1_robot_minimalist.dmi',
- UI_STYLE_HOLOGRAM = 'icons/mob/screen1_robot_minimalist.dmi'
+ UI_STYLE_HOLOGRAM = 'icons/mob/screen1_robot_minimalist.dmi',
)
var/global/list/all_tooltip_styles = list(
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index f1c6e2033a18..a0ae42c236c8 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -400,10 +400,7 @@
desc = "It's a desert eagle."
icon_state = "deagle"
update_icon()
- if (ismob(src.loc))
- var/mob/M = src.loc
- M.update_inv_r_hand()
- M.update_inv_l_hand()
+ update_worn_icon()
/obj/item/gun/energy/chameleon/disguise(var/newtype)
var/obj/item/gun/copy = ..()
@@ -434,9 +431,4 @@
return
disguise(gun_choices[picked])
-
- //so our overlays update.
- if (ismob(src.loc))
- var/mob/M = src.loc
- M.update_inv_r_hand()
- M.update_inv_l_hand()
+ update_worn_icon()
diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm
index 74d5a627e049..4667527d1384 100644
--- a/code/modules/clothing/clothing_accessories.dm
+++ b/code/modules/clothing/clothing_accessories.dm
@@ -1,4 +1,4 @@
-/obj/item/clothing/_inv_return_attached()
+/obj/item/clothing/inv_slot_attached()
if(!accessories)
return ..()
. = ..()
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 2e13510f305a..5793fc0b4e91 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -305,7 +305,7 @@
/obj/item/clothing/suit/straight_jacket/equipped(var/mob/living/user,var/slot)
. = ..()
if(slot == SLOT_ID_SUIT)
- user.drop_all_held_items()
+ user.drop_held_items()
user.drop_item_to_ground(user.item_by_slot_id(SLOT_ID_HANDCUFFED), INV_OP_FORCE)
/obj/item/clothing/suit/ianshirt
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 93f79ff03da4..c93bdf60f8a4 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -35,51 +35,51 @@ Gunshots/explosions/opening doors/less rare audio (done)
if(16 to 25)
//Strange items
//to_chat(src, "Traitor Items")
- if(!halitem)
- halitem = new
- var/list/slots_free = list(ui_lhand,ui_rhand)
- if(l_hand) slots_free -= ui_lhand
- if(r_hand) slots_free -= ui_rhand
- if(istype(src,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = src
- if(!H.belt) slots_free += ui_belt
- if(!H.l_store) slots_free += ui_storage1
- if(!H.r_store) slots_free += ui_storage2
- if(slots_free.len)
- halitem.screen_loc = pick(slots_free)
- halitem.hud_layerise()
- switch(rand(1,6))
- if(1) //revolver
- halitem.icon = 'icons/obj/gun/ballistic.dmi'
- halitem.icon_state = "revolver"
- halitem.name = "Revolver"
- if(2) //c4
- halitem.icon = 'icons/obj/assemblies.dmi'
- halitem.icon_state = "plastic-explosive0"
- halitem.name = "Mysterious Package"
- if(prob(25))
- halitem.icon_state = "c4small_1"
- if(3) //sword
- halitem.icon = 'icons/obj/weapons.dmi'
- halitem.icon_state = "sword1"
- halitem.name = "Sword"
- if(4) //stun baton
- halitem.icon = 'icons/obj/weapons.dmi'
- halitem.icon_state = "stunbaton"
- halitem.name = "Stun Baton"
- if(5) //emag
- halitem.icon = 'icons/obj/card.dmi'
- halitem.icon_state = "emag"
- halitem.name = "Cryptographic Sequencer"
- if(6) //flashbang
- halitem.icon = 'icons/obj/grenade.dmi'
- halitem.icon_state = "flashbang1"
- halitem.name = "Flashbang"
- if(client) client.screen += halitem
- spawn(rand(100,250))
- if(client)
- client.screen -= halitem
- halitem = null
+ // if(!halitem)
+ // halitem = new
+ // var/list/slots_free = list()
+ // for(var/i in get_empty_hand_indices())
+ // slots_free += SCREEN_LOC_INV_HAND(i)
+ // if(istype(src,/mob/living/carbon/human))
+ // var/mob/living/carbon/human/H = src
+ // if(!H.belt) slots_free += ui_belt
+ // if(!H.l_store) slots_free += ui_storage1
+ // if(!H.r_store) slots_free += ui_storage2
+ // if(slots_free.len)
+ // halitem.screen_loc = pick(slots_free)
+ // halitem.hud_layerise()
+ // switch(rand(1,6))
+ // if(1) //revolver
+ // halitem.icon = 'icons/obj/gun/ballistic.dmi'
+ // halitem.icon_state = "revolver"
+ // halitem.name = "Revolver"
+ // if(2) //c4
+ // halitem.icon = 'icons/obj/assemblies.dmi'
+ // halitem.icon_state = "plastic-explosive0"
+ // halitem.name = "Mysterious Package"
+ // if(prob(25))
+ // halitem.icon_state = "c4small_1"
+ // if(3) //sword
+ // halitem.icon = 'icons/obj/weapons.dmi'
+ // halitem.icon_state = "sword1"
+ // halitem.name = "Sword"
+ // if(4) //stun baton
+ // halitem.icon = 'icons/obj/weapons.dmi'
+ // halitem.icon_state = "stunbaton"
+ // halitem.name = "Stun Baton"
+ // if(5) //emag
+ // halitem.icon = 'icons/obj/card.dmi'
+ // halitem.icon_state = "emag"
+ // halitem.name = "Cryptographic Sequencer"
+ // if(6) //flashbang
+ // halitem.icon = 'icons/obj/grenade.dmi'
+ // halitem.icon_state = "flashbang1"
+ // halitem.name = "Flashbang"
+ // if(client) client.screen += halitem
+ // spawn(rand(100,250))
+ // if(client)
+ // client.screen -= halitem
+ // halitem = null
if(26 to 40)
//Flashes of danger
//to_chat(src, "Danger Flash")
@@ -325,23 +325,6 @@ proc/check_panel(mob/M)
SEND_IMAGE(target, I)
spawn(300)
qdel(O)
- return
-
-GLOBAL_LIST_INIT(non_fakeattack_weapons, list(/obj/item/gun/ballistic, /obj/item/ammo_magazine/a357/speedloader,\
- /obj/item/gun/energy/crossbow, /obj/item/melee/transforming/energy/sword,\
- /obj/item/storage/box/syndicate, /obj/item/storage/box/emps,\
- /obj/item/cartridge/syndicate, /obj/item/clothing/under/chameleon,\
- /obj/item/clothing/shoes/syndigaloshes, /obj/item/card/id/syndicate,\
- /obj/item/clothing/mask/gas/voice, /obj/item/clothing/glasses/thermal,\
- /obj/item/chameleon, /obj/item/card/emag,\
- /obj/item/storage/toolbox/syndicate, /obj/item/aiModule,\
- /obj/item/radio/headset/syndicate, /obj/item/plastique,\
- /obj/item/powersink, /obj/item/storage/box/syndie_kit,\
- /obj/item/toy/syndicateballoon, /obj/item/gun/energy/captain,\
- /obj/item/hand_tele, /obj/item/rcd, /obj/item/tank/jetpack,\
- /obj/item/clothing/under/rank/captain, /obj/item/aicard,\
- /obj/item/clothing/shoes/magboots, /obj/item/blueprints, /obj/item/disk/nuclear,\
- /obj/item/clothing/suit/space/void, /obj/item/tank))
/proc/fake_attack(var/mob/living/target)
var/list/possible_clones = new/list()
@@ -363,14 +346,8 @@ GLOBAL_LIST_INIT(non_fakeattack_weapons, list(/obj/item/gun/ballistic, /obj/item
//var/obj/effect/fake_attacker/F = new/obj/effect/fake_attacker(outside_range(target))
var/obj/effect/fake_attacker/F = new/obj/effect/fake_attacker(target.loc)
- if(clone.l_hand)
- if(!(locate(clone.l_hand) in GLOB.non_fakeattack_weapons))
- clone_weapon = clone.l_hand.name
- F.weap = clone.l_hand
- else if (clone.r_hand)
- if(!(locate(clone.r_hand) in GLOB.non_fakeattack_weapons))
- clone_weapon = clone.r_hand.name
- F.weap = clone.r_hand
+ // meta-able but whatever we can redo later.
+ F.weap = clone.get_active_held_item() || clone.get_inactive_held_item()
F.name = clone.name
F.my_target = target
diff --git a/code/modules/frames/frame.dm b/code/modules/frames/frame.dm
index 395c5beada8c..b6628a0e079f 100644
--- a/code/modules/frames/frame.dm
+++ b/code/modules/frames/frame.dm
@@ -237,7 +237,7 @@ GLOBAL_LIST_INIT(frame_datum_lookup, init_frame_datums())
var/obj/item/frame2/collapsed
if(actor?.performer && put_in_hand_if_possible)
collapsed = new(actor.performer, src)
- actor.performer.put_in_hand_or_drop(collapsed)
+ actor.performer.put_in_hands_or_drop(collapsed)
else
collapsed = new(frame.drop_location(), src)
return collapsed
diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm
index 154fa03fb807..825fd26565dc 100644
--- a/code/modules/games/cards.dm
+++ b/code/modules/games/cards.dm
@@ -85,7 +85,7 @@
if(usr.stat || !Adjacent(usr)) return
- if(user.hands_full()) // Safety check lest the card disappear into oblivion
+ if(user.are_usable_hands_full()) // Safety check lest the card disappear into oblivion
to_chat(user,"Your hands are full!")
return
@@ -245,13 +245,7 @@
if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
if(!istype(usr, /mob/living/simple_mob))
if( !usr.get_active_held_item() ) //if active hand is empty
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
-
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user,"You try to move your [temp.name], but cannot!")
+ if(!user.standard_hand_usability_check(src, user.active_hand, HAND_MANIPULATION_GENERAL))
return
to_chat(user,"You pick up [src].")
@@ -261,13 +255,7 @@
if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
if(!istype(usr, /mob/living/simple_mob))
if( !usr.get_active_held_item() ) //if active hand is empty
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
-
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user,"You try to move your [temp.name], but cannot!")
+ if(!user.standard_hand_usability_check(src, user.active_hand, HAND_MANIPULATION_GENERAL))
return
to_chat(user,"You pick up [src].")
@@ -375,7 +363,7 @@
if(user.stat || !Adjacent(user)) return
- if(user.hands_full()) // Safety check lest the card disappear into oblivion
+ if(user.are_usable_hands_full()) // Safety check lest the card disappear into oblivion
to_chat(usr,"Your hands are full!")
return
diff --git a/code/modules/hardsuits/modules/combat.dm b/code/modules/hardsuits/modules/combat.dm
index 66cdec37be3d..6f0229a74635 100644
--- a/code/modules/hardsuits/modules/combat.dm
+++ b/code/modules/hardsuits/modules/combat.dm
@@ -214,7 +214,7 @@
var/mob/living/M = holder.wearer
- if(M.l_hand && M.r_hand)
+ if(M.are_usable_hands_full())
to_chat(M, "Your hands are full.")
deactivate()
return
@@ -266,7 +266,7 @@
H.visible_message("[H] launches \a [firing]!")
firing.throw_at_old(target,fire_force,fire_distance)
else
- if(H.l_hand && H.r_hand)
+ if(H.are_usable_hands_full())
to_chat(H, "Your hands are full.")
else
var/obj/item/new_weapon = new fabrication_type()
diff --git a/code/modules/hardsuits/modules/utility.dm b/code/modules/hardsuits/modules/utility.dm
index 621dd28c09f5..f5fb2a8f031e 100644
--- a/code/modules/hardsuits/modules/utility.dm
+++ b/code/modules/hardsuits/modules/utility.dm
@@ -462,7 +462,7 @@
var/mob/living/M = holder.wearer
- if(M.l_hand && M.r_hand)
+ if(M.are_usable_hands_full())
to_chat(M, "Your hands are full.")
deactivate()
return
diff --git a/code/modules/hardsuits/rig_attackby.dm b/code/modules/hardsuits/rig_attackby.dm
index 2e85da1c35e7..bd6bb2dec671 100644
--- a/code/modules/hardsuits/rig_attackby.dm
+++ b/code/modules/hardsuits/rig_attackby.dm
@@ -102,10 +102,7 @@
to_chat(user, "There is no tank to remove.")
return
- if(user.r_hand && user.l_hand)
- air_supply.forceMove(get_turf(user))
- else
- user.put_in_hands(air_supply)
+ user.put_in_hands_or_drop(air_supply)
to_chat(user, "You detach and remove \the [air_supply].")
air_supply = null
return
diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm
index 4010d4dd9bae..700f6a017525 100644
--- a/code/modules/holodeck/HolodeckObjects.dm
+++ b/code/modules/holodeck/HolodeckObjects.dm
@@ -280,10 +280,7 @@
cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
if(active)
add_overlay(blade_overlay)
- if(istype(usr,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = usr
- H.update_inv_l_hand()
- H.update_inv_r_hand()
+ update_worn_icon()
//BASKETBALL OBJECTS
diff --git a/code/modules/integrated_electronics/core/tools.dm b/code/modules/integrated_electronics/core/tools.dm
index 536f2de027e1..b50faba559f2 100644
--- a/code/modules/integrated_electronics/core/tools.dm
+++ b/code/modules/integrated_electronics/core/tools.dm
@@ -248,11 +248,12 @@
var/mode = 0
/obj/item/multitool/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
if(selected_io)
selected_io = null
to_chat(user, "You clear the wired connection from the multitool.")
- else
- ..()
update_icon()
/obj/item/multitool/update_icon()
diff --git a/code/modules/jobs/job_types/station/civillian/chaplain.dm b/code/modules/jobs/job_types/station/civillian/chaplain.dm
index 778b9591319f..f264ddc9ed28 100644
--- a/code/modules/jobs/job_types/station/civillian/chaplain.dm
+++ b/code/modules/jobs/job_types/station/civillian/chaplain.dm
@@ -146,7 +146,7 @@
else
B.icon_state = "bible"
B.item_state = "bible"
- H.update_inv_l_hand() // so that it updates the bible's item_state in his hand
+ B.update_worn_icon()
switch(input(H,"Look at your bible - is this what you want?") in list("Yes","No"))
if("Yes")
accepted = 1
diff --git a/code/modules/loadout/loadout_entry.dm b/code/modules/loadout/loadout_entry.dm
index de91d7ea0b29..213281ef066c 100644
--- a/code/modules/loadout/loadout_entry.dm
+++ b/code/modules/loadout/loadout_entry.dm
@@ -95,6 +95,8 @@ var/list/gear_datums = list()
where = tweak.tweak_spawn_location(where, tweak_data)
path = tweak.tweak_spawn_path(path, tweak_data)
tweak_assembled[tweak] = tweak_data
+ if(!path)
+ CRASH("[src] ([src.type]) attempted to spawn a null path with data: '[json_encode(entry_data)]'.")
var/obj/item/spawned = new path(where)
if((loadout_customize_flags & LOADOUT_CUSTOMIZE_NAME) && entry_data[LOADOUT_ENTRYDATA_RENAME])
spawned.name = entry_data[LOADOUT_ENTRYDATA_RENAME]
diff --git a/code/modules/maps/overmap/space/talon/talon_jobs.dm b/code/modules/maps/overmap/space/talon/talon_jobs.dm
index 1c685fa589d3..32867c29ee46 100644
--- a/code/modules/maps/overmap/space/talon/talon_jobs.dm
+++ b/code/modules/maps/overmap/space/talon/talon_jobs.dm
@@ -58,15 +58,9 @@
selection_color = "#aaaaaa"
minimal_player_age = 14
pto_type = null
-<<<<<<< HEAD
- access = list(ACCESS_FACTION_TALON)
- minimal_access = list(ACCESS_FACTION_TALON)
- alt_titles = list("Talon Medic" = /datum/alt_title/talon_medic)
-=======
access = list(access_talon)
minimal_access = list(access_talon)
alt_titles = list("Talon Medic" = /datum/prototype/struct/alt_title/talon_medic)
->>>>>>> citrp/master
/datum/prototype/struct/alt_title/talon_medic
title = "Talon Medic"
@@ -88,15 +82,9 @@
selection_color = "#aaaaaa"
minimal_player_age = 14
pto_type = null
-<<<<<<< HEAD
- access = list(ACCESS_FACTION_TALON)
- minimal_access = list(ACCESS_FACTION_TALON)
- alt_titles = list("Talon Technician" = /datum/alt_title/talon_tech)
-=======
access = list(access_talon)
minimal_access = list(access_talon)
alt_titles = list("Talon Technician" = /datum/prototype/struct/alt_title/talon_tech)
->>>>>>> citrp/master
/datum/prototype/struct/alt_title/talon_tech
title = "Talon Technician"
@@ -118,15 +106,9 @@
selection_color = "#aaaaaa"
minimal_player_age = 14
pto_type = null
-<<<<<<< HEAD
- access = list(ACCESS_FACTION_TALON)
- minimal_access = list(ACCESS_FACTION_TALON)
- alt_titles = list("Talon Helmsman" = /datum/alt_title/talon_helmsman)
-=======
access = list(access_talon)
minimal_access = list(access_talon)
alt_titles = list("Talon Helmsman" = /datum/prototype/struct/alt_title/talon_helmsman)
->>>>>>> citrp/master
/datum/prototype/struct/alt_title/talon_helmsman
title = "Talon Helmsman"
@@ -148,15 +130,9 @@
selection_color = "#aaaaaa"
minimal_player_age = 14
pto_type = null
-<<<<<<< HEAD
- access = list(ACCESS_FACTION_TALON)
- minimal_access = list(ACCESS_FACTION_TALON)
- alt_titles = list("Talon Security" = /datum/alt_title/talon_security)
-=======
access = list(access_talon)
minimal_access = list(access_talon)
alt_titles = list("Talon Security" = /datum/prototype/struct/alt_title/talon_security)
->>>>>>> citrp/master
/datum/prototype/struct/alt_title/talon_security
title = "Talon Security"
diff --git a/code/modules/mob/_modifiers/traits_phobias.dm b/code/modules/mob/_modifiers/traits_phobias.dm
index e3d33f6193b8..46bc48eaad77 100644
--- a/code/modules/mob/_modifiers/traits_phobias.dm
+++ b/code/modules/mob/_modifiers/traits_phobias.dm
@@ -130,7 +130,7 @@
human_blood_fear_amount += 1
// List of slots. Some slots like pockets are omitted due to not being visible, if H isn't the holder.
- var/list/clothing_slots = list(H.back, H.wear_mask, H.l_hand, H.r_hand, H.wear_id, H.glasses, H.gloves, H.head, H.shoes, H.belt, H.wear_suit, H.w_uniform, H.s_store, H.l_ear, H.r_ear)
+ var/list/clothing_slots = list(H.back, H.wear_mask, H.wear_id, H.glasses, H.gloves, H.head, H.shoes, H.belt, H.wear_suit, H.w_uniform, H.s_store, H.l_ear, H.r_ear) + H.get_held_items()
if(H == holder)
clothing_slots += list(H.l_store, H.r_store)
@@ -527,16 +527,14 @@
if(istype(thing, /mob/living/carbon/human))
var/mob/living/carbon/human/H = thing
- if(H.l_hand && istype(H.l_hand, /obj/item/reagent_containers/syringe) || H.r_hand && istype(H.r_hand, /obj/item/reagent_containers/syringe))
+ if(H.get_held_item_of_type(/obj/item/reagent_containers/syringe))
fear_amount += 10
if(H.l_ear && istype(H.l_ear, /obj/item/reagent_containers/syringe) || H.r_ear && istype(H.r_ear, /obj/item/reagent_containers/syringe))
- fear_amount +=10
-
+ fear_amount += 10
return fear_amount
-
// Note for the below 'phobias' are of the xeno-phobic variety, and are less centered on pure fear as above, and more on a mix of distrust, fear, and disdainfulness.
// As such, they are mechanically different than the fear-based phobias, in that instead of a buildup of fearful messages, it does intermittent messages specific to what holder sees.
diff --git a/code/modules/mob/grab.dm b/code/modules/mob/grab.dm
index 2c937c8129df..0ceb80369444 100644
--- a/code/modules/mob/grab.dm
+++ b/code/modules/mob/grab.dm
@@ -7,6 +7,7 @@
for(var/obj/item/grab/G in get_held_items())
.[G.affecting] = G.state
+
/**
* returns everyone we're grabbing that are at least the given grab state
*/
@@ -62,10 +63,11 @@
/**
* are we being grabbed
*
- * @return TRUE / FALSE
+ * @return null, or highest state
*/
/mob/proc/is_grabbed()
- return length(grabbed_by)
+ for(var/mob/M as anything in grabbed_by)
+ . = max(., M.check_grab(src))
/**
* are we being grabbed by someone
@@ -89,7 +91,7 @@
L.resist() //shortcut for resisting grabs
//if we are grabbing someone
- for(var/obj/item/grab/G in list(L.l_hand, L.r_hand))
+ for(var/obj/item/grab/G in L.get_held_items_of_type(/obj/item/grab))
G.reset_kill_state() //no wandering across the station/asteroid while choking someone
/obj/item/grab
@@ -137,6 +139,7 @@
icon_state = "grabbed"
hud.name = "reinforce grab"
hud.master = src
+ vis_contents += hud
//check if assailant is grabbed by victim as well
if(assailant.grabbed_by)
@@ -152,16 +155,6 @@
adjust_position()
-//This makes sure that the grab screen object is displayed in the correct hand.
-/obj/item/grab/proc/synch() //why is this needed?
- if(QDELETED(src))
- return
- if(affecting)
- if(assailant.r_hand == src)
- hud.screen_loc = ui_rhand
- else
- hud.screen_loc = ui_lhand
-
/obj/item/grab/process(delta_time)
if(QDELETED(src)) // GC is trying to delete us, we'll kill our processing so we can cleanly GC
return PROCESS_KILL
@@ -178,14 +171,8 @@
if(state <= GRAB_AGGRESSIVE)
allow_upgrade = 1
//disallow upgrading if we're grabbing more than one person
- if((assailant.l_hand && assailant.l_hand != src && istype(assailant.l_hand, /obj/item/grab)))
- var/obj/item/grab/G = assailant.l_hand
- if(G.affecting != affecting)
- allow_upgrade = 0
- if((assailant.r_hand && assailant.r_hand != src && istype(assailant.r_hand, /obj/item/grab)))
- var/obj/item/grab/G = assailant.r_hand
- if(G.affecting != affecting)
- allow_upgrade = 0
+ if(length(assailant.get_grabbing()) > 1)
+ allow_upgrade = 0
//disallow upgrading past aggressive if we're being grabbed aggressively
for(var/obj/item/grab/G in affecting.grabbed_by)
@@ -202,7 +189,7 @@
hud.icon_state = "!reinforce"
if(state >= GRAB_AGGRESSIVE)
- affecting.drop_all_held_items()
+ affecting.drop_held_items()
if(iscarbon(affecting))
handle_eye_mouth_covering(affecting, assailant, assailant.zone_sel.selecting)
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index 13d7a7147657..f7971dab6c18 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -72,7 +72,8 @@
// appearance clone their ass
var/mutable_appearance/MA = new
MA.appearance = M
- MA.plane = plane
+ MA.plane = FLOAT_PLANE
+ MA.layer = FLOAT_LAYER
MA.dir = SOUTH
add_overlay(MA)
name = M.name
diff --git a/code/modules/mob/inventory/hands.dm b/code/modules/mob/inventory/hands.dm
deleted file mode 100644
index 67c8d278b1d4..000000000000
--- a/code/modules/mob/inventory/hands.dm
+++ /dev/null
@@ -1,211 +0,0 @@
-// todo: we need a set of 'core' procs subtypes need to override, and the rest are composites of those procs.
-
-/mob/proc/put_in_hands(obj/item/I, flags)
- if(is_holding(I))
- return TRUE
-
- if(!(flags & INV_OP_NO_MERGE_STACKS) && istype(I, /obj/item/stack))
- var/obj/item/stack/S = I
- for(var/obj/item/stack/held_stack in get_held_items())
- if(S.can_merge(held_stack) && S.merge(held_stack))
- to_chat(src, SPAN_NOTICE("Your [held_stack] stack now contains [held_stack.get_amount()] [held_stack.singular_name]\s."))
- return TRUE
-
- return put_in_active_hand(I, flags) || put_in_inactive_hand(I, flags)
-
-/**
- * put in hands or forcemove to drop location
- * allows an optional param for where to drop on fail
- *
- * @return TRUE/FALSE based on put in hand or dropped to ground
- */
-/mob/proc/put_in_hands_or_drop(obj/item/I, flags, atom/drop_loc = drop_location())
- if(!put_in_hands(I, flags))
- I.forceMove(drop_loc)
- return FALSE
- return TRUE
-
-/**
- * put in hands or del
- *
- * @return TRUE/FALSE based on put in hand or del'd
- */
-/mob/proc/put_in_hands_or_del(obj/item/I, flags)
- if(!put_in_hands(I, flags))
- qdel(I)
- return FALSE
- return TRUE
-
-/mob/proc/put_in_right_hand(obj/item/I, flags)
- return FALSE
-
-/mob/proc/put_in_left_hand(obj/item/I, flags)
- return FALSE
-
-/mob/proc/put_in_active_hand(obj/item/I, flags)
- return FALSE
-
-/mob/proc/put_in_inactive_hand(obj/item/I, flags)
- return FALSE
-
-/mob/proc/put_in_hand(obj/item/I, index, flags)
- return index == 1? put_in_left_hand(I, flags) : put_in_right_hand(I, flags)
-
-/mob/proc/put_in_hand_or_del(obj/item/I, index, flags)
- . = index == 1? put_in_left_hand(I, flags) : put_in_right_hand(I, flags)
- if(!.)
- qdel(I)
-
-/mob/proc/put_in_hand_or_drop(obj/item/I, index, flags, atom/drop_loc = drop_location())
- . = index == 1? put_in_left_hand(I, flags) : put_in_right_hand(I, flags)
- if(!.)
- I.forceMove(drop_loc)
-
-/**
- * returns held items
- */
-/mob/proc/get_held_items()
- . = list()
-
-/mob/proc/get_left_held_item()
- RETURN_TYPE(/obj/item)
- return
-
-/mob/proc/get_left_held_items()
- var/obj/item/I = get_left_held_item()
- if(I)
- return list(I)
- return list()
- // TODO: actual variable hand count
-
-/mob/proc/get_right_held_item()
- RETURN_TYPE(/obj/item)
- return
-
-/mob/proc/get_right_held_items()
- var/obj/item/I = get_right_held_item()
- if(I)
- return list(I)
- return list()
- // TODO: actual variable hand count
-
-/**
- * get held items of type
- */
-/mob/proc/get_held_items_of_type(type)
- . = list()
- for(var/obj/item/I as anything in get_held_items())
- if(istype(I, type))
- . += I
-
-/**
- * get first held item of type
- */
-/mob/proc/get_held_item_of_type(type)
- RETURN_TYPE(/obj/item)
- for(var/obj/item/I as anything in get_held_items())
- if(istype(I, type))
- return I
-
-/**
- * return index of item, or null if not found
- */
-/mob/proc/get_held_index(obj/item/I)
- return
-
-/**
- * get held item item at index
- */
-/mob/proc/get_held_item_of_index(index)
- RETURN_TYPE(/obj/item)
- return FALSE
-
-/**
- * hands are full?
- */
-/mob/proc/hands_full()
- return FALSE
-
-/**
- * returns held item in active hand
- */
-/mob/proc/get_active_held_item()
- RETURN_TYPE(/obj/item)
- return
-
-/**
- * returns held item in inactive hand (or any inactive hand if more than 1)
- */
-/mob/proc/get_inactive_held_item()
- RETURN_TYPE(/obj/item)
- return
-
-/**
- * returns all items held in non active hands
- */
-/mob/proc/get_inactive_held_items()
- var/obj/item/I = get_inactive_held_item()
- if(I)
- return list(I)
- return list()
- // TODO: actual multihanding support, for now this is just a wrapper
-
-/**
- * get number of hand slots
- *
- * semantically this means "physically there"
- * a broken hand is still there, a stump isn't
- */
-/mob/proc/get_number_of_hands()
- return 0
-
-/**
- * do we have hands?
- */
-/mob/proc/has_hands()
- return FALSE
-
-/**
- * returns if we are holding something
- */
-/mob/proc/is_holding(obj/item/I)
- return !!get_held_index(I)
-
-/**
- * returns if we're holding something in inactive hand slots
- */
-/mob/proc/is_holding_inactive(obj/item/I)
- return is_holding(I) && (get_active_held_item() != I)
-
-/**
- * drops all our held items
- *
- * @params
- * force - even if nodrop
- */
-/mob/proc/drop_all_held_items(flags)
- for(var/obj/item/I as anything in get_held_items())
- drop_item_to_ground(I, flags)
-
-/mob/proc/drop_active_held_item(flags)
- return drop_item_to_ground(get_active_held_item(), flags)
-
-/mob/proc/drop_inactive_held_item(flags)
- return drop_item_to_ground(get_inactive_held_item(), flags)
-
-/mob/proc/drop_held_item_of_index(index, flags)
- return drop_item_to_ground(get_held_item_of_index(index), flags)
-
-// these two will need rewritten when we get support for arbitrary hand numbers.
-
-/mob/proc/drop_left_held_item(flags)
- return drop_held_item_of_index(1, flags)
-
-/mob/proc/drop_right_held_item(flags)
- return drop_held_item_of_index(2, flags)
-
-/**
- * means if we have an empty hand able to accept an arbitrary item.
- */
-/mob/proc/has_free_hand()
- return FALSE
diff --git a/code/modules/mob/inventory/inventory-hands-check.dm b/code/modules/mob/inventory/inventory-hands-check.dm
new file mode 100644
index 000000000000..6333f19c08a9
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands-check.dm
@@ -0,0 +1,74 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Procs in this file are mirrored to the /mob level for ease of use. *//
+//* *//
+//* In the future, there should likely be a separation of concerns *//
+//* and the enforcement of 'mob.inventory' access, but given the overhead of *//
+//* a proc-call, this is currently not done. *//
+
+/**
+ * Returns if the mob is holding something in hand.
+ *
+ * @return TRUE / FALSE
+ */
+/datum/inventory/proc/is_holding(obj/item/I)
+ return !!get_held_index(I)
+
+/**
+ * Returns if we are holding something in hand.
+ */
+/mob/proc/is_holding(obj/item/I)
+ return !!get_held_index(I)
+
+/**
+ * Returns the number of nominal hand slots that are empty.
+ *
+ * @return number
+ */
+/datum/inventory/proc/count_empty_hands()
+ . = 0
+ for(var/i in 1 to length(held_items))
+ if(isnull(held_items[i]))
+ .++
+
+/**
+ * Returns the number of empty hands we have.
+ *
+ * * This is not based on usable hands, this based on the hands in our inventory, e.g. nominal hand count.
+ * * This is 0 if we have no inventory.
+ *
+ * @return number
+ */
+/mob/proc/count_empty_hands()
+ . = 0
+ for(var/i in 1 to length(inventory?.held_items))
+ if(isnull(inventory.held_items[i]))
+ .++
+
+/**
+ * Returns the number of nominal hand slots that are full.
+ *
+ * @return number
+ */
+/datum/inventory/proc/count_full_hands()
+ . = 0
+ for(var/i in 1 to length(held_items))
+ if(isnull(held_items[i]))
+ continue
+ .++
+
+/**
+ * Returns the number of full hands we have.
+ *
+ * * This is not based on usable hands, this based on the hands in our inventory, e.g. nominal hand count.
+ * * This is 0 if we have no inventory.
+ *
+ * @return number
+ */
+/mob/proc/count_full_hands()
+ . = 0
+ for(var/i in 1 to length(inventory?.held_items))
+ if(isnull(inventory.held_items[i]))
+ continue
+ .++
diff --git a/code/modules/mob/inventory/inventory-hands-drop.dm b/code/modules/mob/inventory/inventory-hands-drop.dm
new file mode 100644
index 000000000000..8af8c926ddde
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands-drop.dm
@@ -0,0 +1,56 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Procs in this file are mirrored to the /mob level for ease of use. *//
+//* *//
+//* In the future, there should likely be a separation of concerns *//
+//* and the enforcement of 'mob.inventory' access, but given the overhead of *//
+//* a proc-call, this is currently not done. *//
+
+/**
+ * Drops all of the mob's held items.
+ *
+ * @params
+ * * inv_op_flags - inventory operation flags
+ *
+ * @return Nothing
+ */
+/datum/inventory/proc/drop_held_items(inv_op_flags)
+ for(var/obj/item/I in held_items)
+ drop_item_to_ground(I, inv_op_flags)
+
+/**
+ * Drops all our held items.
+ *
+ * @params
+ * * inv_op_flags - inventory operation flags
+ *
+ * @return Nothing
+ */
+/mob/proc/drop_held_items(inv_op_flags)
+ for(var/obj/item/I as anything in get_held_items())
+ inventory.drop_item_to_ground(I, inv_op_flags)
+
+/**
+ * Drops the item held in a given hand index.
+ *
+ * @params
+ * * index - the hand index
+ * * inv_op_flags - inventory operation flags
+ *
+ * @return INV_RETURN_* flags
+ */
+/datum/inventory/proc/drop_held_index(index, inv_op_flags)
+ return drop_item_to_ground(get_held_index(index), inv_op_flags)
+
+/**
+ * Drops the item held in a given hand index.
+ *
+ * @params
+ * * index - the hand index
+ * * inv_op_flags - inventory operation flags
+ *
+ * @return INV_RETURN_* flags
+ */
+/mob/proc/drop_held_index(index, flags)
+ return drop_item_to_ground(get_held_index(index), flags)
diff --git a/code/modules/mob/inventory/inventory-hands-get.dm b/code/modules/mob/inventory/inventory-hands-get.dm
new file mode 100644
index 000000000000..633215f45f56
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands-get.dm
@@ -0,0 +1,212 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Procs in this file are mirrored to the /mob level for ease of use. *//
+//* *//
+//* In the future, there should likely be a separation of concerns *//
+//* and the enforcement of 'mob.inventory' access, but given the overhead of *//
+//* a proc-call, this is currently not done. *//
+
+//* Basic *//
+
+/**
+ * Gets the item held in a given hand index
+ *
+ * * This expects a valid index.
+ *
+ * @return /obj/item or null.
+ */
+/datum/inventory/proc/get_held_item(index)
+ RETURN_TYPE(/obj/item)
+ return held_items[index]
+
+/**
+ * Gets the item held in a given hand index
+ *
+ * * This expects a valid index.
+ *
+ * @return /obj/item or null.
+ */
+/mob/proc/get_held_item(index)
+ RETURN_TYPE(/obj/item)
+ return inventory?.held_items[index]
+
+/**
+ * Gets the hand index that an item is in, if any.
+ *
+ * @return index of item, or null if not found.
+ */
+/datum/inventory/proc/get_held_index(obj/item/I)
+ return held_items?.Find(I) || null
+
+/**
+ * Gets the hand index that an item is in, if any.
+ *
+ * @return index of item, or null if not found.
+ */
+/mob/proc/get_held_index(obj/item/I)
+ return inventory?.held_items?.Find(I) || null
+
+//* Special *//
+
+/**
+ * Get an indexed list of weakrefs or nulls of held items.
+ *
+ * * returns null if we have no inventory.
+ */
+/datum/inventory/proc/get_held_items_as_weakrefs()
+ RETURN_TYPE(/list)
+ . = new /list(length(held_items))
+ for(var/i in 1 to length(held_items))
+ .[i] = WEAKREF(held_items[i])
+
+/**
+ * Get an indexed list of weakrefs or nulls of held items.
+ */
+/mob/proc/get_held_items_as_weakrefs()
+ RETURN_TYPE(/list)
+ if(!inventory)
+ return list()
+ . = new /list(length(inventory.held_items))
+ for(var/i in 1 to length(inventory.held_items))
+ .[i] = WEAKREF(inventory.held_items[i])
+
+//* Iteration *//
+
+/**
+ * @return list of held items
+ */
+/datum/inventory/proc/get_held_items()
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/obj/item/I in held_items)
+ return I
+
+/**
+ * @return list of held items
+ */
+/mob/proc/get_held_items()
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/obj/item/I in inventory?.held_items)
+ . += I
+
+/**
+ * @return list of held items of type
+ */
+/datum/inventory/proc/get_held_items_of_type(type)
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/obj/item/I as anything in held_items)
+ if(istype(I, type))
+ . += I
+
+/**
+ * @return list of held items of type
+ */
+/mob/proc/get_held_items_of_type(type)
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/obj/item/I as anything in inventory?.held_items)
+ if(istype(I, type))
+ . += I
+
+/**
+ * @return first held item of type, if found
+ */
+/datum/inventory/proc/get_held_item_of_type(type)
+ RETURN_TYPE(/obj/item)
+ for(var/obj/item/I as anything in held_items)
+ if(istype(I, type))
+ return I
+
+/**
+ * @return first held item of type, if found
+ */
+/mob/proc/get_held_item_of_type(type)
+ RETURN_TYPE(/obj/item)
+ for(var/obj/item/I as anything in inventory?.held_items)
+ if(istype(I, type))
+ return I
+
+/**
+ * @return list of full hands
+ */
+/datum/inventory/proc/get_full_hand_indices()
+ . = list()
+ for(var/i in 1 to length(held_items))
+ if(held_items[i])
+ . += i
+
+/**
+ * @return list of full hands
+ */
+/mob/proc/get_full_hand_indices()
+ . = list()
+ for(var/i in 1 to length(inventory?.held_items))
+ if(inventory.held_items[i])
+ . += i
+
+/**
+ * @return list of empty hands
+ */
+/datum/inventory/proc/get_empty_hand_indices()
+ . = list()
+ for(var/i in 1 to length(held_items))
+ if(!held_items[i])
+ . += i
+
+/**
+ * @return list of empty hands
+ */
+/mob/proc/get_empty_hand_indices()
+ . = list()
+ for(var/i in 1 to length(inventory?.held_items))
+ if(!inventory.held_items[i])
+ . += i
+
+//* By Side *//
+//* This is not on /datum/inventory level as *//
+//* oftentimes a mob has no semantic 'sided hands'. *//
+
+/**
+ * returns first item on left
+ */
+/mob/proc/get_left_held_item()
+ RETURN_TYPE(/obj/item)
+ for(var/i in 1 to length(inventory?.held_items) step 2)
+ if(isnull(inventory?.held_items[i]))
+ continue
+ return inventory?.held_items[i]
+
+/**
+ * returns first item on right
+ */
+/mob/proc/get_right_held_item()
+ RETURN_TYPE(/obj/item)
+ for(var/i in 2 to length(inventory?.held_items) step 2)
+ if(isnull(inventory?.held_items[i]))
+ continue
+ return inventory?.held_items[i]
+
+/**
+ * returns all items on left
+ */
+/mob/proc/get_left_held_items()
+ RETURN_TYPE(/obj/item)
+ . = list()
+ for(var/i in 1 to length(inventory?.held_items) step 2)
+ if(isnull(inventory?.held_items[i]))
+ continue
+ . += inventory?.held_items[i]
+
+/**
+ * returns all items on right
+ */
+/mob/proc/get_right_held_items()
+ RETURN_TYPE(/obj/item)
+ . = list()
+ for(var/i in 2 to length(inventory?.held_items) step 2)
+ if(isnull(inventory?.held_items[i]))
+ continue
+ . += inventory?.held_items[i]
diff --git a/code/modules/mob/inventory/inventory-hands-legacy.dm b/code/modules/mob/inventory/inventory-hands-legacy.dm
new file mode 100644
index 000000000000..1dd8749bdf3c
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands-legacy.dm
@@ -0,0 +1,71 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * returns if we're holding something in inactive hand slots
+ *
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/is_holding_inactive(obj/item/I)
+ return is_holding(I) && (get_active_held_item() != I)
+
+/**
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/drop_active_held_item(flags)
+ return drop_item_to_ground(get_active_held_item(), flags)
+
+/**
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/drop_inactive_held_item(flags)
+ return drop_item_to_ground(get_inactive_held_item(), flags)
+
+/**
+ * returns held item in active hand
+ *
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/get_active_held_item()
+ RETURN_TYPE(/obj/item)
+ return inventory?.held_items?[active_hand]
+
+/**
+ * returns held item in inactive hand (or any inactive hand if more than 1)
+ *
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/get_inactive_held_item()
+ RETURN_TYPE(/obj/item)
+ for(var/i in 1 to length(inventory?.held_items))
+ if(i == active_hand)
+ continue
+ if(isnull(inventory?.held_items[i]))
+ continue
+ return inventory?.held_items[i]
+
+/**
+ * returns all items held in non active hands
+ *
+ * todo: deprecate; 'active hand' should be based on client intent, not on the mob.
+ */
+/mob/proc/get_inactive_held_items()
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/i in 1 to length(inventory?.held_items))
+ if(i == active_hand)
+ continue
+ if(isnull(inventory?.held_items[i]))
+ continue
+ . += inventory?.held_items[i]
+
+/mob/proc/put_in_active_hand(obj/item/I, flags)
+ return put_in_hand(I, active_hand, flags)
+
+/mob/proc/put_in_inactive_hand(obj/item/I, flags)
+ for(var/i in 1 to length(inventory?.held_items))
+ if(i == active_hand)
+ continue
+ if(put_in_hand(I, i, flags))
+ return TRUE
+ return FALSE
diff --git a/code/modules/mob/inventory/inventory-hands-put.dm b/code/modules/mob/inventory/inventory-hands-put.dm
new file mode 100644
index 000000000000..b5799ca1698f
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands-put.dm
@@ -0,0 +1,148 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Procs in this file are mirrored to the /mob level for ease of use. *//
+//* *//
+//* In the future, there should likely be a separation of concerns *//
+//* and the enforcement of 'mob.inventory' access, but given the overhead of *//
+//* a proc-call, this is currently not done. *//
+
+/datum/inventory/proc/put_in_hand(obj/item/I, index, inv_op_flags)
+ return owner.equip_hand_impl(I, index, inv_op_flags) ? INV_RETURN_SUCCESS : INV_RETURN_FAILED
+
+/mob/proc/put_in_hand(obj/item/I, index, inv_op_flags)
+ return inventory?.put_in_hand(I, index, inv_op_flags)
+
+/**
+ * **Warning**: `prioritize_index` is patched to support legacy behavior by defaulting to owner active hand.
+ * This may be removed at any time. Besure to specify the index if you need this behavior.
+ *
+ * @params
+ * * I - the item
+ * * inv_op_flags - inventory operation flags
+ * * prioritize_index - try that index first; defaults to the inventory owner's active hand, if any. set to `0` (not null!) to prioritize none.
+ */
+/datum/inventory/proc/put_in_hands(obj/item/I, inv_op_flags, prioritize_index)
+ if(isnull(prioritize_index))
+ prioritize_index = owner.active_hand
+
+ if(is_holding(I))
+ return INV_RETURN_SUCCESS
+
+ if(!(inv_op_flags & INV_OP_NO_MERGE_STACKS) && istype(I, /obj/item/stack))
+ var/obj/item/stack/S = I
+ for(var/obj/item/stack/held_stack in get_held_items())
+ if(S.can_merge(held_stack) && S.merge(held_stack))
+ to_chat(src, SPAN_NOTICE("Your [held_stack] stack now contains [held_stack.get_amount()] [held_stack.singular_name]\s."))
+ return INV_RETURN_SUCCESS
+
+ if(prioritize_index)
+ var/priority_result = put_in_hand(I, prioritize_index, inv_op_flags)
+ switch(priority_result)
+ if(INV_RETURN_FAILED)
+ else
+ return priority_result
+
+ for(var/i in 1 to length(held_items))
+ if(i == prioritize_index)
+ continue
+ var/result = put_in_hand(I, i, inv_op_flags)
+ switch(result)
+ if(INV_RETURN_FAILED)
+ else
+ return result
+
+ return INV_RETURN_FAILED
+
+/**
+ * **Warning**: `prioritize_index` is patched to support legacy behavior by defaulting to owner active hand.
+ * This may be removed at any time. Besure to specify the index if you need this behavior.
+ *
+ * @params
+ * * I - the item
+ * * inv_op_flags - inventory operation flags
+ * * prioritize_index - try that index first; defaults to the inventory owner's active hand, if any. set to `0` (not null!) to prioritize none.
+ */
+/mob/proc/put_in_hands(obj/item/I, inv_op_flags, prioritize_index)
+ return inventory?.put_in_hands(I, inv_op_flags, prioritize_index)
+
+/**
+ * puts an item in hands or forcemoves to drop_loc
+ *
+ * * if 'specific_index' is specified, this only tries to go to that hand index.
+ * * drop_loc defaults to the owner's location, if any owner is there.
+ * * not having a valid drop_loc is a runtime error.
+ *
+ * @return INV_RETURN_*
+ */
+/datum/inventory/proc/put_in_hands_or_drop(obj/item/I, inv_op_flags, atom/drop_loc, specific_index)
+ if(isnull(drop_loc))
+ drop_loc = owner?.drop_location()
+ if(isnull(drop_loc))
+ CRASH("invalid drop location; placing stuff into nullspace is usually an error.")
+
+ var/result = specific_index ? put_in_hand(I, specific_index, inv_op_flags) : put_in_hands(I, inv_op_flags)
+
+ // todo: switch(result) once put_in_hands uses INV_RETURN_*; convert this
+ if(!result)
+ I.forceMove(drop_loc)
+ return INV_RETURN_FAILED
+ return INV_RETURN_SUCCESS
+
+/**
+ * puts an item in hands or forcemoves to drop_loc
+ *
+ * * if 'specific_index' is specified, this only tries to go to that hand index.
+ * * drop_loc defaults to the owner's location, if any owner is there.
+ * * not having a valid drop_loc is a runtime error.
+ *
+ * @return INV_RETURN_*
+ */
+/mob/proc/put_in_hands_or_drop(obj/item/I, inv_op_flags, atom/drop_loc, specific_index)
+ // inventory null --> INV_RETURN_FAILED, as that's also #define'd to be null
+ return inventory?.put_in_hands_or_drop(I, inv_op_flags, drop_loc, specific_index)
+
+/**
+ * puts an item in hands or deletes it
+ *
+ * * if 'specific_index' is specified, this only tries to go to that hand index.
+ *
+ * @return INV_RETURN_*
+ */
+/datum/inventory/proc/put_in_hands_or_del(obj/item/I, inv_op_flags, specific_index)
+ var/result = specific_index ? put_in_hand(I, specific_index, inv_op_flags) : put_in_hands(I, inv_op_flags)
+
+ // todo: switch(result) once put_in_hands uses INV_RETURN_*; convert this
+ if(!result)
+ qdel(I)
+ return INV_RETURN_FAILED
+ return INV_RETURN_SUCCESS
+
+/**
+ * puts an item in hands or deletes it
+ *
+ * * if 'specific_index' is specified, this only tries to go to that hand index.
+ *
+ * @return INV_RETURN_*
+ */
+/mob/proc/put_in_hands_or_del(obj/item/I, inv_op_flags, specific_index)
+ if(inventory)
+ return inventory.put_in_hands_or_del(I, inv_op_flags, specific_index)
+ qdel(I)
+ return INV_RETURN_FAILED
+
+//* By Side *//
+//* This is not on /datum/inventory level as *//
+//* oftentimes a mob has no semantic 'sided hands'. *//
+
+/mob/proc/put_in_left_hand(obj/item/I, inv_op_flags)
+ for(var/i in 1 to length(inventory?.held_items) step 2)
+ if(put_in_hand(I, i, inv_op_flags))
+ return TRUE
+ return FALSE
+
+/mob/proc/put_in_right_hand(obj/item/I, inv_op_flags)
+ for(var/i in 1 to length(inventory?.held_items) step 2)
+ if(put_in_hand(I, i, inv_op_flags))
+ return TRUE
+ return FALSE
diff --git a/code/modules/mob/inventory/inventory-hands.dm b/code/modules/mob/inventory/inventory-hands.dm
new file mode 100644
index 000000000000..9f196ff259dd
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hands.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/inventory/proc/set_hand_count(count)
+ LAZYINITLIST(held_items)
+ held_items.len = count
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.rebuild()
+ //! legacy
+ owner.swap_hand(count ? clamp(owner.active_hand, 1, count) : null)
+ //! end
+ SEND_SIGNAL(src, COMSIG_INVENTORY_SLOT_REBUILD)
+
+/datum/inventory/proc/get_hand_count()
+ return length(held_items)
+
+// todo: all inventory core procs go on the inventory, not the mob; mob is just a hook point!
diff --git a/code/modules/mob/inventory/inventory-hooks.dm b/code/modules/mob/inventory/inventory-hooks.dm
new file mode 100644
index 000000000000..a52382909040
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hooks.dm
@@ -0,0 +1,65 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Item enter / exit *//
+
+/**
+ * Should be called when an item is added to inventory.
+ *
+ * The call semantics for this proc is interesting.
+ *
+ * * It is allowed for on_item_swapped to be called instead of this proc if it's a swap.
+ * * It is not required for on_item_swapped to be called instead of this proc if it's a swap.
+ */
+/datum/inventory/proc/on_item_entered(obj/item/item, datum/inventory_slot/slot_or_index)
+ SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_ENTERED_SLOT, slot_or_index)
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.add_item(item, slot_or_index)
+
+/**
+ * Should be called when an item is removed from inventory.
+ *
+ * The call semantics for this proc is interesting.
+ *
+ * * It is allowed for on_item_swapped to be called instead of this proc if it's a swap.
+ * * It is not required for on_item_swapped to be called instead of this proc if it's a swap.
+ */
+/datum/inventory/proc/on_item_exited(obj/item/item, datum/inventory_slot/slot_or_index)
+ SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_EXITED_SLOT, slot_or_index)
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.remove_item(item, slot_or_index)
+
+/**
+ * Should be called when an item is moved from one slot to another.
+ *
+ * The call semantics for this proc is interesting.
+ *
+ * * It is not required for this proc to be called instead of exited & entered.
+ * * It is recommended for this proc to be called instead of exited & entered.
+ *
+ * This is because this proc and exited/entered do not actually trigger item-level hooks,
+ * these are just here for visuals.
+ *
+ * As of right now, the functionality is equivalent; on_item_swapped() is just more efficient.
+ */
+/datum/inventory/proc/on_item_swapped(obj/item/item, datum/inventory_slot/from_slot_or_index, datum/inventory_slot/to_slot_or_index)
+ SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_EXITED_SLOT, from_slot_or_index)
+ SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_ENTERED_SLOT, to_slot_or_index)
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.move_item(item, from_slot_or_index, to_slot_or_index)
+
+/**
+ * Should be called when the mob's mobility flags change.
+ */
+/datum/inventory/proc/on_mobility_update()
+ for(var/datum/action/action in actions.actions)
+ action.update_button_availability()
+
+/**
+ * Trigger handcuffed overlay updates for HUDs.
+ */
+/datum/inventory/proc/on_handcuffed_update()
+ var/restrained = !!owner.item_by_slot_id(/datum/inventory_slot/restraints/handcuffs::id)
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ for(var/atom/movable/screen/actor_hud/inventory/plate/hand/hand_plate in hud.hands)
+ hand_plate.set_handcuffed(restrained)
diff --git a/code/modules/mob/inventory/inventory-rendering.dm b/code/modules/mob/inventory/inventory-rendering.dm
new file mode 100644
index 000000000000..7bad050bff47
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-rendering.dm
@@ -0,0 +1,89 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Rendering *//
+
+/datum/inventory/proc/remove_slot_renders()
+ var/list/transformed = list()
+ for(var/slot_id in rendered_normal_overlays)
+ transformed += rendered_normal_overlays[slot_id]
+ owner.cut_overlay(transformed)
+
+/datum/inventory/proc/reapply_slot_renders()
+ // try not to dupe
+ remove_slot_renders()
+ var/list/transformed = list()
+ for(var/slot_id in rendered_normal_overlays)
+ transformed += rendered_normal_overlays[slot_id]
+ owner.add_overlay(transformed)
+
+/**
+ * just update if a slot is visible
+ */
+/datum/inventory/proc/update_slot_visible(slot_id, cascade = TRUE)
+ // resolve item
+ var/obj/item/target = owner.item_by_slot_id(slot_id)
+
+ // first, cascade incase we early-abort later
+ if(cascade)
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ slot.cascade_render_visibility(owner, target)
+
+ // check existing
+ if(isnull(rendered_normal_overlays[slot_id]))
+ return
+
+ // remove overlay first incase it's already there
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+
+ // check if slot should render
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ if(!slot.should_render(owner, target))
+ return
+
+ // add overlay if it should
+ owner.add_overlay(rendered_normal_overlays[slot_id])
+
+/**
+ * redo a slot's render
+ */
+/datum/inventory/proc/update_slot_render(slot_id, cascade = TRUE)
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ var/obj/item/target = owner.item_by_slot_id(slot_id)
+
+ // first, cascade incase we early-abort later
+ if(cascade)
+ slot.cascade_render_visibility(owner, target)
+
+ if(!slot.should_render(owner, target))
+ remove_slot_render(slot_id)
+ return
+
+ if(isnull(target))
+ remove_slot_render(slot_id)
+ return
+
+ var/bodytype = BODYTYPE_DEFAULT
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/casted_human = owner
+ bodytype = casted_human.species.get_effective_bodytype(casted_human, target, slot_id)
+
+ var/rendering_results = slot.render(owner, target, bodytype)
+ if(islist(rendering_results)? !length(rendering_results) : isnull(rendering_results))
+ remove_slot_render(slot_id)
+ return
+
+ set_slot_render(slot_id, rendering_results)
+
+/datum/inventory/proc/remove_slot_render(slot_id)
+ if(isnull(rendered_normal_overlays[slot_id]))
+ return
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+ rendered_normal_overlays -= slot_id
+
+/datum/inventory/proc/set_slot_render(slot_id, overlay)
+ if(!isnull(rendered_normal_overlays[slot_id]))
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+ rendered_normal_overlays[slot_id] = overlay
+ owner.add_overlay(overlay)
diff --git a/code/modules/mob/inventory/inventory.dm b/code/modules/mob/inventory/inventory.dm
index 8d9c39a05ae1..197ac691ea81 100644
--- a/code/modules/mob/inventory/inventory.dm
+++ b/code/modules/mob/inventory/inventory.dm
@@ -1,5 +1,11 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/**
* mob inventory data goes in here.
+ *
+ * * this does not include hands; that's handled mob-side.
+ * * this only includes inventory slots.
*/
/datum/inventory
//* Basics *//
@@ -10,15 +16,33 @@
/// our action holder
var/datum/action_holder/actions
- //* Inventory *//
-
//* Caches *//
- /// cached overlays by slot id
+ /// cached overlays by slot id or hand index
var/list/rendered_normal_overlays = list()
/// cached overlays by slot id
// todo: emissives
// var/list/rendered_emissive_overlays = list()
+ //* HUDs *//
+ /// Actor HUDs using us
+ var/list/datum/actor_hud/inventory/huds_using
+
+ //* Inventory *//
+ /// held items
+ ///
+ /// * empty indices are null
+ /// * this is also our rendered & nominal hand count
+ /// * 1, 3, 5, ... are left
+ /// * 2, 4, 6, ... are right
+ var/list/obj/item/held_items = list()
+
+ //* Slots *//
+ /// our base slot ids associated to remappings
+ ///
+ /// * key: string id; value: remapping list with keys of INVENTORY_SLOT_REMAP_*
+ /// * never ever modify this list in-place, this is why it's private; this may be shared lists in species!
+ VAR_PRIVATE/list/base_inventory_slots
+
/datum/inventory/New(mob/M)
if(!istype(M))
CRASH("no mob")
@@ -30,95 +54,10 @@
/datum/inventory/Destroy()
QDEL_NULL(actions)
owner = null
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.unbind_from_inventory(src)
return ..()
-//* Rendering *//
-
-/datum/inventory/proc/remove_slot_renders()
- var/list/transformed = list()
- for(var/slot_id in rendered_normal_overlays)
- transformed += rendered_normal_overlays[slot_id]
- owner.cut_overlay(transformed)
-
-/datum/inventory/proc/reapply_slot_renders()
- // try not to dupe
- remove_slot_renders()
- var/list/transformed = list()
- for(var/slot_id in rendered_normal_overlays)
- transformed += rendered_normal_overlays[slot_id]
- owner.add_overlay(transformed)
-
-/**
- * just update if a slot is visible
- */
-/datum/inventory/proc/update_slot_visible(slot_id, cascade = TRUE)
- // resolve item
- var/obj/item/target = owner.item_by_slot_id(slot_id)
-
- // first, cascade incase we early-abort later
- if(cascade)
- var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
- slot.cascade_render_visibility(owner, target)
-
- // check existing
- if(isnull(rendered_normal_overlays[slot_id]))
- return
-
- // remove overlay first incase it's already there
- owner.cut_overlay(rendered_normal_overlays[slot_id])
-
- // check if slot should render
- var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
- if(!slot.should_render(owner, target))
- return
-
- // add overlay if it should
- owner.add_overlay(rendered_normal_overlays[slot_id])
-
-/**
- * redo a slot's render
- */
-/datum/inventory/proc/update_slot_render(slot_id, cascade = TRUE)
- var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
- var/obj/item/target = owner.item_by_slot_id(slot_id)
-
- // first, cascade incase we early-abort later
- if(cascade)
- slot.cascade_render_visibility(owner, target)
-
- if(!slot.should_render(owner, target))
- remove_slot_render(slot_id)
- return
-
- if(isnull(target))
- remove_slot_render(slot_id)
- return
-
- var/bodytype = BODYTYPE_DEFAULT
-
- if(ishuman(owner))
- var/mob/living/carbon/human/casted_human = owner
- bodytype = casted_human.species.get_effective_bodytype(casted_human, target, slot_id)
-
- var/rendering_results = slot.render(owner, target, bodytype)
- if(islist(rendering_results)? !length(rendering_results) : isnull(rendering_results))
- remove_slot_render(slot_id)
- return
-
- set_slot_render(slot_id, rendering_results)
-
-/datum/inventory/proc/remove_slot_render(slot_id)
- if(isnull(rendered_normal_overlays[slot_id]))
- return
- owner.cut_overlay(rendered_normal_overlays[slot_id])
- rendered_normal_overlays -= slot_id
-
-/datum/inventory/proc/set_slot_render(slot_id, overlay)
- if(!isnull(rendered_normal_overlays[slot_id]))
- owner.cut_overlay(rendered_normal_overlays[slot_id])
- rendered_normal_overlays[slot_id] = overlay
- owner.add_overlay(overlay)
-
//* Queries *//
/**
@@ -132,140 +71,24 @@
if(I.body_cover_flags & cover_flags)
. += I
-//* Update Hooks *//
+//* Slots *//
/**
- * Only called if mobility changed.
+ * mapped slots input should be a list of slot IDs, optionally associated to remapping lists
*/
-/datum/inventory/proc/on_mobility_update()
- for(var/datum/action/action in actions.actions)
- action.update_button_availability()
-
-// todo: redo things below, slowly
+/datum/inventory/proc/set_inventory_slots(list/mapped_slots)
+ base_inventory_slots = deep_copy_list(mapped_slots)
+ for(var/datum/actor_hud/inventory/hud in huds_using)
+ hud.rebuild()
+ SEND_SIGNAL(src, COMSIG_INVENTORY_SLOT_REBUILD)
/**
- * handles the insertion
- * item can be moved or not moved before calling
- *
- * slot must be a typepath
- *
- * @return true/false based on if it worked
+ * @return list(id = list(INVENTORY_SLOT_REMAP_*))
*/
-/mob/proc/handle_abstract_slot_insertion(obj/item/I, slot, flags)
- if(!ispath(slot, /datum/inventory_slot/abstract))
- slot = resolve_inventory_slot(slot)?.type
- if(!ispath(slot, /datum/inventory_slot/abstract))
- stack_trace("invalid slot: [slot]")
- else if(slot != /datum/inventory_slot/abstract/put_in_hands)
- stack_trace("attempted usage of slot id in abstract insertion converted successfully")
- . = FALSE
- switch(slot)
- if(/datum/inventory_slot/abstract/hand/left)
- return put_in_left_hand(I, flags)
- if(/datum/inventory_slot/abstract/hand/right)
- return put_in_right_hand(I, flags)
- if(/datum/inventory_slot/abstract/put_in_belt)
- var/obj/item/held = item_by_slot_id(SLOT_ID_BELT)
- if(flags & INV_OP_FORCE)
- return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
- return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
- if(/datum/inventory_slot/abstract/put_in_backpack)
- var/obj/item/held = item_by_slot_id(SLOT_ID_BACK)
- if(flags & INV_OP_FORCE)
- return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
- return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
- if(/datum/inventory_slot/abstract/put_in_hands)
- return put_in_hands(I, flags)
- if(/datum/inventory_slot/abstract/put_in_storage, /datum/inventory_slot/abstract/put_in_storage_try_active)
- if(slot == /datum/inventory_slot/abstract/put_in_storage_try_active)
- // todo: redirection
- if(flags & INV_OP_FORCE)
- if(active_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING))
- return TRUE
- else
- if(active_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND))
- return TRUE
- for(var/obj/item/held in get_equipped_items_in_slots(list(
- SLOT_ID_BELT,
- SLOT_ID_BACK,
- SLOT_ID_UNIFORM,
- SLOT_ID_SUIT,
- SLOT_ID_LEFT_POCKET,
- SLOT_ID_RIGHT_POCKET
- )) + get_held_items())
- if(isnull(held?.obj_storage))
- continue
- if(flags & INV_OP_FORCE)
- return held.obj_storage.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
- return held.obj_storage.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
- return FALSE
- if(/datum/inventory_slot/abstract/attach_as_accessory)
- for(var/obj/item/clothing/C in get_equipped_items())
- if(C.attempt_attach_accessory(I))
- return TRUE
- return FALSE
- else
- CRASH("Invalid abstract slot [slot]")
+/datum/inventory/proc/build_inventory_slots_with_remappings()
+ return deep_copy_list(base_inventory_slots)
-/**
- * handles internal logic of unequipping an item
- *
- * @params
- * - I - item
- * - flags - inventory operation hint bitfield, see defines
- * - newloc - where to transfer to. null for nullspace, FALSE for don't transfer
- * - user - can be null - person doing the removals
- *
- * @return TRUE/FALSE for success
- */
-/mob/proc/_unequip_item(obj/item/I, flags, newloc, mob/user = src)
- PROTECTED_PROC(TRUE)
- if(!I)
- return TRUE
-
- var/hand = get_held_index(I)
- var/old
- if(hand)
- if(!can_unequip(I, SLOT_ID_HANDS, flags, user))
- return FALSE
- _unequip_held(I, TRUE)
- I.unequipped(src, SLOT_ID_HANDS, flags)
- old = SLOT_ID_HANDS
- else
- if(!I.worn_slot)
- stack_trace("tried to unequip an item without current equipped slot.")
- I.worn_slot = _slot_by_item(I)
- if(!can_unequip(I, I.worn_slot, flags, user))
- return FALSE
- old = I.worn_slot
- _unequip_slot(I.worn_slot, flags)
- I.unequipped(src, I.worn_slot, flags)
- handle_item_denesting(I, old, flags, user)
-
- // this qdeleted catches unequipped() deleting the item.
- . = QDELETED(I)? FALSE : TRUE
-
- log_inventory("[key_name(src)] unequipped [I] from [old].")
-
- if(I)
- // todo: better rendering that takes observers into account
- if(client)
- client.screen -= I
- I.screen_loc = null
- //! at some point we should have /pre_dropped and /pre_pickup, because dropped should logically come after move.
- if(I.dropped(src, flags, newloc) == ITEM_RELOCATED_BY_DROPPED)
- . = FALSE
- else if(QDELETED(I))
- // this check RELIES on dropped() being the first if
- // make sure you don't blindly move it!!
- // this is meant to catch any potential deletions dropped can cause.
- . = FALSE
- else
- if(!(I.item_flags & ITEM_DROPDEL))
- if(newloc == null)
- I.moveToNullspace()
- else if(newloc != FALSE)
- I.forceMove(newloc)
+//! unsorted / legacy below
/mob/proc/handle_item_denesting(obj/item/I, old_slot, flags, mob/user)
// if the item was inside something,
@@ -296,178 +119,6 @@
over.worn_hook_suppressed = FALSE
// put it back in the slot
_equip_slot(over, old_slot, flags)
- // put it back on the screen
- over.hud_layerise()
- position_hud_item(over, old_slot)
- client?.screen |= over
-
-/**
- * checks if we can unequip an item
- *
- * Preconditions: The item is either equipped already, or isn't equipped.
- *
- * @return TRUE/FALSE
- *
- * @params
- * - I - item
- * - slot - slot we're unequipping from - can be null
- * - flags - inventory operation hint bitfield, see defines
- * - user - stripper - can be null
- */
-/mob/proc/can_unequip(obj/item/I, slot, flags, mob/user = src)
- // destroyed IS allowed to call these procs
- if(I && QDELETED(I) && !QDESTROYING(I))
- to_chat(user, SPAN_DANGER("A deleted [I] was checked in can_unequip(). Report this entire line to coders immediately. Debug data: [I] ([REF(I)]) slot [slot] flags [flags] user [user]"))
- to_chat(user, SPAN_DANGER("can_unequip will return TRUE to allow you to drop the item, but expect potential glitches!"))
- return TRUE
-
- if(!slot)
- slot = slot_id_by_item(I)
-
- if(!(flags & INV_OP_FORCE) && HAS_TRAIT(I, TRAIT_ITEM_NODROP))
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
- to_chat(user, SPAN_WARNING("[I] is stubbornly stuck [slot_meta.display_preposition] your [slot_meta.display_name]!"))
- return FALSE
-
- var/blocked_by
- if((blocked_by = inventory_slot_reachability_conflict(I, slot, user)) && !(flags & (INV_OP_FORCE | INV_OP_IGNORE_REACHABILITY)))
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, SPAN_WARNING("\the [blocked_by] is in the way!"))
- return FALSE
-
- // lastly, check item's opinion
- if(!I.can_unequip(src, slot, user, flags))
- return FALSE
-
- return TRUE
-
-/**
- * checks if we can equip an item to a slot
- *
- * Preconditions: The item will either be equipped on us already, or not yet equipped.
- *
- * @return TRUE/FALSE
- *
- * @params
- * - I - item
- * - slot - slot ID
- * - flags - inventory operation hint bitfield, see defines
- * - user - user trying to equip that thing to us there - can be null
- * - denest_to - the old slot we're leaving if called from handle_item_reequip. **extremely** snowflakey
- *
- * todo: refactor nesting to not require this shit
- */
-/mob/proc/can_equip(obj/item/I, slot, flags, mob/user, denest_to)
- // let's NOT.
- if(I && QDELETED(I))
- to_chat(user, SPAN_DANGER("A deleted [I] was checked in can_equip(). Report this entire line to coders immediately. Debug data: [I] ([REF(I)]) slot [slot] flags [flags] user [user]"))
- to_chat(user, SPAN_DANGER("can_equip will now attempt to prevent the deleted item from being equipped. There should be no glitches."))
- return FALSE
-
- var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
- var/self_equip = user == src
- if(!slot_meta)
- . = FALSE
- CRASH("Failed to resolve to slot datm.")
-
- if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
- // special handling: make educated guess, defaulting to yes
- switch(slot_meta.type)
- if(/datum/inventory_slot/abstract/hand/left)
- return (flags & INV_OP_FORCE) || !get_left_held_item()
- if(/datum/inventory_slot/abstract/hand/right)
- return (flags & INV_OP_FORCE) || !get_right_held_item()
- if(/datum/inventory_slot/abstract/put_in_backpack)
- var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
- return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
- if(/datum/inventory_slot/abstract/put_in_belt)
- var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
- return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
- if(/datum/inventory_slot/abstract/put_in_hands)
- return (flags & INV_OP_FORCE) || !hands_full()
- return TRUE
-
- if(!inventory_slot_bodypart_check(I, slot, user, flags) && !(flags & INV_OP_FORCE))
- return FALSE
-
- var/conflict_result = inventory_slot_conflict_check(I, slot, flags)
- var/obj/item/to_wear_over
-
- if((flags & INV_OP_IS_FINAL_CHECK) && conflict_result && (slot != SLOT_ID_HANDS))
- // try to fit over
- var/obj/item/conflicting = item_by_slot_id(slot)
- if(conflicting)
- // there's something there
- var/can_fit_over = I.equip_worn_over_check(src, slot, user, conflicting, flags)
- if(can_fit_over)
- conflict_result = CAN_EQUIP_SLOT_CONFLICT_NONE
- to_wear_over = conflicting
- // ! DANGER: snowflake time
- // take it out of the slot
- _unequip_slot(slot, flags | INV_OP_NO_LOGIC | INV_OP_NO_UPDATE_ICONS)
- // recheck
- conflict_result = inventory_slot_conflict_check(I, slot)
- // put it back in incase something else breaks
- _equip_slot(conflicting, slot, flags | INV_OP_NO_LOGIC | INV_OP_NO_UPDATE_ICONS)
-
- switch(conflict_result)
- if(CAN_EQUIP_SLOT_CONFLICT_HARD)
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, SPAN_WARNING("[self_equip? "You" : "They"] are already [slot_meta.display_plural? "holding too many things" : "wearing something"] [slot_meta.display_preposition] [self_equip? "your" : "their"] [slot_meta.display_name]."))
- return FALSE
- if(CAN_EQUIP_SLOT_CONFLICT_SOFT)
- if(!(flags & INV_OP_FORCE))
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, SPAN_WARNING("[self_equip? "You" : "They"] are already [slot_meta.display_plural? "holding too many things" : "wearing something"] [slot_meta.display_preposition] [self_equip? "your" : "their"] [slot_meta.display_name]."))
- return FALSE
-
- if(!inventory_slot_semantic_conflict(I, slot, user) && !(flags & INV_OP_FORCE))
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, SPAN_WARNING("[I] doesn't fit there."))
- return FALSE
-
- var/blocked_by
-
- if((blocked_by = inventory_slot_reachability_conflict(I, slot, user)) && !(flags & (INV_OP_FORCE | INV_OP_IGNORE_REACHABILITY)))
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, SPAN_WARNING("\the [blocked_by] is in the way!"))
- return FALSE
-
- // lastly, check item's opinion
- if(!I.can_equip(src, slot, user, flags))
- return FALSE
-
- // we're the final check - side effects ARE allowed
- if((flags & INV_OP_IS_FINAL_CHECK) && to_wear_over)
- //! Note: this means that can_unequip is NOT called for to wear over.
- //! This is intentional, but very, very sonwflakey.
- to_wear_over.worn_inside = I
- // setting worn inside first disallows equip/unequip from triggering
- to_wear_over.forceMove(I)
- // check we don't have something already (wtf)
- if(I.worn_over)
- handle_item_denesting(I, denest_to, flags, user)
- // set the other way around
- I.worn_over = to_wear_over
- // tell it we're inserting the old item
- I.equip_on_worn_over_insert(src, slot, user, to_wear_over, flags)
- // take the old item off our screen
- client?.screen -= to_wear_over
- to_wear_over.screen_loc = null
- to_wear_over.hud_unlayerise()
- // we don't call slot re-equips here because the equip proc does this for us
-
- return TRUE
-
-/**
- * checks if we are missing the bodypart for a slot
- * return FALSE if we are missing, or TRUE if we're not
- *
- * this proc should give the feedback of what's missing!
- */
-/mob/proc/inventory_slot_bodypart_check(obj/item/I, slot, mob/user, flags)
- return TRUE
/**
* drop items if a bodypart is missing
@@ -477,7 +128,7 @@
var/list/obj/item/affected
switch(bodypart)
if(BP_HEAD)
- affected = items_by_slot(
+ affected = items_by_slot_id(
SLOT_ID_HEAD,
SLOT_ID_LEFT_EAR,
SLOT_ID_RIGHT_EAR,
@@ -485,7 +136,7 @@
SLOT_ID_GLASSES
)
if(BP_GROIN, BP_TORSO)
- affected = items_by_slot(
+ affected = items_by_slot_id(
SLOT_ID_BACK,
SLOT_ID_BELT,
SLOT_ID_SUIT,
@@ -495,12 +146,12 @@
SLOT_ID_UNIFORM
)
if(BP_L_ARM, BP_L_HAND, BP_R_ARM, BP_R_HAND)
- affected = items_by_slot(
+ affected = items_by_slot_id(
SLOT_ID_HANDCUFFED,
SLOT_ID_GLOVES
)
if(BP_L_LEG, BP_L_FOOT, BP_R_LEG, BP_R_FOOT)
- affected = items_by_slot(
+ affected = items_by_slot_id(
SLOT_ID_LEGCUFFED,
SLOT_ID_SHOES
)
@@ -511,384 +162,3 @@
for(var/obj/item/I as anything in affected)
if(!inventory_slot_bodypart_check(I, I.worn_slot, null, INV_OP_SILENT))
drop_item_to_ground(I, INV_OP_SILENT)
-
-/**
- * checks for slot conflict
- */
-/mob/proc/inventory_slot_conflict_check(obj/item/I, slot, flags)
- var/obj/item/conflicting = _item_by_slot(slot)
- if(conflicting)
- if((flags & (INV_OP_CAN_DISPLACE | INV_OP_IS_FINAL_CHECK)) == (INV_OP_CAN_DISPLACE | INV_OP_IS_FINAL_CHECK))
- drop_item_to_ground(conflicting, INV_OP_FORCE)
- if(_item_by_slot(slot))
- return CAN_EQUIP_SLOT_CONFLICT_HARD
- else
- return CAN_EQUIP_SLOT_CONFLICT_HARD
- switch(slot)
- if(SLOT_ID_LEFT_EAR, SLOT_ID_RIGHT_EAR)
- if(I.slot_flags & SLOT_TWOEARS)
- if(_item_by_slot(SLOT_ID_LEFT_EAR) || _item_by_slot(SLOT_ID_RIGHT_EAR))
- return CAN_EQUIP_SLOT_CONFLICT_SOFT
- else
- var/obj/item/left_ear = _item_by_slot(SLOT_ID_LEFT_EAR)
- var/obj/item/right_ear = _item_by_slot(SLOT_ID_RIGHT_EAR)
- if(left_ear && left_ear != INVENTORY_SLOT_DOES_NOT_EXIST && left_ear != I && left_ear.slot_flags & SLOT_TWOEARS)
- return CAN_EQUIP_SLOT_CONFLICT_SOFT
- else if(right_ear && right_ear != INVENTORY_SLOT_DOES_NOT_EXIST && right_ear != I && right_ear.slot_flags & SLOT_TWOEARS)
- return CAN_EQUIP_SLOT_CONFLICT_SOFT
- return CAN_EQUIP_SLOT_CONFLICT_NONE
-
-/**
- * checks if you can reach a slot
- * return null or the first item blocking
- */
-/mob/proc/inventory_slot_reachability_conflict(obj/item/I, slot, mob/user)
- return null
-
-/**
- * semantic check - should this item fit here? slot flag checks/etc should go in here.
- *
- * return TRUE if conflicting, otherwise FALSE
- */
-/mob/proc/inventory_slot_semantic_conflict(obj/item/I, datum/inventory_slot/slot, mob/user)
- . = FALSE
- slot = resolve_inventory_slot(slot)
- return slot._equip_check(I, src, user)
-
-/**
- * handles internal logic of equipping an item
- *
- * @params
- * - I - item to equip
- * - flags - inventory operation hint flags, see defines
- * - slot - slot to equip it to
- * - user - user trying to put it on us
- *
- * @return TRUE/FALSE on success
- */
-/mob/proc/_equip_item(obj/item/I, flags, slot, mob/user = src)
- PROTECTED_PROC(TRUE)
-
- if(!I) // how tf would we put on "null"?
- return FALSE
-
- // resolve slot
- var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
- if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
- // if it's abstract, we go there directly - do not use can_equip as that will just guess.
- return handle_abstract_slot_insertion(I, slot, flags)
-
- // slots must have IDs.
- ASSERT(!isnull(slot_meta.id))
- // convert to ID after abstract slot checks
- slot = slot_meta.id
-
- var/old_slot = slot_id_by_item(I)
-
- if(old_slot)
- . = _handle_item_reequip(I, slot, old_slot, flags, user)
- if(!.)
- return
-
- log_inventory("[key_name(src)] moved [I] from [old_slot] to [slot].")
- else
- if(!can_equip(I, slot, flags | INV_OP_IS_FINAL_CHECK, user))
- return FALSE
-
- var/atom/oldLoc = I.loc
- if(I.loc != src)
- I.forceMove(src)
- if(I.loc != src)
- // UH OH, SOMEONE MOVED US
- log_inventory("[key_name(src)] failed to equip [I] to slot (loc sanity failed).")
- // UH OH x2, WE GOT WORN OVER SOMETHING
- if(I.worn_over)
- handle_item_denesting(I, slot, INV_OP_FATAL, user)
- return FALSE
-
- _equip_slot(I, slot, flags)
-
- log_inventory("[key_name(src)] equipped [I] to [slot].")
-
- // TODO: HANDLE DELETIONS IN PICKUP AND EQUIPPED PROPERLY
- I.pickup(src, flags, oldLoc)
- I.equipped(src, slot, flags)
-
- if(I.zoom)
- I.zoom()
-
- return TRUE
-
-/**
- * checks if we already have something in our inventory
- * if so, this will try to shift the slots over, calling equipped/unequipped automatically
- *
- * INV_OP_FORCE will allow ignoring can unequip.
- *
- * return true/false based on if we succeeded
- */
-/mob/proc/_handle_item_reequip(obj/item/I, slot, old_slot, flags, mob/user = src)
- ASSERT(slot)
- if(!old_slot)
- // DO NOT USE _slot_by_item - at this point, the item has already been var-set into the new slot!
- // slot_id_by_item however uses cached values still!
- old_slot = slot_id_by_item(I)
- if(!old_slot)
- // still not there, wasn't already in inv
- return FALSE
- // this IS a slot shift!
- . = old_slot
- if((slot == old_slot) && (slot != SLOT_ID_HANDS))
- // lol we're done (unless it was hands)
- return TRUE
- if(slot == SLOT_ID_HANDS)
- // if we're going into hands,
- // just check can unequip
- if(!can_unequip(I, old_slot, flags, user))
- // check can unequip
- return FALSE
- // call procs
- if(old_slot == SLOT_ID_HANDS)
- _unequip_held(I, flags)
- else
- _unequip_slot(old_slot, flags)
- I.unequipped(src, old_slot, flags)
- // sigh
- handle_item_denesting(I, old_slot, flags, user)
- // TODO: HANDLE DELETIONS ON EQUIPPED PROPERLY, INCLUDING ON HANDS
- // ? we don't do this on hands, hand procs do it
- // _equip_slot(I, slot, update_icons)
- I.equipped(src, slot, flags)
- log_inventory("[key_name(src)] moved [I] from [old_slot] to hands.")
- // hand procs handle rest
- return TRUE
- else
- // else, this gets painful
- if(!can_unequip(I, old_slot, flags, user))
- return FALSE
- if(!can_equip(I, slot, flags | INV_OP_IS_FINAL_CHECK, user, old_slot))
- return FALSE
- // ?if it's from hands, hands aren't a slot.
- if(old_slot == SLOT_ID_HANDS)
- _unequip_held(I, flags)
- else
- _unequip_slot(old_slot, flags)
- I.unequipped(src, old_slot, flags)
- // TODO: HANDLE DELETIONS ON EQUIPPED PROPERLY
- // sigh
- _equip_slot(I, slot, flags)
- I.equipped(src, slot, flags)
- log_inventory("[key_name(src)] moved [I] from [old_slot] to [slot].")
- return TRUE
-
-/**
- * handles removing an item from our hud
- *
- * some things call us from outside inventory code. this is shitcode and shouldn't be propageted.
- */
-/mob/proc/_handle_inventory_hud_remove(obj/item/I)
- if(client)
- client.screen -= I
- I.screen_loc = null
-
-/**
- * handles adding an item or updating an item to our hud
- */
-/mob/proc/_handle_inventory_hud_update(obj/item/I, slot)
- var/datum/inventory_slot/meta = resolve_inventory_slot(slot)
- I.screen_loc = meta.hud_position
- if(client)
- client.screen |= I
-
-/**
- * get all equipped items
- *
- * @params
- * include_inhands - include held items too?
- * include_restraints - include restraints too?
- */
-/mob/proc/get_equipped_items(include_inhands, include_restraints)
- return get_held_items() + _get_all_slots(include_restraints)
-
-/**
- * wipe our inventory
- *
- * @params
- * include_inhands - include held items too?
- * include_restraints - include restraints too?
- */
-/mob/proc/delete_inventory(include_inhands = TRUE, include_restraints = TRUE)
- for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
- qdel(I)
-
-/**
- * drops everything in our inventory
- *
- * @params
- * - include_inhands - include held items too?
- * - include_restraints - include restraints too?
- * - force - ignore nodrop and all that
- */
-/mob/proc/drop_inventory(include_inhands = TRUE, include_restraints = TRUE, force = TRUE)
- for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
- drop_item_to_ground(I, INV_OP_SILENT | INV_OP_FLUFFLESS | (force? INV_OP_FORCE : NONE))
-
- // todo: handle what happens if dropping something requires a logic thing
- // e.g. dropping jumpsuit makes it impossible to transfer a belt since it
- // de-equipped from the jumpsuit
-
-/mob/proc/transfer_inventory_to_loc(atom/newLoc, include_inhands = TRUE, include_restraints = TRUE, force = TRUE)
- for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
- transfer_item_to_loc(I, newLoc, INV_OP_SILENT | INV_OP_FLUFFLESS | (force? INV_OP_FORCE : NONE))
- // todo: handle what happens if dropping something requires a logic thing
- // e.g. dropping jumpsuit makes it impossible to transfer a belt since it
- // de-equipped from the jumpsuit
-
-/**
- * gets the primary item in a slot
- * null if not in inventory. inhands don't count as inventory here, use held item procs.
- */
-/mob/proc/item_by_slot_id(slot)
- return _item_by_slot(slot) // why the needless indirection? so people don't override this for slots!
-
-/**
- * gets the primary item and nested items (e.g. gloves, magboots, accessories) in a slot
- * null if not in inventory, otherwise list
- * inhands do not count as inventory
- */
-/mob/proc/items_by_slot(slot)
- var/obj/item/I = _item_by_slot(slot)
- if(!I)
- return list()
- I = I._inv_return_attached()
- return islist(I)? I : list(I)
-
-/**
- * returns if we have something equipped - the slot if it is, null if not
- *
- * SLOT_ID_HANDS if in hands
- */
-/mob/proc/is_in_inventory(obj/item/I)
- return (I?.worn_mob() == src) && I.worn_slot
- // we use entirely cached vars for speed.
- // if this returns bad data well fuck you, don't break equipped()/unequipped().
-
-/**
- * returns if an item is in inventory (equipped) rather than hands
- */
-/mob/proc/is_wearing(obj/item/I)
- var/slot = is_in_inventory(I)
- return slot && (slot != SLOT_ID_HANDS)
-
-/**
- * get slot of item if it's equipped.
- * null if not in inventory. SLOT_HANDS if held.
- */
-/mob/proc/slot_id_by_item(obj/item/I)
- return is_in_inventory(I) || null // short circuited to that too
- // if equipped/unequipped didn't set worn_slot well jokes on you lmfao
-
-/mob/proc/_equip_slot(obj/item/I, slot, flags)
- SHOULD_NOT_OVERRIDE(TRUE)
- . = _set_inv_slot(slot, I, flags) != INVENTORY_SLOT_DOES_NOT_EXIST
-
-/mob/proc/_unequip_slot(slot, flags)
- SHOULD_NOT_OVERRIDE(TRUE)
- . = _set_inv_slot(slot, null, flags) != INVENTORY_SLOT_DOES_NOT_EXIST
-
-/mob/proc/_unequip_held(obj/item/I, flags)
- return
-
-/mob/proc/has_slot(id)
- SHOULD_NOT_OVERRIDE(TRUE)
- return _item_by_slot(id) != INVENTORY_SLOT_DOES_NOT_EXIST
-
-// todo: both of these below procs needs optimization for when we need the datum anyways, to avoid two lookups
-
-/mob/proc/semantically_has_slot(id)
- return has_slot(id) && _semantic_slot_id_check(id)
-
-/mob/proc/get_inventory_slot_ids(semantic, sorted)
- // get all
- if(sorted)
- . = list()
- for(var/id as anything in GLOB.inventory_slot_meta)
- if(!semantically_has_slot(id))
- continue
- . += id
- return
- else
- . = _get_inventory_slot_ids()
- // check if we should filter
- if(!semantic)
- return
- . = _get_inventory_slot_ids()
- for(var/id in .)
- if(!_semantic_slot_id_check(id))
- . -= id
-
-/**
- * THESE PROCS MUST BE OVERRIDDEN FOR NEW SLOTS ON MOBS
- * yes, i managed to shove all basic behaviors that needed overriding into 5-6 procs
- * you're
- * welcome.
- *
- * These are UNSAFE PROCS.
- *
- * oh and can_equip_x* might need overriding for complex mobs like humans but frankly
- * sue me, there's no better way right now.
- */
-
-/**
- * sets a slot to icon or null
- *
- * some behaviors may be included other than update icons
- * even update icons is unpreferred but we're stuck with this for now.
- *
- * todo: logic should be moved out of the proc, but where?
- *
- * @params
- * slot - slot to set
- * I - item or null
- * update_icons - update icons immediately?
- * logic - apply logic like dropping stuff from pockets when unequippiing a jumpsuit imemdiately?
- */
-/mob/proc/_set_inv_slot(slot, obj/item/I, flags)
- PROTECTED_PROC(TRUE)
- . = INVENTORY_SLOT_DOES_NOT_EXIST
- CRASH("Attempting to set inv slot of [slot] to [I] went to base /mob. You probably had someone assigning to a nonexistant slot!")
-
-/**
- * ""expensive"" proc that scans for the real slot of an item
- * usually used when safety checks detect something is amiss
- */
-/mob/proc/_slot_by_item(obj/item/I)
- PROTECTED_PROC(TRUE)
-
-/**
- * doubles as slot detection
- * returns -1 if no slot
- * YES, MAGIC VALUE BUT SOLE USER IS 20 LINES ABOVE, SUE ME.
- */
-/mob/proc/_item_by_slot(slot)
- PROTECTED_PROC(TRUE)
- return INVENTORY_SLOT_DOES_NOT_EXIST
-
-/mob/proc/_get_all_slots(include_restraints)
- PROTECTED_PROC(TRUE)
- return list()
-
-/**
- * return all slot ids we implement
- */
-/mob/proc/_get_inventory_slot_ids()
- PROTECTED_PROC(TRUE)
- return list()
-
-/**
- * override this if you need to make a slot not semantically exist
- * useful for other species that don't have a slot so you don't have jumpsuit requirements apply
- */
-/mob/proc/_semantic_slot_id_check(id)
- PROTECTED_PROC(TRUE)
- return TRUE
diff --git a/code/modules/mob/inventory/inventory_slot.dm b/code/modules/mob/inventory/inventory_slot.dm
index 5ef3563998cb..5b5df236dec9 100644
--- a/code/modules/mob/inventory/inventory_slot.dm
+++ b/code/modules/mob/inventory/inventory_slot.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
/// global slot meta cache - all ids must be string!
/// initialized by SSearly_init
GLOBAL_LIST_EMPTY(inventory_slot_meta)
@@ -66,11 +69,11 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
CRASH("Failed to do type lookup for [type].")
/**
- * inventory slot meta
+ * inventory slot metadata
* stores all the required information for an inventory slot
*
- * **Typepaths for these are used directly in most circumstances of slot IDs**
- * **Use get_inventory_slot_meta(id) to automatically translate anything to the static datum.**
+ * **Typepaths for these are used directly in many circumstances instead of their slot IDs**
+ * **Use resolve_inventory_slot(id) to automatically translate anything to the static datum.**
*
* ABSTRACT SLOTS:
* Abstract slots attempts to do something special, based on mob.
@@ -93,9 +96,19 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/// display order - higher is upper. a
is applied on 0.
var/sort_order = 0
- //* HUD
- /// our screen loc
- var/hud_position
+ //* HUD *//
+ /// do we render on hud at all?
+ var/inventory_hud_rendered = FALSE
+ /// our anchoring enum
+ var/inventory_hud_anchor = INVENTORY_HUD_ANCHOR_AUTOMATIC
+ /// our hiding class
+ var/inventory_hud_class = INVENTORY_HUD_CLASS_ALWAYS
+ /// preferred main axis offset
+ var/inventory_hud_main_axis = 0
+ /// preferred cross axis offset
+ var/inventory_hud_cross_axis = 0
+ /// hud icon state in hud style
+ var/inventory_hud_icon_state = ""
//* Grammar
/// player friendly name
@@ -255,6 +268,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/datum/inventory_slot/inventory
abstract_type = /datum/inventory_slot/inventory
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_HUD_REQUIRES_EXPAND | INV_SLOT_CONSIDERED_WORN
+ inventory_hud_rendered = TRUE
/datum/inventory_slot/inventory/back
name = "back"
@@ -263,7 +277,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 2000
display_name = "back"
display_preposition = "on"
- hud_position = ui_back
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_HANDS
+ inventory_hud_main_axis = -1
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "back"
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN
slot_flags_required = SLOT_BACK
@@ -285,7 +304,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 5000
display_name = "body"
display_preposition = "on"
- hud_position = ui_iclothing
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 1
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "uniform"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_ICLOTHING
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_HUD_REQUIRES_EXPAND | INV_SLOT_CONSIDERED_WORN
@@ -376,7 +401,6 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
return FALSE
return ..()
-
/datum/inventory_slot/inventory/uniform/render(mob/wearer, obj/item/item, bodytype)
. = ..()
if(!ishuman(wearer))
@@ -409,7 +433,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 10000
display_name = "head"
display_preposition = "on"
- hud_position = ui_head
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 4
+ inventory_hud_cross_axis = 1
+ inventory_hud_icon_state = "head"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_HEAD
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_HUD_REQUIRES_EXPAND | INV_SLOT_CONSIDERED_WORN
@@ -445,7 +475,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
display_name = "suit"
display_preposition = "over"
- hud_position = ui_oclothing
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 1
+ inventory_hud_cross_axis = 1
+ inventory_hud_icon_state = "suit"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_OCLOTHING
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_HUD_REQUIRES_EXPAND | INV_SLOT_CONSIDERED_WORN
@@ -513,7 +548,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 6000
display_name = "waist"
display_preposition = "on"
- hud_position = ui_belt
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_HANDS
+ inventory_hud_main_axis = -2
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "belt"
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_BELT
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN
@@ -545,14 +585,22 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
id = SLOT_ID_LEFT_POCKET
display_name = "left pocket"
display_preposition = "in"
- hud_position = ui_storage1
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_HANDS
+ inventory_hud_main_axis = 1
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "pocket"
/datum/inventory_slot/inventory/pocket/right
name = "right pocket"
id = SLOT_ID_RIGHT_POCKET
display_name = "right pocket"
display_preposition = "in"
- hud_position = ui_storage2
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_HANDS
+ inventory_hud_main_axis = 2
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "pocket"
/datum/inventory_slot/inventory/id
name = "id"
@@ -561,7 +609,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 3000
display_name = "badge"
display_preposition = "as"
- hud_position = ui_id
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_HANDS
+ inventory_hud_main_axis = -3
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "id"
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_ID
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN
@@ -592,7 +645,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 4000
display_name = "feet"
display_preposition = "on"
- hud_position = ui_shoes
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_cross_axis = 1
+ inventory_hud_icon_state = "shoes"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_FEET
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN | INV_SLOT_HUD_REQUIRES_EXPAND
@@ -628,7 +686,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 6500
display_name = "hands"
display_preposition = "on"
- hud_position = ui_gloves
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 1
+ inventory_hud_cross_axis = 2
+ inventory_hud_icon_state = "gloves"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_GLOVES
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN | INV_SLOT_HUD_REQUIRES_EXPAND
@@ -651,7 +715,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 7500
display_name = "eyes"
display_preposition = "over"
- hud_position = ui_glasses
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 2
+ inventory_hud_cross_axis = 0
+ inventory_hud_icon_state = "glasses"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_EYES
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN | INV_SLOT_HUD_REQUIRES_EXPAND
@@ -674,7 +744,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 500
display_name = "suit"
display_preposition = "on"
- hud_position = ui_sstore1
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 0
+ inventory_hud_cross_axis = 2
+ inventory_hud_icon_state = "suit-store"
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_PROC
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE
render_layer = HUMAN_LAYER_SLOT_SUITSTORE
@@ -723,7 +798,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
id = SLOT_ID_LEFT_EAR
display_name = "left ear"
display_preposition = "on"
- hud_position = ui_l_ear
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 3
+ inventory_hud_cross_axis = 2
+ inventory_hud_icon_state = "ears"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_EARS
@@ -733,7 +814,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
id = SLOT_ID_RIGHT_EAR
display_name = "right ear"
display_preposition = "on"
- hud_position = ui_r_ear
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 4
+ inventory_hud_cross_axis = 2
+ inventory_hud_icon_state = "ears"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_EARS
render_layer = HUMAN_LAYER_SLOT_EARS
@@ -745,7 +832,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
sort_order = 9250
display_name = "face"
display_preposition = "on"
- hud_position = ui_mask
+
+ inventory_hud_anchor = INVENTORY_HUD_ANCHOR_TO_DRAWER
+ inventory_hud_main_axis = 3
+ inventory_hud_cross_axis = 1
+ inventory_hud_icon_state = "mask"
+ inventory_hud_class = INVENTORY_HUD_CLASS_DRAWER
+
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_MASK
render_default_icons = list(
diff --git a/code/modules/mob/inventory/mobs.dm b/code/modules/mob/inventory/mobs.dm
deleted file mode 100644
index 44f83b5254e6..000000000000
--- a/code/modules/mob/inventory/mobs.dm
+++ /dev/null
@@ -1,185 +0,0 @@
-//? init
-
-/mob/proc/init_inventory()
- return
-
-//? equip
-
-/**
- * equips an item to a slot if possible
- *
- * @params
- * - I - item
- * - slot - the slot
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-/mob/proc/equip_to_slot_if_possible(obj/item/I, slot, flags, mob/user)
- return _equip_item(I, flags, slot, user)
-
-/**
- * equips an item to a slot if possible
- * item is deleted on failure
- *
- * @params
- * - I - item
- * - slot - the slot
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-/mob/proc/equip_to_slot_or_del(obj/item/I, slot, flags, mob/user)
- . = equip_to_slot_if_possible(I, slot, flags, user)
- if(!.)
- qdel(I)
-/**
- * automatically equips to the best inventory (non storage!) slot we can find for an item, if possible
- * this proc is silent for the sub-calls by default to prevent spam.
- *
- * @params
- * - I - item
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-/mob/proc/equip_to_appropriate_slot(obj/item/I, flags, mob/user)
- for(var/slot in GLOB.slot_equipment_priority)
- if(equip_to_slot_if_possible(I, slot, flags | INV_OP_SUPPRESS_WARNING, user))
- return TRUE
- if(!(flags & INV_OP_SUPPRESS_WARNING))
- to_chat(user, user == src? SPAN_WARNING("You can't find somewhere to equip [I] to!") : SPAN_WARNING("[src] has nowhere to equip [I] to!"))
- return FALSE
-
-/**
- * automatically equips to the best inventory (non storage!) slot we can find for an item, if possible
- *
- * item is deleted on failure.
- *
- * @params
- * - I - item
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-
-/mob/proc/equip_to_appropriate_slot_or_del(obj/item/I, flags, mob/user)
- if(!equip_to_appropriate_slot(I, flags, user))
- qdel(I)
-
-/**
- * forcefully equips an item to a slot
- * kicks out conflicting items if possible
- *
- * This CAN fail, so listen to return value
- * Why? YOU MIGHT EQUIP TO A MOB WITHOUT A CERTAIN SLOT!
- *
- * @params
- * - I - item
- * - slot - slot to equip to
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-/mob/proc/force_equip_to_slot(obj/item/I, slot, flags, mob/user)
- return _equip_item(I, flags | INV_OP_FORCE | INV_OP_CAN_DISPLACE, slot, user)
-
-/**
- * forcefully equips an item to a slot
- * kicks out conflicting items if possible
- * if still failing, item is deleted
- *
- * this can fail, so listen to return values.
- * @params
- * - I - item
- * - slot - slot to equip to
- * - flags - inventory operation hint bitfield, see defines
- * - user - the user doing the action, if any. defaults to ourselves.
- *
- * @return TRUE/FALSE
- */
-/mob/proc/force_equip_to_slot_or_del(obj/item/I, slot, flags, mob/user)
- if(!force_equip_to_slot(I, slot, flags, user))
- qdel(I)
- return FALSE
- return TRUE
-
-//? drop
-
-// So why do all of these return true if the item is null?
-// Semantically, transferring/dropping nothing always works
-// This lets us tidy up other pieces of code by not having to typecheck everything.
-// However, if you do pass in an invalid object instead of null, the procs will fail or pass
-// depending on needed behavior.
-// Use the helpers when you can, it's easier for everyone and makes replacing behaviors later easier.
-
-// This logic is actually **stricter** than the previous logic, which was "if item doesn't exist, it always works"
-
-/**
- * drops an item to ground
- *
- * semantically returns true if the thing is no longer in our inventory after our call, whether or not we dropped it
- * if you require better checks, check if something is in inventory first.
- *
- * if the item is null, this returns true
- * if an item is not in us, this returns true
- */
-/mob/proc/drop_item_to_ground(obj/item/I, flags, mob/user = src)
- // destroyed IS allowed to call these procs
- if(I && QDELETED(I) && !QDESTROYING(I))
- to_chat(user, SPAN_DANGER("A deleted item [I] was used in drop_item_to_ground(). Report the entire line to coders. Debugging information: [I] ([REF(I)]) flags [flags] user [user]"))
- to_chat(user, SPAN_DANGER("Drop item to ground will now proceed, ignoring the bugged state. Errors may ensue."))
- else if(!is_in_inventory(I))
- return TRUE
- return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, drop_location(), user)
-
-/**
- * transfers an item somewhere
- * newloc MUST EXIST, use transfer_item_to_nullspace otherwise
- *
- * semantically returns true if we transferred something from our inventory to newloc in the call
- *
- * if the item is null, this returns true
- * if an item is not in us, this crashes
- */
-/mob/proc/transfer_item_to_loc(obj/item/I, newloc, flags, mob/user)
- if(!I)
- return TRUE
- ASSERT(newloc)
- if(!is_in_inventory(I))
- return FALSE
- return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, newloc, user)
-
-/**
- * transfers an item into nullspace
- *
- * semantically returns true if we transferred something from our inventory to null in the call
- *
- * if the item is null, this returns true
- * if an item is not in us, this crashes
- */
-/mob/proc/transfer_item_to_nullspace(obj/item/I, flags, mob/user)
- if(!I)
- return TRUE
- if(!is_in_inventory(I))
- return FALSE
- return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, null, user)
-
-/**
- * removes an item from inventory. does NOT move it.
- * item MUST be qdel'd or moved after this if it returns TRUE!
- *
- * semantically returns true if the passed item is no longer in our inventory after the call
- *
- * if the item is null, ths returns true
- * if an item is not in us, this returns true
- */
-/mob/proc/temporarily_remove_from_inventory(obj/item/I, flags, mob/user)
- if(!is_in_inventory(I))
- return TRUE
- return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, FALSE, user)
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory_legacy.dm
similarity index 70%
rename from code/modules/mob/inventory.dm
rename to code/modules/mob/inventory_legacy.dm
index de100a4abc1d..d616c11b785b 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory_legacy.dm
@@ -1,20 +1,3 @@
-// todo: see all this? needs to be decided what to do with and shoved into the inventory handling system proper once evaluated.
-
-//This proc is called whenever someone clicks an inventory ui slot.
-/mob/proc/attack_ui(var/slot)
- var/obj/item/W = get_active_held_item()
-
- var/obj/item/E = item_by_slot_id(slot)
- if (istype(E))
- if(istype(W))
- E.attackby(W,src)
- else
- E.attack_hand(src)
- else
- equip_to_slot_if_possible(W, slot)
-
-//! helpers below
-
/**
* smart equips an item - puts in a slot or tries to put it in storage.
*/
@@ -54,11 +37,3 @@
// todo: actual flag like BUCKLING_IS_CONSIDERED_RESTRICTING or something
if(buckled?.buckle_flags & (BUCKLING_NO_DEFAULT_RESIST | BUCKLING_NO_DEFAULT_UNBUCKLE))
unbuckle(BUCKLE_OP_FORCE)
-
-//* Carry Weight
-
-/mob/proc/update_carry_slowdown()
- return
-
-/mob/proc/update_item_slowdown()
- return
diff --git a/code/modules/mob/living/carbon/alien/alien_attacks.dm b/code/modules/mob/living/carbon/alien/alien_attacks.dm
index 41b16b48f56a..567dbcba5cad 100644
--- a/code/modules/mob/living/carbon/alien/alien_attacks.dm
+++ b/code/modules/mob/living/carbon/alien/alien_attacks.dm
@@ -1,8 +1,3 @@
-//There has to be a better way to define this shit. ~ Z
-//can't equip anything
-/mob/living/carbon/alien/attack_ui(slot_id)
- return
-
/mob/living/carbon/alien/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
. = ..()
if(.)
@@ -25,7 +20,6 @@
grabbed_by += G
G.affecting = src
- G.synch()
LAssailant = L
diff --git a/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm b/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
index 6ad459199922..ad65c3ca1f86 100644
--- a/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
+++ b/code/modules/mob/living/carbon/alien/diona/diona_attacks.dm
@@ -2,7 +2,7 @@
var/mob/living/carbon/human/H = over_object
if(!istype(H) || !Adjacent(H))
return ..()
- if(H.a_intent == "grab" && hat && !H.hands_full())
+ if(H.a_intent == "grab" && hat && !H.are_usable_hands_full())
H.put_in_hands_or_drop(hat)
H.visible_message("\The [H] removes \the [src]'s [hat].")
hat = null
diff --git a/code/modules/mob/living/carbon/carbon-hands.dm b/code/modules/mob/living/carbon/carbon-hands.dm
new file mode 100644
index 000000000000..2d6aa9f2d653
--- /dev/null
+++ b/code/modules/mob/living/carbon/carbon-hands.dm
@@ -0,0 +1,98 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Hands - Abstraction *//
+
+/mob/living/carbon/get_usable_hand_count()
+ // todo: slow as shit
+ return length(get_usable_hand_indices())
+
+/mob/living/carbon/get_usable_hand_indices()
+ // todo: slow as shit
+ . = list()
+ for(var/i in 1 to get_nominal_hand_count())
+ if(get_hand_manipulation_level(i) > HAND_MANIPULATION_NONE)
+ . += i
+
+//* Hands - Checks *//
+
+/mob/living/carbon/get_hand_manipulation_level(index)
+ // todo: implement after organ refactors
+ return HAND_MANIPULATION_PRECISE
+
+/mob/living/carbon/why_hand_manipulation_insufficient(index, manipulation)
+ // todo: implement after organ refactors
+ return list()
+
+//* Hands - Organs *//
+
+/**
+ * Get the hand index of an organ
+ *
+ * * If an organ is responsible for more than one index, this only returns one of them.
+ *
+ * @return numerical index or null
+ */
+/mob/living/carbon/proc/get_hand_of_organ(obj/item/organ/external/organ)
+ switch(organ.organ_tag)
+ if(BP_L_HAND)
+ return 1
+ if(BP_R_HAND)
+ return 2
+ else
+ return null
+
+/**
+ * Get all hand indexes of an organ
+ *
+ * @return list of numerical indices
+ */
+/mob/living/carbon/proc/get_hands_of_organ(obj/item/organ/external/organ) as /list
+ switch(organ.organ_tag)
+ if(BP_L_HAND)
+ return list(1)
+ if(BP_R_HAND)
+ return list(2)
+ return list()
+
+/**
+ * Get the external organ of a hand index
+ *
+ * @params
+ * * index - the hand index
+ *
+ * @return external organ or null
+ */
+/mob/living/carbon/proc/get_hand_organ(index)
+ RETURN_TYPE(/obj/item/organ/external)
+ if(index % 2)
+ return get_organ(BP_L_HAND)
+ else
+ return get_organ(BP_R_HAND)
+
+/**
+ * Get the external organ of a held item
+ *
+ * @params
+ * * held - the held item
+ *
+ * @return external organ or null
+ */
+/mob/living/carbon/proc/get_held_organ(obj/item/held)
+ RETURN_TYPE(/obj/item/organ/external)
+ return get_hand_organ(get_held_index(held))
+
+//* Hands - Organs - Legacy Default Handling *//
+//* To allow for multiple people able to control multiple active hands later, *//
+//* we'll need to pass active hand index through the clickchain / actor handlers. *//
+//*
+//* However, this system isn't in yet, so old code should still use these procs. *//
+
+/**
+ * Get the external organ of the active hand
+ *
+ * * in mobs with no logical arm and only a hand, this returns the hand
+ */
+/mob/living/carbon/proc/get_active_hand_organ(index)
+ RETURN_TYPE(/obj/item/organ/external)
+ return get_hand_organ(active_hand)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 2090b46d05cf..90a288fe23a0 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -8,16 +8,6 @@
if (!default_language && species_language)
default_language = RSlanguages.legacy_resolve_language_name(species_language)
-/mob/living/carbon/BiologicalLife(seconds, times_fired)
- if((. = ..()))
- return
-
- handle_viruses()
-
- // Increase germ_level regularly
- if(germ_level < GERM_LEVEL_AMBIENT && prob(30)) //if you're just standing there, you shouldn't get more germs beyond an ambient level
- germ_level++
-
/mob/living/carbon/Destroy()
qdel(ingested)
qdel(touching)
@@ -28,6 +18,22 @@
qdel(food)
return ..()
+/mob/living/carbon/init_inventory()
+ . = ..()
+ inventory.set_hand_count(2)
+ if(species) // todo: sigh we need to talk about init order; this shouldn't be needed
+ inventory.set_inventory_slots(species.inventory_slots)
+
+/mob/living/carbon/BiologicalLife(seconds, times_fired)
+ if((. = ..()))
+ return
+
+ handle_viruses()
+
+ // Increase germ_level regularly
+ if(germ_level < GERM_LEVEL_AMBIENT && prob(30)) //if you're just standing there, you shouldn't get more germs beyond an ambient level
+ germ_level++
+
/mob/living/carbon/gib()
for(var/mob/M in src)
if(M in src.stomach_contents)
@@ -45,7 +51,7 @@
if (ishuman(M))
var/mob/living/carbon/human/H = M
var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
+ if (H.active_hand % 2)
temp = H.organs_by_name["l_hand"]
if(temp && !temp.is_usable())
to_chat(H, "You can't use your [temp.name]")
@@ -303,7 +309,7 @@
/mob/living/carbon/proc/update_handcuffed()
if(handcuffed)
- drop_all_held_items()
+ drop_held_items()
stop_pulling()
update_inv_handcuffed()
update_mobility()
diff --git a/code/modules/mob/living/carbon/give.dm b/code/modules/mob/living/carbon/give.dm
index e08567103dcd..34796a0c23a0 100644
--- a/code/modules/mob/living/carbon/give.dm
+++ b/code/modules/mob/living/carbon/give.dm
@@ -32,7 +32,7 @@
to_chat(target, "\The [src] seems to have given up on passing \the [I] to you.")
return
- if(target.hands_full())
+ if(target.are_usable_hands_full())
to_chat(target, "Your hands are full.")
to_chat(src, "Their hands are full.")
return
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index a8365424b41c..e1b54ef03507 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -783,11 +783,8 @@
if ("signal")
if (!src.restrained())
var/t1 = round(text2num(param))
- if (isnum(t1))
- if (t1 <= 5 && (!src.r_hand || !src.l_hand))
- message = "raises [t1] finger\s."
- else if (t1 <= 10 && (!src.r_hand && !src.l_hand))
- message = "raises [t1] finger\s."
+ if (isnum(t1) && t1 <= (count_empty_hands() * 5))
+ message = "raises [t1] finger\s."
m_type = 1
if ("smile")
@@ -906,7 +903,7 @@
if ("handshake")
m_type = 1
- if (!src.restrained() && !src.r_hand)
+ if (!restrained() && !are_usable_hands_full())
var/mob/living/M = null
if (param)
for (var/mob/living/A in view(1, null))
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 906f80f456a3..ff5ba89adf81 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -177,19 +177,18 @@
else
. += SPAN_INFO("[icon2html(back, user)] [T.He] [T.has] \a [FORMAT_TEXT_LOOKITEM(back)] on [T.his] back.")
- //left hand
- if(l_hand && l_hand.show_examine)
- if(l_hand.blood_DNA)
- . += SPAN_WARNING("[icon2html(l_hand, user)] [T.He] [T.is] holding [l_hand.gender == PLURAL ? "some" : "a"] [(l_hand.blood_color != SYNTH_BLOOD_COLOUR) ? "blood" : "oil"]-stained [FORMAT_TEXT_LOOKITEM(l_hand)] in [T.his] left hand!")
- else
- . += SPAN_INFO("[icon2html(l_hand, user)] [T.He] [T.is] holding \a [FORMAT_TEXT_LOOKITEM(l_hand)] in [T.his] left hand.")
-
- //right hand
- if(r_hand && r_hand.show_examine)
- if(r_hand.blood_DNA)
- . += SPAN_WARNING("[icon2html(r_hand, user)] [T.He] [T.is] holding [r_hand.gender == PLURAL ? "some" : "a"] [(r_hand.blood_color != SYNTH_BLOOD_COLOUR) ? "blood" : "oil"]-stained [FORMAT_TEXT_LOOKITEM(r_hand)] in [T.his] right hand!")
+ // hands
+ for(var/i in 1 to length(inventory?.held_items))
+ if(isnull(inventory.held_items[i]))
+ continue
+ var/obj/item/held = inventory.held_items[i]
+ if(held.show_examine)
+ continue
+ var/hand_str = (i % 2)? "left hand[i > 2? " #[round(i / 2)]" : ""]" : "right hand[i > 2? " #[round(i / 2)]" : ""]"
+ if(held.blood_DNA)
+ . += SPAN_WARNING("[icon2html(held, user)] [T.He] [T.is] holding [held.gender == PLURAL ? "some" : "a"] [(held.blood_color != SYNTH_BLOOD_COLOUR) ? "blood" : "oil"]-stained [FORMAT_TEXT_LOOKITEM(held)] in [T.his] [hand_str]!")
else
- . += SPAN_INFO("[icon2html(r_hand, user)] [T.He] [T.is] holding \a [FORMAT_TEXT_LOOKITEM(r_hand)] in [T.his] right hand.")
+ . += SPAN_INFO("[icon2html(held, user)] [T.He] [T.is] holding \a [FORMAT_TEXT_LOOKITEM(held)] in [T.his] [hand_str].")
//gloves
if(gloves && !(skip_gear & EXAMINE_SKIPGLOVES) && gloves.show_examine)
diff --git a/code/modules/mob/living/carbon/human/human-defense-legacy.dm b/code/modules/mob/living/carbon/human/human-defense-legacy.dm
index 5e0f68cf84ea..4d0e86fa83fb 100644
--- a/code/modules/mob/living/carbon/human/human-defense-legacy.dm
+++ b/code/modules/mob/living/carbon/human/human-defense-legacy.dm
@@ -12,9 +12,9 @@
if(BP_L_HAND, BP_R_HAND)
var/c_hand
if (def_zone == BP_L_HAND)
- c_hand = l_hand
+ c_hand = get_left_held_item()
else
- c_hand = r_hand
+ c_hand = get_right_held_item()
if(c_hand && (stun_amount || agony_amount > 10))
msg_admin_attack("[key_name(src)] was disarmed by a stun effect")
@@ -426,7 +426,7 @@
/mob/living/carbon/human/proc/can_catch(var/obj/O)
if(!get_active_held_item()) // If active hand is empty
var/obj/item/organ/external/temp = organs_by_name["r_hand"]
- if (hand)
+ if (active_hand % 2)
temp = organs_by_name["l_hand"]
if(temp && !temp.is_usable())
return FALSE // The hand isn't working in the first place
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index f64d28e2f48d..7610c6191d36 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1129,8 +1129,8 @@
// i seriously hate vorecode
species.on_apply(src)
- // set our has hands
- has_hands = (species && species.hud)? species.hud.has_hands : TRUE
+ inventory.set_inventory_slots(species.inventory_slots)
+ inventory.set_hand_count(species.hud? (species.hud.has_hands ? 2 : 0) : 2)
// until we unfuck hud datums, this will force reload our entire hud
if(hud_used)
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 5c7aa6f329fc..5bf483f4fa42 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -30,7 +30,7 @@
var/mob/living/carbon/human/H = user
if(istype(H))
var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if(H.hand)
+ if(H.active_hand % 2)
temp = H.organs_by_name["l_hand"]
if(!temp || !temp.is_usable())
to_chat(H, "You can't use your hand.")
@@ -92,7 +92,6 @@
if(!G) //the grab will delete itself in New if affecting is anchored
return
L.put_in_active_hand(G)
- G.synch()
LAssailant = L
H.do_attack_animation(src)
@@ -292,7 +291,7 @@
return
//Actually disarm them
- drop_all_held_items()
+ drop_held_items()
visible_message("[L] has disarmed [src]!")
playsound(src, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
@@ -334,14 +333,8 @@
//Used to attack a joint through grabbing
/mob/living/carbon/human/proc/grab_joint(var/mob/living/user, var/def_zone)
- var/has_grab = 0
- for(var/obj/item/grab/G in list(user.l_hand, user.r_hand))
- if(G.affecting == src && G.state == GRAB_NECK)
- has_grab = 1
- break
-
- if(!has_grab)
- return FALSE
+ if(user.check_grab(src) < GRAB_NECK)
+ return
if(!def_zone) def_zone = user.zone_sel.selecting
var/target_zone = check_zone(def_zone)
@@ -366,20 +359,11 @@
success = TRUE
stop_pulling()
- if(istype(l_hand, /obj/item/grab))
- var/obj/item/grab/lgrab = l_hand
- if(lgrab.affecting)
- visible_message("[user] has broken [src]'s grip on [lgrab.affecting]!")
- success = TRUE
- spawn(1)
- qdel(lgrab)
- if(istype(r_hand, /obj/item/grab))
- var/obj/item/grab/rgrab = r_hand
- if(rgrab.affecting)
- visible_message("[user] has broken [src]'s grip on [rgrab.affecting]!")
+ for(var/obj/item/grab/grab as anything in get_held_items_of_type(/obj/item/grab))
+ if(grab.affecting)
+ visible_message("[user] has broken [src]'s grip on [grab.affecting]!")
success = TRUE
- spawn(1)
- qdel(rgrab)
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), grab)
return success
/*
diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm
index 4d9e0db3c5fa..f529701c0e7d 100644
--- a/code/modules/mob/living/carbon/human/human_organs.dm
+++ b/code/modules/mob/living/carbon/human/human_organs.dm
@@ -105,12 +105,9 @@
// Canes and crutches help you stand (if the latter is ever added)
// One cane mitigates a broken leg+foot, or a missing foot.
- var/cane_help=4
- if (l_hand && istype(l_hand, /obj/item/cane))
- stance_damage -= cane_help
- cane_help-=1
- if (r_hand && istype(r_hand, /obj/item/cane))
- stance_damage -= cane_help
+ // Two canes are needed for a lost leg. If you are missing both legs, canes aren't gonna help you.
+ for(var/obj/item/cane/cane as anything in get_held_items_of_type(/obj/item/cane))
+ stance_damage -= 4
// standing is poor
if(stance_damage >= 4 || (stance_damage >= 2 && prob(5)))
@@ -121,67 +118,37 @@
afflict_paralyze(20 * 5) //can't emote while weakened, apparently.
/mob/living/carbon/human/proc/handle_grasp()
- if(!l_hand && !r_hand)
- return
-
- // You should not be able to pick anything up, but stranger things have happened.
- if(l_hand)
- for(var/limb_tag in list(BP_L_HAND, BP_L_ARM))
- var/obj/item/organ/external/E = get_organ(limb_tag)
- if(!E)
- visible_message("Lacking a functioning left hand, \the [src] drops \the [l_hand].")
- drop_left_held_item(INV_OP_FORCE)
- break
-
- if(r_hand)
- for(var/limb_tag in list(BP_R_HAND, BP_R_ARM))
- var/obj/item/organ/external/E = get_organ(limb_tag)
- if(!E)
- visible_message("Lacking a functioning right hand, \the [src] drops \the [r_hand].")
- drop_right_held_item(INV_OP_FORCE)
- break
-
- // Check again...
- if(!l_hand && !r_hand)
- return
-
- for (var/obj/item/organ/external/E in organs)
- if(!E || !E.can_grasp)
+ for(var/i in 1 to length(inventory?.held_items))
+ var/obj/item/held = inventory.held_items[i]
+ if(isnull(held))
continue
-
- if((E.is_broken() || E.is_dislocated()) && !E.splinted)
- switch(E.body_part_flags)
- if(HAND_LEFT, ARM_LEFT)
- if(!l_hand)
- continue
- drop_left_held_item()
- if(HAND_RIGHT, ARM_RIGHT)
- if(!r_hand)
- continue
- drop_right_held_item()
-
+ var/obj/item/organ/external/hand = get_hand_organ(i)
+ var/obj/item/organ/external/arm = get_organ(hand.parent_organ)
+ if(isnull(arm) || isnull(hand))
+ visible_message("Lacking a functioning left hand, \the [src] drops \the [held].")
+ drop_item_to_ground(held, INV_OP_FORCE)
+ continue
+ if(((hand.is_broken() || hand.is_dislocated()) && !hand.splinted) || ((arm.is_broken() || arm.is_dislocated()) && !arm.splinted))
var/emote_scream = pick("screams in pain and ", "lets out a sharp cry and ", "cries out and ")
- emote("me", 1, "[(can_feel_pain()) ? "" : emote_scream ]drops what they were holding in their [E.name]!")
-
- else if(E.is_malfunctioning())
- switch(E.body_part_flags)
- if(HAND_LEFT, ARM_LEFT)
- if(!l_hand)
- continue
- drop_left_held_item()
- if(HAND_RIGHT, ARM_RIGHT)
- if(!r_hand)
- continue
- drop_right_held_item()
-
- emote("me", 1, "drops what they were holding, their [E.name] malfunctioning!")
-
+ emote("me", 1, "[(can_feel_pain()) ? "" : emote_scream ]drops what they were holding in their [hand.name]!")
+ drop_item_to_ground(held, INV_OP_FORCE)
+ continue
+ else if(hand.is_malfunctioning())
+ emote("me", 1, "drops what they were holding, their [hand.name] malfunctioning!")
+ drop_item_to_ground(held, INV_OP_FORCE)
+ var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
+ spark_system.set_up(5, 0, src)
+ spark_system.attach(src)
+ spark_system.start()
+ QDEL_IN(spark_system, 1 SECONDS)
+ else if(arm.is_malfunctioning())
+ emote("me", 1, "drops what they were holding, their [hand.name] malfunctioning!")
+ drop_item_to_ground(held, INV_OP_FORCE)
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
spark_system.start()
- spawn(10)
- qdel(spark_system)
+ QDEL_IN(spark_system, 1 SECONDS)
//Handles chem traces
/mob/living/carbon/human/proc/handle_trace_chems()
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 8c67c1da4092..17c1c6da0818 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -132,31 +132,31 @@
/mob/living/carbon/human/_get_all_slots(include_restraints)
. = ..()
if(wear_suit)
- . += wear_suit._inv_return_attached()
+ . += wear_suit.inv_slot_attached()
if(w_uniform)
- . += w_uniform._inv_return_attached()
+ . += w_uniform.inv_slot_attached()
if(shoes)
- . += shoes._inv_return_attached()
+ . += shoes.inv_slot_attached()
if(belt)
- . += belt._inv_return_attached()
+ . += belt.inv_slot_attached()
if(gloves)
- . += gloves._inv_return_attached()
+ . += gloves.inv_slot_attached()
if(glasses)
- . += glasses._inv_return_attached()
+ . += glasses.inv_slot_attached()
if(head)
- . += head._inv_return_attached()
+ . += head.inv_slot_attached()
if(l_ear)
- . += l_ear._inv_return_attached()
+ . += l_ear.inv_slot_attached()
if(r_ear)
- . += r_ear._inv_return_attached()
+ . += r_ear.inv_slot_attached()
if(wear_id)
- . += wear_id._inv_return_attached()
+ . += wear_id.inv_slot_attached()
if(r_store)
- . += r_store._inv_return_attached()
+ . += r_store.inv_slot_attached()
if(l_store)
- . += l_store._inv_return_attached()
+ . += l_store.inv_slot_attached()
if(s_store)
- . += s_store._inv_return_attached()
+ . += s_store.inv_slot_attached()
/mob/living/carbon/human/_get_inventory_slot_ids()
return ..() + list(
@@ -219,8 +219,7 @@
var/self_equip = user == src
- // first, check species
- if(species?.hud?.equip_slots && !(slot in species.hud.equip_slots))
+ if(!semantically_has_slot(slot))
if(!(flags & INV_OP_SUPPRESS_WARNING))
to_chat(user, SPAN_WARNING("[self_equip? "You" : "They"] have nowhere to put that!"))
return FALSE
@@ -266,7 +265,10 @@
. = ..()
if(!.)
return
+ switch(id)
+ if(SLOT_ID_HANDCUFFED)
+ return has_hands()
var/datum/inventory_slot/slot_meta = resolve_inventory_slot(id)
if(!slot_meta)
return FALSE
- return !(slot_meta.inventory_slot_flags & INV_SLOT_IS_INVENTORY) || !species || (id in species.hud.gear)
+ return !(slot_meta.inventory_slot_flags & INV_SLOT_IS_INVENTORY) || !species || (id in species.inventory_slots)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index ed3e49e5a8f4..cfd9aec30caa 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -1047,10 +1047,10 @@
if(check_belly(I))
continue
if(src.species && !(src.species.species_flags & CONTAMINATION_IMMUNE))
- // This is hacky, I'm so sorry.
- if(I != l_hand && I != r_hand) //If the item isn't in your hands, you're probably wearing it. Full damage for you.
+ if(isnull(I.held_index))
+ //If the item isn't in your hands, you're probably wearing it. Full damage for you.
total_phoronloss += loss_per_part
- else if((I == l_hand | I == r_hand) && !((src.wear_suit?.body_cover_flags & HANDS) | src.gloves | (src.w_uniform?.body_cover_flags & HANDS))) //If the item is in your hands, but you're wearing protection, you might be alright.
+ else if(!((src.wear_suit.body_cover_flags & HANDS) | src.gloves | (src.w_uniform.body_cover_flags & HANDS))) //If the item is in your hands, but you're wearing protection, you might be alright.
//If you hold it in hand, and your hands arent covered by anything
total_phoronloss += loss_per_part
if(total_phoronloss)
diff --git a/code/modules/mob/living/carbon/human/movement.dm b/code/modules/mob/living/carbon/human/movement.dm
index 51ebe5d718cf..4150e5a903dc 100644
--- a/code/modules/mob/living/carbon/human/movement.dm
+++ b/code/modules/mob/living/carbon/human/movement.dm
@@ -168,28 +168,6 @@
return ..()
-/mob/living/carbon/human/Process_Spaceslipping(var/prob_slip = 5)
- //If knocked out we might just hit it and stop. This makes it possible to get dead bodies and such.
-
- if(species.species_flags & NO_SLIP)
- return
-
- if(stat)
- prob_slip = 0 // Changing this to zero to make it line up with the comment, and also, make more sense.
-
- //Do we have magboots or such on if so no slip
- if(istype(shoes, /obj/item/clothing/shoes/magboots) && (shoes.clothing_flags & NOSLIP))
- prob_slip = 0
-
- //Check hands and mod slip
- if(!l_hand) prob_slip -= 2
- else if(l_hand.w_class <= 2) prob_slip -= 1
- if (!r_hand) prob_slip -= 2
- else if(r_hand.w_class <= 2) prob_slip -= 1
-
- prob_slip = round(prob_slip)
- return(prob_slip)
-
// Handle footstep sounds
/mob/living/carbon/human/handle_footstep(turf/T)
if(is_incorporeal())
diff --git a/code/modules/mob/living/carbon/human/rendering.dm b/code/modules/mob/living/carbon/human/rendering.dm
index 1e601df9b654..0fb48af7a4bd 100644
--- a/code/modules/mob/living/carbon/human/rendering.dm
+++ b/code/modules/mob/living/carbon/human/rendering.dm
@@ -838,25 +838,17 @@
inventory.update_slot_render(SLOT_ID_BACK)
/mob/living/carbon/human/update_inv_handcuffed()
+ inventory.on_handcuffed_update()
inventory.update_slot_render(SLOT_ID_HANDCUFFED)
/mob/living/carbon/human/update_inv_legcuffed()
inventory.update_slot_render(SLOT_ID_LEGCUFFED)
-/mob/living/carbon/human/update_inv_r_hand()
- if(isnull(r_hand))
- remove_standing_overlay(HUMAN_OVERLAY_RHAND)
+/mob/living/carbon/human/update_inv_hand(index)
+ if(isnull(inventory.held_items[index]))
+ remove_standing_overlay(HUMAN_OVERLAY_HAND(index))
return
set_standing_overlay(
- HUMAN_OVERLAY_RHAND,
- r_hand.render_mob_appearance(src, 2, BODYTYPE_DEFAULT),
- )
-
-/mob/living/carbon/human/update_inv_l_hand()
- if(isnull(l_hand))
- remove_standing_overlay(HUMAN_OVERLAY_LHAND)
- return
- set_standing_overlay(
- HUMAN_OVERLAY_LHAND,
- l_hand.render_mob_appearance(src, 1, BODYTYPE_DEFAULT),
+ HUMAN_OVERLAY_HAND(index),
+ inventory.held_items[index].render_mob_appearance(src, index, BODYTYPE_DEFAULT),
)
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index 6a4525847f75..567cf7693da1 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -175,9 +175,10 @@
if(r_ear && istype(r_ear,/obj/item/radio))
R = r_ear
has_radio = 1
- if(r_hand && istype(r_hand, /obj/item/radio))
- R = r_hand
- has_radio = 1
+ for(var/obj/item/radio/potential in get_right_held_items())
+ R = potential
+ has_radio = TRUE
+ break
if(has_radio)
R.talk_into(src,message,null,verb,speaking)
used_radios += R
@@ -187,9 +188,10 @@
if(l_ear && istype(l_ear,/obj/item/radio))
R = l_ear
has_radio = 1
- if(l_hand && istype(l_hand,/obj/item/radio))
- R = l_hand
- has_radio = 1
+ for(var/obj/item/radio/potential in get_left_held_items())
+ R = potential
+ has_radio = TRUE
+ break
if(has_radio)
R.talk_into(src,message,null,verb,speaking)
used_radios += R
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 7f5ab6620904..be9af271b290 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -29,23 +29,6 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
/mob/living/carbon/human/update_icons_huds()
stack_trace("CANARY: Old human update_icons_huds was called.")
-//TODO: Carbon procs in my human update_icons??
-/mob/living/carbon/human/update_hud() //TODO: do away with this if possible
- // todo: this is utterly shitcode and fucking stupid ~silicons
- // todo: the rest of hud code here ain't much better LOL
- var/list/obj/item/relevant = get_equipped_items(TRUE, TRUE)
- if(hud_used)
- for(var/obj/item/I as anything in relevant)
- position_hud_item(I, slot_id_by_item(I))
- if(client)
- client.screen |= relevant
-
-//update whether handcuffs appears on our hud.
-/mob/living/carbon/proc/update_hud_handcuffed()
- if(hud_used && hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
- hud_used.l_hand_hud_object.update_icon()
- hud_used.r_hand_hud_object.update_icon()
-
/mob/living/carbon/human/update_transform()
var/matrix/old_matrix = transform
var/matrix/M = matrix()
@@ -110,8 +93,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
update_inv_belt()
update_inv_back()
update_inv_wear_suit()
- update_inv_r_hand()
- update_inv_l_hand()
+ update_inv_hands()
update_handcuffed()
update_inv_legcuffed()
//update_inv_pockets() //Doesn't do anything
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index b75175e9c479..8256d60a3e13 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -1,6 +1,8 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
+//* Abstraction *//
+
/mob/living/carbon/_slot_by_item(obj/item/I)
if(handcuffed == I)
return SLOT_ID_HANDCUFFED
@@ -38,9 +40,9 @@
. = ..()
if(include_restraints)
if(handcuffed)
- . += handcuffed._inv_return_attached()
+ . += handcuffed.inv_slot_attached()
if(legcuffed)
- . += legcuffed._inv_return_attached()
+ . += legcuffed.inv_slot_attached()
/mob/living/carbon/_get_inventory_slot_ids()
return ..() + list(
diff --git a/code/modules/mob/living/inventory.dm b/code/modules/mob/living/inventory.dm
deleted file mode 100644
index c9db1ad494af..000000000000
--- a/code/modules/mob/living/inventory.dm
+++ /dev/null
@@ -1,299 +0,0 @@
-/mob/living/init_inventory()
- inventory = new(src)
-
-/mob/living/get_active_held_item()
- RETURN_TYPE(/obj/item)
- return hand? l_hand : r_hand
-
-/mob/living/get_inactive_held_item()
- RETURN_TYPE(/obj/item)
- return hand? r_hand : l_hand
-
-/mob/living/get_left_held_item()
- RETURN_TYPE(/obj/item)
- return l_hand
-
-/mob/living/get_right_held_item()
- RETURN_TYPE(/obj/item)
- return r_hand
-
-/mob/living/get_held_index(obj/item/I)
- if(l_hand == I)
- return 1
- else if(r_hand == I)
- return 2
-
-/mob/living/get_held_items()
- RETURN_TYPE(/list)
- . = list()
- if(l_hand)
- . += l_hand
- if(r_hand)
- . += r_hand
-
-/mob/living/hands_full()
- return l_hand && r_hand
-
-/mob/living/put_in_active_hand(obj/item/I, flags)
- return hand? put_in_left_hand(I, flags) : put_in_right_hand(I, flags)
-
-/mob/living/put_in_inactive_hand(obj/item/I, flags)
- return hand? put_in_right_hand(I, flags) : put_in_left_hand(I, flags)
-
-/mob/living/get_held_item_of_index(index)
- RETURN_TYPE(/obj/item)
- switch(index)
- if(1)
- return l_hand
- if(2)
- return r_hand
-
-/mob/living/get_number_of_hands()
- return 2
-
-/mob/living/put_in_left_hand(obj/item/I, flags)
- if(!I)
- return TRUE
- if(!has_hands)
- return FALSE
- if(l_hand)
- if(flags & INV_OP_FORCE)
- drop_item_to_ground(l_hand, flags)
- if(l_hand) // incase drop item fails which is potentially possible
- return FALSE
- if(!_common_handle_put_in_hand(I, flags))
- return FALSE
- l_hand = I
- log_inventory("[key_name(src)] put [I] in hand 1")
- l_hand.update_twohanding()
- l_hand.update_held_icon()
- // ! WARNING: snowflake - at time of equipped, vars aren't set yet.
- position_hud_item(l_hand, SLOT_ID_HANDS)
- update_inv_l_hand()
- return TRUE
-
-/mob/living/put_in_right_hand(obj/item/I, flags)
- if(!I)
- return TRUE
- if(!has_hands)
- return FALSE
- if(r_hand)
- if(flags & INV_OP_FORCE)
- drop_item_to_ground(r_hand, flags)
- if(r_hand) // incase drop item fails which is potentially possible
- return FALSE
- if(!_common_handle_put_in_hand(I, flags))
- return FALSE
- r_hand = I
- log_inventory("[key_name(src)] put [I] in hand 1")
- r_hand.update_twohanding()
- r_hand.update_held_icon()
- // ! WARNING: snowflake - at time of equipped, vars aren't set yet.
- position_hud_item(r_hand, SLOT_ID_HANDS)
- update_inv_r_hand()
- return TRUE
-
-/mob/living/proc/_common_handle_put_in_hand(obj/item/I, flags)
- // let's not do that if it's deleted!
- if(I && QDELETED(I))
- to_chat(src, SPAN_DANGER("A deleted item [I] ([REF(I)]) was sent into inventory hand procs with flags [flags]. Report this line to coders immediately."))
- to_chat(src, SPAN_DANGER("The inventory system will attempt to reject the bad equip. Glitches may occur."))
- return FALSE
- if(!(I.interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ON_TOUCH))
- I.add_fingerprint(src)
- else
- I.add_hiddenprint(src)
- var/existing_slot = is_in_inventory(I)
- if(existing_slot)
- // handle item reequip can fail.
- return _handle_item_reequip(I, SLOT_ID_HANDS, existing_slot, flags)
- // newly equipped
- var/atom/oldLoc = I.loc
- if(I.loc != src)
- I.forceMove(src)
- if(I.loc != src)
- return FALSE
- I.pickup(src, flags, oldLoc)
- I.equipped(src, SLOT_ID_HANDS, flags)
- return TRUE
-
-/mob/living/put_in_hand(obj/item/I, index, flags)
- // TODO: WHEN MULTIHAND IS DONE, BESURE TO MAKE THIS HAVE THE LOGIC I PUT INI PUT IN L/R HANDS!!
- switch(index)
- if(1)
- return put_in_left_hand(I, flags)
- if(2)
- return put_in_right_hand(I, flags)
-
-/mob/living/_unequip_held(obj/item/I, flags)
- if(l_hand == I)
- l_hand = null
- else if(r_hand == I)
- r_hand = null
- if(!(flags & INV_OP_NO_UPDATE_ICONS))
- update_inv_hands()
-
-/mob/living/_slot_by_item(obj/item/I)
- if(back == I)
- return SLOT_ID_BACK
- else if(wear_mask == I)
- return SLOT_ID_MASK
- return ..()
-
-/mob/living/_item_by_slot(slot)
- switch(slot)
- if(SLOT_ID_MASK)
- return wear_mask
- if(SLOT_ID_BACK)
- return back
- else
- return ..()
-
-/mob/living/_set_inv_slot(slot, obj/item/I, flags)
- switch(slot)
- if(SLOT_ID_BACK)
- back = I
- if(!(flags & INV_OP_NO_UPDATE_ICONS))
- update_inv_back()
- if(SLOT_ID_MASK)
- wear_mask = I
- if(!(flags & INV_OP_NO_UPDATE_ICONS))
- update_inv_wear_mask()
- // todo: only rebuild when needed for BLOCKHAIR|BLOCKHEADHAIR
- update_hair(0)
- update_inv_ears()
- if(!(flags & INV_OP_NO_LOGIC))
- if(!wear_mask)
- // todo: why are internals code shit
- if(internal)
- internal = null
- if(internals)
- internals.icon_state = "internal0"
- else
- return ..()
-
-/mob/living/_get_all_slots(include_restraints)
- . = ..()
- if(back)
- . += back._inv_return_attached()
- if(wear_mask)
- . += wear_mask._inv_return_attached()
-
-/mob/living/_get_inventory_slot_ids()
- return ..() + list(
- SLOT_ID_BACK,
- SLOT_ID_MASK
- )
-
-/mob/living/abiotic(full_body)
- if(full_body)
- if(item_considered_abiotic(wear_mask))
- return TRUE
- if(item_considered_abiotic(back))
- return TRUE
-
- for(var/obj/item/I as anything in get_held_items())
- if(item_considered_abiotic(I))
- return TRUE
-
- return FALSE
-
-/mob/living/get_number_of_hands()
- return has_hands? 2 : 0
-
-/mob/living/has_hands()
- return has_hands
-
-/mob/living/has_free_hand()
- return !l_hand || !r_hand
-
-//* carry weight
-
-// don't call this you shouldn't need to
-/mob/living/update_carry_slowdown()
- recalculate_carry()
-
-/mob/living/proc/recalculate_carry(update = TRUE)
- var/tally_weight = 0
- var/tally_encumbrance = 0
- var/flat_encumbrance = 0
- for(var/obj/item/I as anything in get_equipped_items())
- tally_weight += (I.weight_registered = I.get_weight())
- if(I.is_held())
- if(!(I.item_flags & ITEM_ENCUMBERS_WHILE_HELD))
- I.encumbrance_registered = null
- continue
- else
- if(I.item_flags & ITEM_ENCUMBERS_ONLY_HELD)
- I.encumbrance_registered = null
- continue
- var/encumbrance = I.get_encumbrance()
- tally_encumbrance += encumbrance
- I.encumbrance_registered = encumbrance
- flat_encumbrance = max(flat_encumbrance, I.get_flat_encumbrance())
- cached_carry_weight = tally_weight
- cached_carry_encumbrance = tally_encumbrance
- cached_carry_flat_encumbrance = flat_encumbrance
- if(update)
- update_carry()
-
-/mob/living/proc/adjust_current_carry_weight(amount)
- if(!amount)
- return
- cached_carry_weight += amount
- update_carry()
-
-/mob/living/proc/adjust_current_carry_encumbrance(amount)
- if(!amount)
- return
- cached_carry_encumbrance += amount
- update_carry()
-
-/**
- * @return penalty as speed multiplier from 0 to 1
- */
-/mob/living/proc/carry_weight_to_penalty(amount)
- return 1
-
-/**
- * @return penalty as speed multiplier from 0 to 1
- */
-/mob/living/proc/carry_encumbrance_to_penalty(amount)
- return 1
-
-/mob/living/proc/update_carry()
- var/weight_penalty = carry_weight_to_penalty(cached_carry_weight)
- var/encumbrance_penalty = carry_encumbrance_to_penalty(cached_carry_encumbrance)
- var/flat_encumbrance_penalty = carry_encumbrance_to_penalty(cached_carry_flat_encumbrance)
- var/penalty = min(weight_penalty, encumbrance_penalty, flat_encumbrance_penalty)
- switch(round(min(weight_penalty, encumbrance_penalty) * 100))
- if(85 to 99)
- throw_alert("encumbered", /atom/movable/screen/alert/encumbered/minor)
- if(65 to 84)
- throw_alert("encumbered", /atom/movable/screen/alert/encumbered/moderate)
- if(36 to 64)
- throw_alert("encumbered", /atom/movable/screen/alert/encumbered/severe)
- if(0 to 35)
- throw_alert("encumbered", /atom/movable/screen/alert/encumbered/extreme)
- else
- clear_alert("encumbered")
- /// do not slow down below 10% of base
- penalty = max(penalty, 0.1)
- if(penalty)
- add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_inventory_carry, params = list(MOVESPEED_PARAM_MULTIPLY_SPEED = penalty))
- else
- remove_movespeed_modifier(/datum/movespeed_modifier/mob_inventory_carry)
-
-//* hard movespeed slowdown
-
-/mob/living/update_item_slowdown()
- var/tally = get_item_slowdown()
- if(tally)
- add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_item_slowdown, params = list(MOVESPEED_PARAM_DELAY_MOD = tally))
- else
- remove_movespeed_modifier(/datum/movespeed_modifier/mob_item_slowdown)
-
-/mob/living/proc/get_item_slowdown()
- . = 0
- for(var/obj/item/I as anything in get_equipped_items())
- . += I.slowdown
diff --git a/code/modules/mob/living/inventory_legacy.dm b/code/modules/mob/living/inventory_legacy.dm
new file mode 100644
index 000000000000..8e0ed6f2a08c
--- /dev/null
+++ b/code/modules/mob/living/inventory_legacy.dm
@@ -0,0 +1,12 @@
+/mob/living/abiotic(full_body)
+ if(full_body)
+ if(item_considered_abiotic(wear_mask))
+ return TRUE
+ if(item_considered_abiotic(back))
+ return TRUE
+
+ for(var/obj/item/I as anything in get_held_items())
+ if(item_considered_abiotic(I))
+ return TRUE
+
+ return FALSE
diff --git a/code/modules/mob/living/living-inventory.dm b/code/modules/mob/living/living-inventory.dm
new file mode 100644
index 000000000000..24431f48340e
--- /dev/null
+++ b/code/modules/mob/living/living-inventory.dm
@@ -0,0 +1,154 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Abstraction *//
+
+/mob/living/_slot_by_item(obj/item/I)
+ if(back == I)
+ return SLOT_ID_BACK
+ else if(wear_mask == I)
+ return SLOT_ID_MASK
+ return ..()
+
+/mob/living/_item_by_slot(slot)
+ switch(slot)
+ if(SLOT_ID_MASK)
+ return wear_mask
+ if(SLOT_ID_BACK)
+ return back
+ else
+ return ..()
+
+/mob/living/_set_inv_slot(slot, obj/item/I, flags)
+ switch(slot)
+ if(SLOT_ID_BACK)
+ back = I
+ if(!(flags & INV_OP_NO_UPDATE_ICONS))
+ update_inv_back()
+ if(SLOT_ID_MASK)
+ wear_mask = I
+ if(!(flags & INV_OP_NO_UPDATE_ICONS))
+ update_inv_wear_mask()
+ // todo: only rebuild when needed for BLOCKHAIR|BLOCKHEADHAIR
+ update_hair(0)
+ update_inv_ears()
+ if(!(flags & INV_OP_NO_LOGIC))
+ if(!wear_mask)
+ // todo: why are internals code shit
+ if(internal)
+ internal = null
+ if(internals)
+ internals.icon_state = "internal0"
+ else
+ return ..()
+
+/mob/living/_get_all_slots(include_restraints)
+ . = ..()
+ if(back)
+ . += back.inv_slot_attached()
+ if(wear_mask)
+ . += wear_mask.inv_slot_attached()
+
+/mob/living/_get_inventory_slot_ids()
+ return ..() + list(
+ SLOT_ID_BACK,
+ SLOT_ID_MASK
+ )
+
+//* Init *//
+
+/mob/living/init_inventory()
+ if(inventory)
+ return
+ inventory = new(src)
+
+//* Carry Weight *//
+
+// don't call this you shouldn't need to
+/mob/living/update_carry_slowdown()
+ recalculate_carry()
+
+/mob/living/proc/recalculate_carry(update = TRUE)
+ var/tally_weight = 0
+ var/tally_encumbrance = 0
+ var/flat_encumbrance = 0
+ for(var/obj/item/I as anything in get_equipped_items())
+ tally_weight += (I.weight_registered = I.get_weight())
+ if(I.is_held())
+ if(!(I.item_flags & ITEM_ENCUMBERS_WHILE_HELD))
+ I.encumbrance_registered = null
+ continue
+ else
+ if(I.item_flags & ITEM_ENCUMBERS_ONLY_HELD)
+ I.encumbrance_registered = null
+ continue
+ var/encumbrance = I.get_encumbrance()
+ tally_encumbrance += encumbrance
+ I.encumbrance_registered = encumbrance
+ flat_encumbrance = max(flat_encumbrance, I.get_flat_encumbrance())
+ cached_carry_weight = tally_weight
+ cached_carry_encumbrance = tally_encumbrance
+ cached_carry_flat_encumbrance = flat_encumbrance
+ if(update)
+ update_carry()
+
+/mob/living/proc/adjust_current_carry_weight(amount)
+ if(!amount)
+ return
+ cached_carry_weight += amount
+ update_carry()
+
+/mob/living/proc/adjust_current_carry_encumbrance(amount)
+ if(!amount)
+ return
+ cached_carry_encumbrance += amount
+ update_carry()
+
+/**
+ * @return penalty as speed multiplier from 0 to 1
+ */
+/mob/living/proc/carry_weight_to_penalty(amount)
+ return 1
+
+/**
+ * @return penalty as speed multiplier from 0 to 1
+ */
+/mob/living/proc/carry_encumbrance_to_penalty(amount)
+ return 1
+
+/mob/living/proc/update_carry()
+ var/weight_penalty = carry_weight_to_penalty(cached_carry_weight)
+ var/encumbrance_penalty = carry_encumbrance_to_penalty(cached_carry_encumbrance)
+ var/flat_encumbrance_penalty = carry_encumbrance_to_penalty(cached_carry_flat_encumbrance)
+ var/penalty = min(weight_penalty, encumbrance_penalty, flat_encumbrance_penalty)
+ switch(round(min(weight_penalty, encumbrance_penalty) * 100))
+ if(85 to 99)
+ throw_alert("encumbered", /atom/movable/screen/alert/encumbered/minor)
+ if(65 to 84)
+ throw_alert("encumbered", /atom/movable/screen/alert/encumbered/moderate)
+ if(36 to 64)
+ throw_alert("encumbered", /atom/movable/screen/alert/encumbered/severe)
+ if(0 to 35)
+ throw_alert("encumbered", /atom/movable/screen/alert/encumbered/extreme)
+ else
+ clear_alert("encumbered")
+ /// do not slow down below 10% of base
+ penalty = max(penalty, 0.1)
+ if(penalty)
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_inventory_carry, params = list(MOVESPEED_PARAM_MULTIPLY_SPEED = penalty))
+ else
+ remove_movespeed_modifier(/datum/movespeed_modifier/mob_inventory_carry)
+
+//* Movespeed *//
+
+/mob/living/update_item_slowdown()
+ var/tally = get_item_slowdown()
+ if(tally)
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_item_slowdown, params = list(MOVESPEED_PARAM_DELAY_MOD = tally))
+ else
+ remove_movespeed_modifier(/datum/movespeed_modifier/mob_item_slowdown)
+
+/mob/living/proc/get_item_slowdown()
+ . = 0
+ for(var/obj/item/I as anything in get_equipped_items())
+ . += I.slowdown
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 18322f70ef42..b01ba32341a6 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -636,49 +636,6 @@ default behaviour is:
else // No colors, so remove the client's color.
animate(client, color = null, time = 10)
-/mob/living/swap_hand()
- src.hand = !( src.hand )
- if(hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
- if(hand) //This being 1 means the left hand is in use
- hud_used.l_hand_hud_object.icon_state = "l_hand_active"
- hud_used.r_hand_hud_object.icon_state = "r_hand_inactive"
- else
- hud_used.l_hand_hud_object.icon_state = "l_hand_inactive"
- hud_used.r_hand_hud_object.icon_state = "r_hand_active"
-
- // We just swapped hands, so the thing in our inactive hand will notice it's not the focus
- var/obj/item/I = get_inactive_held_item()
- if(I)
- if(I.zoom)
- I.zoom()
- return
-
-/mob/proc/activate_hand(selhand)
-
-/mob/living/activate_hand(selhand) //0 or "r" or "right" for right hand; 1 or "l" or "left" for left hand.
-
- if(istext(selhand))
- selhand = lowertext(selhand)
-
- if(selhand == "right" || selhand == "r")
- selhand = 0
- if(selhand == "left" || selhand == "l")
- selhand = 1
-
- if(selhand != src.hand)
- swap_hand()
-
-// todo: multihands
-
-/mob/proc/activate_hand_of_index(index)
-
-/mob/living/activate_hand_of_index(index)
- switch(index)
- if(1)
- activate_hand("l")
- if(2)
- activate_hand("r")
-
/mob/living/get_sound_env(var/pressure_factor)
if (hallucination)
return PSYCHOTIC
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 9312e6a33a18..93115e811f1d 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -122,17 +122,10 @@
var/custom_whisper = null
//? inventory
- var/hand = null
- var/obj/item/l_hand = null
- var/obj/item/r_hand = null
var/obj/item/back = null//Human/Monkey
var/obj/item/tank/internal = null//Human/Monkey
var/obj/item/clothing/mask/wear_mask = null//Carbon
- // TODO: /tg/ arbitrary hand numbers
- /// Set to TRUE to enable the use of hands and the hands hud
- var/has_hands = FALSE
-
//* Carry Weight
// todo: put all this on /datum/inventory after hand refactor
/// cached carry weight of all items
diff --git a/code/modules/mob/living/mobility.dm b/code/modules/mob/living/mobility.dm
index 64edf2557f27..4a5b7abc5d8d 100644
--- a/code/modules/mob/living/mobility.dm
+++ b/code/modules/mob/living/mobility.dm
@@ -17,7 +17,7 @@
. = ..()
if(!(mobility_flags & MOBILITY_CAN_HOLD))
- drop_all_held_items()
+ drop_held_items()
if(!(mobility_flags & MOBILITY_CAN_PULL))
stop_pulling()
if(!(mobility_flags & MOBILITY_CAN_STAND))
diff --git a/code/modules/mob/living/silicon/pai/mobility.dm b/code/modules/mob/living/silicon/pai/mobility.dm
index dd233dcf509c..42350a53da04 100644
--- a/code/modules/mob/living/silicon/pai/mobility.dm
+++ b/code/modules/mob/living/silicon/pai/mobility.dm
@@ -109,8 +109,7 @@
return
H.icon_state = "[chassis]"
- grabber.update_inv_l_hand()
- grabber.update_inv_r_hand()
+ H.update_worn_icon()
return H
/// Handle movement speed
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 19c7654659b2..e2defee3997a 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -466,7 +466,7 @@
A.update_buttons()
/mob/living/silicon/pai/proc/update_chassis_actions()
- for(var/datum/action/pai/A in actions)
+ for(var/datum/action/pai/A in actions_to_grant)
if(A.update_on_chassis_change)
A.update_buttons()
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm
index b97100929b52..9b90ec4918ee 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm
@@ -25,7 +25,7 @@
var/mob/living/carbon/human/H = over_object
if(!istype(H) || !Adjacent(H))
return ..()
- if(H.a_intent == "grab" && hat && !(H.l_hand && H.r_hand))
+ if(H.a_intent == "grab" && hat && !H.are_usable_hands_full())
hat.loc = get_turf(src)
H.put_in_hands(hat)
H.visible_message(SPAN_DANGER("\The [H] removes \the [src]'s [hat]."))
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index 3e012dabb128..f08ddfd06dc4 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -270,7 +270,7 @@
if(module_state_3)
. += module_state_3
-/mob/living/silicon/robot/get_number_of_hands()
+/mob/living/silicon/robot/get_nominal_hand_count()
return 3
/mob/living/silicon/robot/get_held_index(obj/item/I)
@@ -281,7 +281,7 @@
if(module_state_3 == I)
return 3
-/mob/living/silicon/robot/get_held_item_of_index(index)
+/mob/living/silicon/robot/get_held_index(index)
switch(index)
if(1)
return module_state_1
diff --git a/code/modules/mob/living/simple_mob/appearance.dm b/code/modules/mob/living/simple_mob/appearance.dm
index c14f12c7b83a..b33ac8e588e7 100644
--- a/code/modules/mob/living/simple_mob/appearance.dm
+++ b/code/modules/mob/living/simple_mob/appearance.dm
@@ -26,12 +26,6 @@
else
icon_state = initial(icon_state)
- if(has_hands)
- if(r_hand_sprite)
- add_overlay(r_hand_sprite)
- if(l_hand_sprite)
- add_overlay(l_hand_sprite)
-
if(has_eye_glow)
if(icon_state != icon_living)
remove_eyes()
diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm
index 3ea0519d6c51..b6d9482e119d 100644
--- a/code/modules/mob/living/simple_mob/defense.dm
+++ b/code/modules/mob/living/simple_mob/defense.dm
@@ -30,7 +30,6 @@
L.put_in_active_hand(G)
- G.synch()
G.affecting = src
LAssailant = L
diff --git a/code/modules/mob/living/simple_mob/hands.dm b/code/modules/mob/living/simple_mob/hands.dm
index 82d80f9989e2..908ec9452873 100644
--- a/code/modules/mob/living/simple_mob/hands.dm
+++ b/code/modules/mob/living/simple_mob/hands.dm
@@ -1,46 +1,10 @@
-// Hand procs for player-controlled SA's
-/mob/living/simple_mob/swap_hand()
- src.hand = !( src.hand )
- if(hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
- if(hand) //This being 1 means the left hand is in use
- hud_used.l_hand_hud_object.icon_state = "l_hand_active"
- hud_used.r_hand_hud_object.icon_state = "r_hand_inactive"
- else
- hud_used.l_hand_hud_object.icon_state = "l_hand_inactive"
- hud_used.r_hand_hud_object.icon_state = "r_hand_active"
- return
-
-/mob/living/simple_mob/update_inv_r_hand()
- if(QDESTROYING(src))
- return
-
- if(r_hand)
- r_hand.screen_loc = ui_rhand //TODO
- r_hand_sprite = r_hand.render_mob_appearance(src, 2, BODYTYPE_DEFAULT)
- else
- r_hand_sprite = null
-
- update_icon()
-
-/mob/living/simple_mob/update_inv_l_hand()
- if(QDESTROYING(src))
- return
-
- if(l_hand)
- l_hand.screen_loc = ui_lhand //TODO
- l_hand_sprite = r_hand.render_mob_appearance(src, 1, BODYTYPE_DEFAULT)
- else
- l_hand_sprite = null
-
- update_icon()
-
//Can insert extra huds into the hud holder here.
/mob/living/simple_mob/proc/extra_huds(var/datum/hud/hud,var/icon/ui_style,var/list/hud_elements)
return
//If they can or cannot use tools/machines/etc
/mob/living/simple_mob/IsAdvancedToolUser()
- return has_hands
+ return hand_count
/mob/living/simple_mob/proc/IsHumanoidToolUser(var/atom/tool)
if(!humanoid_hands)
diff --git a/code/modules/mob/living/simple_mob/on_click.dm b/code/modules/mob/living/simple_mob/on_click.dm
index 6bafe9f6573c..3d56890f0e29 100644
--- a/code/modules/mob/living/simple_mob/on_click.dm
+++ b/code/modules/mob/living/simple_mob/on_click.dm
@@ -7,7 +7,7 @@
// setClickCooldown(get_attack_speed())
- if(has_hands && istype(A,/obj) && a_intent != INTENT_HARM)
+ if(has_hands() && istype(A,/obj) && a_intent != INTENT_HARM)
var/obj/O = A
return O.attack_hand(src)
@@ -27,13 +27,13 @@
attack_target(A)
if(INTENT_GRAB)
- if(has_hands)
+ if(has_hands())
A.attack_hand(src)
else
attack_target(A)
if(INTENT_DISARM)
- if(has_hands)
+ if(has_hands())
A.attack_hand(src)
else
attack_target(A)
diff --git a/code/modules/mob/living/simple_mob/simple_hud.dm b/code/modules/mob/living/simple_mob/simple_hud.dm
index c17c0c2bd2c1..d9b44d4317e6 100644
--- a/code/modules/mob/living/simple_mob/simple_hud.dm
+++ b/code/modules/mob/living/simple_mob/simple_hud.dm
@@ -12,8 +12,6 @@
var/list/adding = list()
var/list/other = list()
var/list/hotkeybuttons = list()
- var/list/slot_info = list()
- var/list/hand_info = list()
hud.adding = adding
hud.other = other
@@ -21,42 +19,6 @@
var/list/hud_elements = list()
var/atom/movable/screen/using
- var/atom/movable/screen/inventory/slot/inv_box
-
- var/has_hidden_gear
- if(LAZYLEN(hud_gears))
- for(var/gear_slot in hud_gears)
- inv_box = new /atom/movable/screen/inventory()
- inv_box.icon = ui_style
- inv_box.color = ui_color
- inv_box.alpha = ui_alpha
-
- var/list/slot_data = hud_gears[gear_slot]
- inv_box.name = gear_slot
- inv_box.screen_loc = slot_data["loc"]
- inv_box.slot_id = slot_data["slot"]
- inv_box.icon_state = slot_data["state"]
- slot_info["[inv_box.slot_id]"] = inv_box.screen_loc
-
- if(slot_data["dir"])
- inv_box.setDir(slot_data["dir"])
-
- if(slot_data["toggle"])
- other += inv_box
- has_hidden_gear = 1
- else
- adding += inv_box
-
- if(has_hidden_gear)
- using = new /atom/movable/screen()
- using.name = "toggle"
- using.icon = ui_style
- using.icon_state = "other"
- using.screen_loc = ui_inventory
- using.hud_layerise()
- using.color = ui_color
- using.alpha = ui_alpha
- adding += using
//Intent Backdrop
using = new /atom/movable/screen()
@@ -219,7 +181,7 @@
hud_elements |= zone_sel
//Hand things
- if(has_hands)
+ if(get_nominal_hand_count() > 0)
//Drop button
using = new /atom/movable/screen()
using.name = "drop"
@@ -230,68 +192,6 @@
using.alpha = ui_alpha
hud.hotkeybuttons += using
- //Equip detail
- using = new /atom/movable/screen()
- using.name = "equip"
- using.icon = ui_style
- using.icon_state = "act_equip"
- using.screen_loc = ui_equip
- using.color = ui_color
- using.alpha = ui_alpha
- hud.adding += using
-
- //Hand slots themselves
- var/atom/movable/screen/inventory/hand/right/right_hand = new
- right_hand.index = 2
- using = right_hand
- using.hud = src
- using.name = "r_hand"
- using.icon = ui_style
- using.icon_state = "r_hand_inactive"
- if(!hand) //This being 0 or null means the right hand is in use
- using.icon_state = "r_hand_active"
- using.screen_loc = ui_rhand
- using.color = ui_color
- using.alpha = ui_alpha
- hud.r_hand_hud_object = using
- hud.adding += using
- hand_info["2"] = using.screen_loc
-
- var/atom/movable/screen/inventory/hand/left/left_hand = new
- left_hand.index = 1
- using = left_hand
- using.hud = src
- using.name = "l_hand"
- using.icon = ui_style
- using.icon_state = "l_hand_inactive"
- if(hand) //This being 1 means the left hand is in use
- using.icon_state = "l_hand_active"
- using.screen_loc = ui_lhand
- using.color = ui_color
- using.alpha = ui_alpha
- hud.l_hand_hud_object = using
- hud.adding += using
- hand_info["1"] = using.screen_loc
-
- //Swaphand titlebar
- using = new /atom/movable/screen/inventory/swap_hands
- using.name = "hand"
- using.icon = ui_style
- using.icon_state = "hand1"
- using.screen_loc = ui_swaphand1
- using.color = ui_color
- using.alpha = ui_alpha
- hud.adding += using
-
- using = new /atom/movable/screen/inventory/swap_hands
- using.name = "hand"
- using.icon = ui_style
- using.icon_state = "hand2"
- using.screen_loc = ui_swaphand2
- using.color = ui_color
- using.alpha = ui_alpha
- hud.adding += using
-
//Throw button
throw_icon = new /atom/movable/screen()
throw_icon.icon = ui_style
diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm
index db05b31e0a25..ad4d7794dfb5 100644
--- a/code/modules/mob/living/simple_mob/simple_mob.dm
+++ b/code/modules/mob/living/simple_mob/simple_mob.dm
@@ -16,7 +16,7 @@
iff_factions = MOB_IFF_FACTION_BIND_AUTO
- //? Attacks - Basic
+ //* Attacks - Basic *//
/// melee style
var/datum/unarmed_attack/melee_style
@@ -24,6 +24,10 @@
/// our innate darksight
var/datum/vision/baseline/vision_innate = /datum/vision/baseline/default
+ //* Inventory *//
+ /// how many hands we have
+ var/hand_count = 0
+
///Tooltip description
var/tt_desc = null
@@ -38,10 +42,6 @@
var/list/hud_gears
/// Icon file path to use for the HUD, otherwise generic icons are used
var/ui_icons
- /// If they have hands, they could use some icons.
- var/r_hand_sprite
- /// If they have hands, they could use some icons.
- var/l_hand_sprite
/// Message to print to players about 'how' to play this mob on login.
var/player_msg
@@ -457,3 +457,13 @@
. = ..()
if(. && (!is_sharp(I) || !has_edge(I)))
return FALSE
+
+//* Inventory *//
+
+/mob/living/simple_mob/get_usable_hand_count()
+ return hand_count
+
+/mob/living/simple_mob/get_usable_hand_indices()
+ . = list()
+ for(var/i in 1 to hand_count)
+ . += i
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm
index f607f5267a71..af313e8f2e66 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm
@@ -28,7 +28,7 @@
maxHealth = 50
health = 50
randomized = TRUE
- has_hands = TRUE
+ hand_count = 2
humanoid_hands = TRUE
pass_flags = ATOM_PASS_TABLE
diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm
index a362c613559e..a8c87264302f 100644
--- a/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/possessed.dm
@@ -38,7 +38,7 @@
attack_sound = "punch"
armor_legacy_mob = list(melee = 30, bullet = 10, laser = 20,energy = 25, bomb = 20, bio = 100, rad = 100) //This should be the same as the base RIG.
- has_hands = 1
+ hand_count = 2
humanoid_hands = 1
grab_resist = 100
diff --git a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
index 721c948243db..79194466ade6 100644
--- a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm
@@ -40,10 +40,7 @@
// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but
// this is to prevent easy checks from the opposing force.
/mob/living/simple_mob/illusion/examine(mob/user, dist)
- if(copying)
- copying.examine(user)
- return
-
+ return copying?.examine(user) || ..() // ugh
/mob/living/simple_mob/illusion/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)
if(realistic)
diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm
index b191aaf8ba44..627872fd5353 100644
--- a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/_construct.dm
@@ -40,7 +40,7 @@
mob_class = MOB_CLASS_DEMONIC
ui_icons = 'icons/mob/screen1_construct.dmi'
- has_hands = 1
+ hand_count = 2
hand_form = "stone manipulators"
response_help = "thinks better of touching"
@@ -103,16 +103,14 @@
if(S.run_checks())
S.on_innate_cast(src)
- if(l_hand && r_hand) //Make sure our hands aren't full.
- if(istype(r_hand, /obj/item/spell)) //If they are full, perhaps we can still be useful.
- var/obj/item/spell/r_spell = r_hand
- if(r_spell.aspect == ASPECT_CHROMATIC) //Check if we can combine the new spell with one in our hands.
- r_spell.on_combine_cast(S, src)
- else if(istype(l_hand, /obj/item/spell))
- var/obj/item/spell/l_spell = l_hand
- if(l_spell.aspect == ASPECT_CHROMATIC) //Check the other hand too.
- l_spell.on_combine_cast(S, src)
- else //Welp
+ if(are_usable_hands_full())
+ var/found = FALSE
+ for(var/obj/item/spell/spell as anything in get_held_item_of_type(/obj/item/spell))
+ if(spell.aspect != ASPECT_CHROMATIC)
+ continue
+ found = TRUE
+ spell.on_combine_cast(S, src)
+ if(!found)
to_chat(src, "You require a free manipulator to use this power.")
return 0
diff --git a/code/modules/mob/mob-hands.dm b/code/modules/mob/mob-hands.dm
new file mode 100644
index 000000000000..95aa28d62df6
--- /dev/null
+++ b/code/modules/mob/mob-hands.dm
@@ -0,0 +1,194 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Hands are weird. Unlike inventory slots, they tend to be very tightly *//
+//* coupled to organ state, so it's not a good thing to have all behavior *//
+//* on /datum/inventory. *//
+//* *//
+//* To make it worse, hands are not just an inventory concept; interacting *//
+//* with the world requires hands, which requires hand code, meaning hand *//
+//* code cannot only live on /datum/inventory. *//
+//* *//
+//* Therefore, the separation of concerns is that mob-side hands code *//
+//* is only responsible for orchestrating state that concerns the health *//
+//* system like organ integration and whatnot, while /datum/inventory *//
+//* side orchestrates pickup/drop logic and all that stuff. *//
+//* *//
+//* An abstraction layer provides mob integration to the inventory datum. *//
+//* That is what is in this file, along with common mob-level handling *//
+//* for non-inventory-related uses of hands. *//
+//* *//
+//* Any purpose / notion of 'usable' hands also belongs here, because our *//
+//* inventory datum has no notion of a usable hand or an unusable one, let *//
+//* alone manipulation levels, as it only cares about the actual state *//
+//* of whether or not an object is held. *//
+
+//* Hands - Abstraction *//
+
+/**
+ * gets if we have any hands at all
+ */
+/mob/proc/has_hands()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return !!get_nominal_hand_count()
+
+/**
+ * get number of physical hands / arms / whatever that we have and should check for
+ *
+ * this is not number we can use
+ * this is the number we should use for things like rendering
+ * a hand stump is still rendered, and we should never render less than 2 hands for mobs
+ * that nominally have hands.
+ */
+/mob/proc/get_nominal_hand_count()
+ return length(inventory?.held_items)
+
+/**
+ * get number of usable hands / arms / whatever that we have and should check for
+ *
+ * this is the number we can use
+ * missing = can't use
+ * stump = can't use
+ * broken = *can* use.
+ *
+ * basically if a red deny symbol is in the hand it is not usable, otherwise it's usable.
+ */
+/mob/proc/get_usable_hand_count() as num
+ return get_nominal_hand_count()
+
+/**
+ * get indices of usable hands
+ */
+/mob/proc/get_usable_hand_indices() as /list
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/i in 1 to get_nominal_hand_count())
+ . += i
+
+/**
+ * Are usable hands all holding items?
+ *
+ * * if a hand slot is unusable but still has an item, it's ignored.
+ * * if we have no hands, this returns TRUE
+ */
+/mob/proc/are_usable_hands_full()
+ if(!length(inventory?.held_items))
+ return TRUE
+ for(var/i in get_usable_hand_indices())
+ if(isnull(inventory.held_items[i]))
+ return FALSE
+ return TRUE
+
+/**
+ * usable hands are all empty?
+ *
+ * * if a hand slot is unusable but still has an item, it's ignored.
+ * * if we have no hands, this returns TRUE
+ */
+/mob/proc/are_usable_hands_empty()
+ if(!length(inventory?.held_items))
+ return TRUE
+ for(var/i in get_usable_hand_indices())
+ if(isnull(inventory.held_items[i]))
+ continue
+ return FALSE
+ return TRUE
+
+//* Hands - Checks *//
+
+/**
+ * Gets effective manipulation level of a hand index
+ */
+/mob/proc/get_hand_manipulation_level(index)
+ return HAND_MANIPULATION_PRECISE
+
+/**
+ * Checks if a hand can be used at a given manipulation level.
+ */
+/mob/proc/is_hand_manipulation_sufficient(index, manipulation)
+ // until there's a reason to do otherwise, get_hand_manipulation_level() should be what you override!
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return get_hand_manipulation_level(index) >= manipulation
+
+/**
+ * get a list of reasons (e.g. 'broken bone', 'stunned', etc)
+ * a hand **cannot** be at a certain manipulation level.
+ */
+/mob/proc/why_hand_manipulation_insufficient(index, manipulation)
+ RETURN_TYPE(/list)
+ return list()
+
+//* Hands - Helpers *//
+
+/**
+ * Runs a standard hand usability check against a target with a given manipulation level required.
+ *
+ * @params
+ * * target - what is being interacted with
+ * * index - hand index being used
+ * * manipulation - required manipulation level
+ * * actor - (optional) interactor
+ * * silent - (optional) if set, will not emit error message
+ *
+ * @return TRUE / FALSE sucecss / fail
+ */
+/mob/proc/standard_hand_usability_check(atom/target, index, manipulation, datum/event_args/actor/actor, silent)
+ if(is_hand_manipulation_sufficient(index, manipulation))
+ return
+ if(silent)
+ return
+ var/list/reasons_we_cant = why_hand_manipulation_insufficient(index, manipulation)
+ if(actor)
+ actor.chat_feedback(
+ SPAN_WARNING("You can't do that right now! ([length(reasons_we_cant) ? english_list(reasons_we_cant) : "hand nonfunctional for unknown reason"])"),
+ target = target,
+ )
+ else
+ action_feedback(
+ SPAN_WARNING("You can't do that right now! ([length(reasons_we_cant) ? english_list(reasons_we_cant) : "hand nonfunctional for unknown reason"])"),
+ target = target,
+ )
+
+//* Hands - Identity *//
+
+/**
+ * Returns something like "left hand", "right hand", "3rd right hand", "left hand #2", etc.
+ */
+/mob/proc/get_hand_generalized_name(index)
+ var/number_on_side = round(index / 2)
+ return "[index % 2? "left" : "right"] hand[number_on_side > 1 && " #[number_on_side]"]"
+
+//* Hands - Legacy / WIP *//
+
+/**
+ * Swaps our active hand
+ *
+ * * In the future, we'll want to track active hand, attack intents, etc, by operator, instead of by mob.
+ * * This is so remote control abstraction works.
+ */
+/mob/proc/swap_hand(to_index)
+ if(active_hand == to_index)
+ return
+ var/hand_count = get_nominal_hand_count()
+ var/obj/item/was_active = get_active_held_item()
+ var/old_index = active_hand || 1
+
+ if(isnull(to_index))
+ if(active_hand >= hand_count)
+ active_hand = hand_count? 1 : null
+ else
+ ++active_hand
+ else
+ if(to_index > hand_count)
+ return FALSE
+ active_hand = to_index
+ to_index = active_hand
+
+ . = TRUE
+
+ client?.actor_huds?.inventory?.swap_active_hand(old_index, to_index)
+
+ //! LEGACY
+ if(was_active?.zoom)
+ was_active?.zoom()
+ //! End
diff --git a/code/modules/mob/mob-inventory-abstraction.dm b/code/modules/mob/mob-inventory-abstraction.dm
new file mode 100644
index 000000000000..b9f6ee115cf1
--- /dev/null
+++ b/code/modules/mob/mob-inventory-abstraction.dm
@@ -0,0 +1,248 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Abstraction procs
+ *
+ * With these, you can implement different inventory handling per mob
+ * These should usually not be called by non-mobs / items / etc.
+ *
+ * Core abstraction should never be called from outside
+ * Optional abstraction can be called with the caveat that you should be very careful in doing so.
+ */
+
+//* Core Abstraction *//
+
+/**
+ * THESE PROCS MUST BE OVERRIDDEN FOR NEW SLOTS ON MOBS
+ * yes, i managed to shove all basic behaviors that needed overriding into 5-6 procs
+ * you're
+ * welcome.
+ *
+ * These are UNSAFE PROCS.
+ *
+ * oh and can_equip_x* might need overriding for complex mobs like humans but frankly
+ * sue me, there's no better way right now.
+ */
+
+/**
+ * sets a slot to icon or null
+ *
+ * some behaviors may be included other than update icons
+ * even update icons is unpreferred but we're stuck with this for now.
+ *
+ * todo: logic should be moved out of the proc, but where?
+ *
+ * @params
+ * slot - slot to set
+ * I - item or null
+ * update_icons - update icons immediately?
+ * logic - apply logic like dropping stuff from pockets when unequippiing a jumpsuit imemdiately?
+ */
+/mob/proc/_set_inv_slot(slot, obj/item/I, flags)
+ PROTECTED_PROC(TRUE)
+ . = INVENTORY_SLOT_DOES_NOT_EXIST
+ CRASH("Attempting to set inv slot of [slot] to [I] went to base /mob. You probably had someone assigning to a nonexistant slot!")
+
+/**
+ * ""expensive"" proc that scans for the real slot of an item
+ * usually used when safety checks detect something is amiss
+ */
+/mob/proc/_slot_by_item(obj/item/I)
+ PROTECTED_PROC(TRUE)
+
+/**
+ * doubles as slot detection
+ * returns -1 if no slot
+ * YES, MAGIC VALUE BUT SOLE USER IS 20 LINES ABOVE, SUE ME.
+ */
+/mob/proc/_item_by_slot(slot)
+ PROTECTED_PROC(TRUE)
+ return INVENTORY_SLOT_DOES_NOT_EXIST
+
+/mob/proc/_get_all_slots(include_restraints)
+ PROTECTED_PROC(TRUE)
+ return list()
+
+/**
+ * return all slot ids we implement
+ */
+/mob/proc/_get_inventory_slot_ids()
+ PROTECTED_PROC(TRUE)
+ return list()
+
+/**
+ * override this if you need to make a slot not semantically exist
+ * useful for other species that don't have a slot so you don't have jumpsuit requirements apply
+ */
+/mob/proc/_semantic_slot_id_check(id)
+ PROTECTED_PROC(TRUE)
+ return TRUE
+
+//* Optional Behaviors *//
+
+/**
+ * checks for slot conflict
+ */
+/mob/proc/inventory_slot_conflict_check(obj/item/I, slot, flags)
+ var/obj/item/conflicting = _item_by_slot(slot)
+ if(conflicting)
+ if((flags & (INV_OP_CAN_DISPLACE | INV_OP_IS_FINAL_CHECK)) == (INV_OP_CAN_DISPLACE | INV_OP_IS_FINAL_CHECK))
+ drop_item_to_ground(conflicting, INV_OP_FORCE)
+ if(_item_by_slot(slot))
+ return CAN_EQUIP_SLOT_CONFLICT_HARD
+ else
+ return CAN_EQUIP_SLOT_CONFLICT_HARD
+ switch(slot)
+ if(SLOT_ID_LEFT_EAR, SLOT_ID_RIGHT_EAR)
+ if(I.slot_flags & SLOT_TWOEARS)
+ if(_item_by_slot(SLOT_ID_LEFT_EAR) || _item_by_slot(SLOT_ID_RIGHT_EAR))
+ return CAN_EQUIP_SLOT_CONFLICT_SOFT
+ else
+ var/obj/item/left_ear = _item_by_slot(SLOT_ID_LEFT_EAR)
+ var/obj/item/right_ear = _item_by_slot(SLOT_ID_RIGHT_EAR)
+ if(left_ear && left_ear != INVENTORY_SLOT_DOES_NOT_EXIST && left_ear != I && left_ear.slot_flags & SLOT_TWOEARS)
+ return CAN_EQUIP_SLOT_CONFLICT_SOFT
+ else if(right_ear && right_ear != INVENTORY_SLOT_DOES_NOT_EXIST && right_ear != I && right_ear.slot_flags & SLOT_TWOEARS)
+ return CAN_EQUIP_SLOT_CONFLICT_SOFT
+ return CAN_EQUIP_SLOT_CONFLICT_NONE
+
+/**
+ * checks if you can reach a slot
+ * return null or the first item blocking
+ */
+/mob/proc/inventory_slot_reachability_conflict(obj/item/I, slot, mob/user)
+ return null
+
+/**
+ * semantic check - should this item fit here? slot flag checks/etc should go in here.
+ *
+ * return TRUE if conflicting, otherwise FALSE
+ */
+/mob/proc/inventory_slot_semantic_conflict(obj/item/I, datum/inventory_slot/slot, mob/user)
+ . = FALSE
+ slot = resolve_inventory_slot(slot)
+ return slot._equip_check(I, src, user)
+
+/**
+ * checks if we are missing the bodypart for a slot
+ * return FALSE if we are missing, or TRUE if we're not
+ *
+ * this proc should give the feedback of what's missing!
+ */
+/mob/proc/inventory_slot_bodypart_check(obj/item/I, slot, mob/user, flags)
+ return TRUE
+
+//* Hands *//
+
+/**
+ * Hands are a bit of a special case
+ *
+ * They don't use inventory slots like the inventory system does,
+ * But still exists in the inventory module because they do a number of things,
+ * like count as equipped under [SLOT_ID_HANDS].
+ *
+ * This semi-integration lets us do a few cool things like not needing separate hooks
+ * for when someone has something in hand, but not in inventory, transitioning
+ * to them having it in inventory, but not in hands.
+ */
+
+/**
+ * the big, bad proc ultimately in charge of putting something into someone's hand
+ * whether it's from the ground, from a slot, or from another hand.
+ */
+/mob/proc/equip_hand_impl(obj/item/I, index, flags)
+ if(!I)
+ return TRUE
+ // let's not do that if it's deleted!
+ if(QDELETED(I))
+ to_chat(src, SPAN_DANGER("A deleted item [I] ([REF(I)]) was sent into inventory hand procs with flags [flags]. Report this line to coders immediately."))
+ to_chat(src, SPAN_DANGER("The inventory system will attempt to reject the bad equip. Glitches may occur."))
+ return FALSE
+
+ if(length(inventory?.held_items) < index)
+ return FALSE
+
+ var/obj/item/existing = inventory?.held_items[index]
+ if(!isnull(existing))
+ if(flags & INV_OP_FORCE)
+ drop_held_index(index, flags | INV_OP_NO_UPDATE_ICONS)
+ if(!isnull(inventory?.held_items[index]))
+ // failed to drop
+ return FALSE
+ else
+ return FALSE
+
+ var/existing_slot = is_in_inventory(I)
+ if(existing_slot == SLOT_ID_HANDS)
+ handle_item_handswap(I, index, get_held_index(I), flags)
+ else
+ if(existing_slot)
+ // already in inv
+ if(!_handle_item_reequip(I, SLOT_ID_HANDS, existing_slot, flags, src, index))
+ return FALSE
+ log_inventory("equip-to-hand: keyname [key_name(src)] index [index] item [I]([ref(I)]) from slot [existing_slot]")
+ else
+ // newly eqiupped
+ var/atom/old_loc = I.loc
+ if(I.loc != src)
+ I.forceMove(src)
+ if(I.loc != src)
+ return FALSE
+ log_inventory("pickup-to-hand: keyname [key_name(src)] index [index] item [I]([ref(I)])")
+ I.held_index = index
+ I.pickup(src, flags, old_loc)
+ I.equipped(src, SLOT_ID_HANDS, flags)
+
+ inventory.held_items[index] = I
+ inventory.on_item_entered(I, index)
+
+ //! LEGACY BEGIN
+ I.update_twohanding()
+ //! END
+
+ if(!(flags & INV_OP_NO_UPDATE_ICONS))
+ update_inv_hand(index)
+
+ if(!(I.interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ON_TOUCH))
+ I.add_fingerprint(src)
+ else
+ I.add_hiddenprint(src)
+
+ return TRUE
+
+/**
+ * get something out of our hand
+ *
+ * @return unequipped item
+ */
+/mob/proc/unequip_hand_impl(obj/item/I, index, flags)
+ ASSERT(inventory?.held_items[index] == I)
+
+ inventory.held_items[index] = null
+ inventory.on_item_exited(I, index)
+
+ I.held_index = null
+ I.unequipped(src, SLOT_ID_HANDS, flags)
+
+ if(!(flags & INV_OP_NO_UPDATE_ICONS))
+ update_inv_hand(index)
+
+ return TRUE
+
+/**
+ * handle swapping item from one hand index to another
+ */
+/mob/proc/handle_item_handswap(obj/item/I, index, old_index, flags, mob/user = src)
+ ASSERT(inventory?.held_items[old_index] == I)
+ ASSERT(isnull(inventory?.held_items[index]))
+
+ inventory.held_items[old_index] = null
+ inventory.held_items[index] = I
+ I.held_index = index
+ inventory.on_item_swapped(I, old_index, index)
+
+
+ if(!(flags & INV_OP_NO_UPDATE_ICONS))
+ update_inv_hand(old_index)
+ update_inv_hand(index)
diff --git a/code/modules/mob/inventory/helpers.dm b/code/modules/mob/mob-inventory-helpers.dm
similarity index 97%
rename from code/modules/mob/inventory/helpers.dm
rename to code/modules/mob/mob-inventory-helpers.dm
index 64bd6e13dd10..8bc6fa3e1493 100644
--- a/code/modules/mob/inventory/helpers.dm
+++ b/code/modules/mob/mob-inventory-helpers.dm
@@ -1,7 +1,7 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2023 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
-//* these call other procs in external.dm *//
+//* these have the primary function of calling other procs in public.dm *//
/**
* dels something or says "x is stuck to your hand"
diff --git a/code/modules/mob/mob-inventory-internal.dm b/code/modules/mob/mob-inventory-internal.dm
new file mode 100644
index 000000000000..b3421051cdb8
--- /dev/null
+++ b/code/modules/mob/mob-inventory-internal.dm
@@ -0,0 +1,288 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Internal inventory logic
+ * You shouldn't be calling or modifying these without good reason.
+ */
+
+//* Routing *//
+
+/**
+ * handles the insertion
+ * item can be moved or not moved before calling
+ *
+ * slot must be a typepath
+ *
+ * @return true/false based on if it worked
+ */
+/mob/proc/handle_abstract_slot_insertion(obj/item/I, slot, flags)
+ if(!ispath(slot, /datum/inventory_slot/abstract))
+ slot = resolve_inventory_slot(slot)?.type
+ if(!ispath(slot, /datum/inventory_slot/abstract))
+ stack_trace("invalid slot: [slot]")
+ else if(slot != /datum/inventory_slot/abstract/put_in_hands)
+ stack_trace("attempted usage of slot id in abstract insertion converted successfully")
+ . = FALSE
+ switch(slot)
+ if(/datum/inventory_slot/abstract/hand/left)
+ return put_in_left_hand(I, flags)
+ if(/datum/inventory_slot/abstract/hand/right)
+ return put_in_right_hand(I, flags)
+ if(/datum/inventory_slot/abstract/put_in_belt)
+ var/obj/item/held = item_by_slot_id(SLOT_ID_BELT)
+ if(flags & INV_OP_FORCE)
+ return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
+ return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
+ if(/datum/inventory_slot/abstract/put_in_backpack)
+ var/obj/item/held = item_by_slot_id(SLOT_ID_BACK)
+ if(flags & INV_OP_FORCE)
+ return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
+ return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
+ if(/datum/inventory_slot/abstract/put_in_hands)
+ return put_in_hands(I, flags)
+ if(/datum/inventory_slot/abstract/put_in_storage, /datum/inventory_slot/abstract/put_in_storage_try_active)
+ if(slot == /datum/inventory_slot/abstract/put_in_storage_try_active)
+ // todo: redirection
+ if(flags & INV_OP_FORCE)
+ if(active_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING))
+ return TRUE
+ else
+ if(active_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND))
+ return TRUE
+ for(var/obj/item/held in get_equipped_items_in_slots(list(
+ SLOT_ID_BELT,
+ SLOT_ID_BACK,
+ SLOT_ID_UNIFORM,
+ SLOT_ID_SUIT,
+ SLOT_ID_LEFT_POCKET,
+ SLOT_ID_RIGHT_POCKET
+ )) + get_held_items())
+ if(isnull(held?.obj_storage))
+ continue
+ if(flags & INV_OP_FORCE)
+ return held.obj_storage.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
+ return held.obj_storage.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
+ return FALSE
+ if(/datum/inventory_slot/abstract/attach_as_accessory)
+ for(var/obj/item/clothing/C in get_equipped_items())
+ if(C.attempt_attach_accessory(I))
+ return TRUE
+ return FALSE
+ else
+ CRASH("Invalid abstract slot [slot]")
+
+//* Unequip *//
+
+/**
+ * handles internal logic of unequipping an item
+ *
+ * @params
+ * - I - item
+ * - flags - inventory operation hint bitfield, see defines
+ * - newloc - where to transfer to. null for nullspace, FALSE for don't transfer
+ * - user - can be null - person doing the removals
+ *
+ * @return TRUE/FALSE for success
+ */
+/mob/proc/_unequip_item(obj/item/I, flags, newloc, mob/user = src)
+ PROTECTED_PROC(TRUE)
+ if(!I)
+ return TRUE
+
+ var/hand = get_held_index(I)
+ var/old
+ if(hand)
+ if(!can_unequip(I, SLOT_ID_HANDS, flags, user))
+ return FALSE
+ unequip_hand_impl(I, hand, flags)
+ old = SLOT_ID_HANDS
+ else
+ if(!I.worn_slot)
+ stack_trace("tried to unequip an item without current equipped slot.")
+ I.worn_slot = _slot_by_item(I)
+ if(!can_unequip(I, I.worn_slot, flags, user))
+ return FALSE
+ old = I.worn_slot
+ _unequip_slot(I.worn_slot, flags)
+ I.unequipped(src, I.worn_slot, flags)
+ handle_item_denesting(I, old, flags, user)
+
+ // this qdeleted catches unequipped() deleting the item.
+ . = QDELETED(I)? FALSE : TRUE
+
+ if(I)
+ // todo: better rendering that takes observers into account
+ if(client)
+ client.screen -= I
+ I.screen_loc = null
+ //! at some point we should have /pre_dropped and /pre_pickup, because dropped should logically come after move.
+ if(I.dropped(src, flags, newloc) == ITEM_RELOCATED_BY_DROPPED)
+ . = FALSE
+ else if(QDELETED(I))
+ // this check RELIES on dropped() being the first if
+ // make sure you don't blindly move it!!
+ // this is meant to catch any potential deletions dropped can cause.
+ . = FALSE
+ else
+ if(!(I.item_flags & ITEM_DROPDEL))
+ if(newloc == null)
+ I.moveToNullspace()
+ else if(newloc != FALSE)
+ I.forceMove(newloc)
+
+ log_inventory("[key_name(src)] unequipped [I] from [old].")
+
+/mob/proc/_unequip_slot(slot, flags)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ var/obj/item/old = _item_by_slot(slot)
+ . = _set_inv_slot(slot, null, flags) != INVENTORY_SLOT_DOES_NOT_EXIST
+ if(.)
+ inventory.on_item_exited(old, resolve_inventory_slot(slot))
+
+/**
+ * handles removing an item from our hud
+ *
+ * some things call us from outside inventory code. this is shitcode and shouldn't be propageted.
+ */
+/mob/proc/_handle_inventory_hud_remove(obj/item/I)
+ if(client)
+ client.screen -= I
+ I.screen_loc = null
+
+//* Equip *//
+
+/**
+ * handles internal logic of equipping an item
+ *
+ * @params
+ * - I - item to equip
+ * - flags - inventory operation hint flags, see defines
+ * - slot - slot to equip it to
+ * - user - user trying to put it on us
+ *
+ * @return TRUE/FALSE on success
+ */
+/mob/proc/_equip_item(obj/item/I, flags, slot, mob/user = src)
+ PROTECTED_PROC(TRUE)
+
+ if(!I) // how tf would we put on "null"?
+ return FALSE
+
+ // resolve slot
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
+ if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
+ // if it's abstract, we go there directly - do not use can_equip as that will just guess.
+ return handle_abstract_slot_insertion(I, slot, flags)
+
+ // slots must have IDs.
+ ASSERT(!isnull(slot_meta.id))
+ // convert to ID after abstract slot checks
+ slot = slot_meta.id
+
+ var/old_slot = slot_id_by_item(I)
+
+ if(old_slot)
+ . = _handle_item_reequip(I, slot, old_slot, flags, user)
+ if(!.)
+ return
+
+ log_inventory("[key_name(src)] moved [I] from [old_slot] to [slot].")
+ else
+ if(!can_equip(I, slot, flags | INV_OP_IS_FINAL_CHECK, user))
+ return FALSE
+
+ var/atom/oldLoc = I.loc
+ if(I.loc != src)
+ I.forceMove(src)
+ if(I.loc != src)
+ // UH OH, SOMEONE MOVED US
+ log_inventory("[key_name(src)] failed to equip [I] to slot (loc sanity failed).")
+ // UH OH x2, WE GOT WORN OVER SOMETHING
+ if(I.worn_over)
+ handle_item_denesting(I, slot, INV_OP_FATAL, user)
+ return FALSE
+
+ _equip_slot(I, slot, flags)
+
+ // TODO: HANDLE DELETIONS IN PICKUP AND EQUIPPED PROPERLY
+ I.pickup(src, flags, oldLoc)
+ I.equipped(src, slot, flags)
+
+ log_inventory("[key_name(src)] equipped [I] to [slot].")
+
+ if(I.zoom)
+ I.zoom()
+
+ return TRUE
+
+/mob/proc/_equip_slot(obj/item/I, slot, flags)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ . = _set_inv_slot(slot, I, flags) != INVENTORY_SLOT_DOES_NOT_EXIST
+ if(.)
+ inventory.on_item_entered(I, resolve_inventory_slot(slot))
+
+//* Slot Change *//
+
+/**
+ * checks if we already have something in our inventory
+ * if so, this will try to shift the slots over, calling equipped/unequipped automatically
+ *
+ * INV_OP_FORCE will allow ignoring can unequip.
+ *
+ * return true/false based on if we succeeded
+ */
+/mob/proc/_handle_item_reequip(obj/item/I, slot, old_slot, flags, mob/user = src, hand_index)
+ ASSERT(slot)
+ if(!old_slot)
+ // DO NOT USE _slot_by_item - at this point, the item has already been var-set into the new slot!
+ // slot_id_by_item however uses cached values still!
+ old_slot = slot_id_by_item(I)
+ if(!old_slot)
+ // still not there, wasn't already in inv
+ return FALSE
+ // this IS a slot shift!
+ . = old_slot
+ if((slot == old_slot) && (slot != SLOT_ID_HANDS))
+ // lol we're done (unless it was hands)
+ return TRUE
+ if(slot == SLOT_ID_HANDS)
+ // if we're going into hands,
+ // just check can unequip
+ if(!can_unequip(I, old_slot, flags, user))
+ // check can unequip
+ return FALSE
+ // call procs
+ if(old_slot == SLOT_ID_HANDS)
+ unequip_hand_impl(I, get_held_index(I), flags)
+ else
+ _unequip_slot(old_slot, flags)
+ I.unequipped(src, old_slot, flags)
+ // sigh
+ handle_item_denesting(I, old_slot, flags, user)
+ // TODO: HANDLE DELETIONS ON EQUIPPED PROPERLY, INCLUDING ON HANDS
+ // ? we don't do this on hands, hand procs do it
+ // _equip_slot(I, slot, update_icons)
+ I.held_index = hand_index
+ I.equipped(src, slot, flags)
+ log_inventory("[key_name(src)] moved [I] from [old_slot] to hands.")
+ // hand procs handle rest
+ return TRUE
+ else
+ // else, this gets painful
+ if(!can_unequip(I, old_slot, flags, user))
+ return FALSE
+ if(!can_equip(I, slot, flags | INV_OP_IS_FINAL_CHECK, user, old_slot))
+ return FALSE
+ // ?if it's from hands, hands aren't a slot.
+ if(old_slot == SLOT_ID_HANDS)
+ unequip_hand_impl(I, get_held_index(I), flags)
+ else
+ _unequip_slot(old_slot, flags)
+ I.unequipped(src, old_slot, flags)
+ // TODO: HANDLE DELETIONS ON EQUIPPED PROPERLY
+ // sigh
+ _equip_slot(I, slot, flags)
+ I.equipped(src, slot, flags)
+ log_inventory("[key_name(src)] moved [I] from [old_slot] to [slot].")
+ return TRUE
diff --git a/code/modules/mob/inventory/stripping.dm b/code/modules/mob/mob-inventory-stripping.dm
similarity index 93%
rename from code/modules/mob/inventory/stripping.dm
rename to code/modules/mob/mob-inventory-stripping.dm
index 20691482912a..7af49e06e983 100644
--- a/code/modules/mob/inventory/stripping.dm
+++ b/code/modules/mob/mob-inventory-stripping.dm
@@ -1,3 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// todo: this should be just mob-interaction-panel and be a tgui interaction panel lol
+
+/**
+ * Stripping system
+ * Procs can be called and overridden as needed
+ * Please be careful when doing so and understand what you are overriding.
+ */
+
// todo: tgui
// todo: ui state handles prechecks? interesting to deal with.
/mob/proc/mouse_drop_strip_interaction(mob/user)
@@ -69,7 +80,7 @@
// now for hands
if(has_hands())
- for(var/i in 1 to get_number_of_hands())
+ for(var/i in 1 to get_nominal_hand_count())
switch(i)
if(1)
. += "Left hand: "
@@ -77,7 +88,7 @@
. += "Right hand: "
else
. += "Hand [i]: "
- var/obj/item/holding = get_held_item_of_index(i)
+ var/obj/item/holding = get_held_index(i)
. += "[holding? holding.name : "nothing"]
"
. += "
"
@@ -127,10 +138,10 @@
if(!strip_interaction_prechecks(user))
return FALSE
- if((index < 1) || (index > get_number_of_hands()))
+ if((index < 1) || (index > get_nominal_hand_count()))
return FALSE
- var/obj/item/ours = get_held_item_of_index(index)
+ var/obj/item/ours = get_held_index(index)
var/obj/item/theirs = user.get_active_held_item()
if(!ours && !theirs)
@@ -238,7 +249,7 @@
. = attempt_slot_strip(user, slot)
if("hand")
var/index = text2num(href_list["id"])
- if(!index || (index < 1) || (index > get_number_of_hands()))
+ if(!index || (index < 1) || (index > get_nominal_hand_count()))
return
. = attempt_hand_strip(user, index)
// option mob
diff --git a/code/modules/mob/mob-inventory.dm b/code/modules/mob/mob-inventory.dm
new file mode 100644
index 000000000000..a5c6fc71c5bc
--- /dev/null
+++ b/code/modules/mob/mob-inventory.dm
@@ -0,0 +1,482 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+//* Init *//
+
+// todo: rework this proc. what happens if it's already there? documentation?? this should probably reset the inventory maybe?
+/mob/proc/init_inventory()
+ return
+
+//* Carry Weight *//
+
+/mob/proc/update_carry_slowdown()
+ return
+
+/mob/proc/update_item_slowdown()
+ return
+
+//* Checks / Enumerations *//
+
+/**
+ * gets the primary item in a slot
+ * null if not in inventory. inhands don't count as inventory here, use held item procs.
+ */
+/mob/proc/item_by_slot_id(slot)
+ return _item_by_slot(slot) // why the needless indirection? so people don't override this for slots!
+
+/**
+ * get slot of item if it's equipped.
+ * null if not in inventory. SLOT_HANDS if held.
+ */
+/mob/proc/slot_id_by_item(obj/item/I)
+ return is_in_inventory(I) || null // short circuited to that too
+ // if equipped/unequipped didn't set worn_slot well jokes on you lmfao
+
+/**
+ * gets the primary item and nested items (e.g. gloves, magboots, accessories) in a slot
+ * null if not in inventory, otherwise list
+ * inhands do not count as inventory
+ */
+/mob/proc/items_by_slot_id(slot)
+ var/obj/item/I = _item_by_slot(slot)
+ if(!I)
+ return list()
+ I = I.inv_slot_attached()
+ return islist(I)? I : list(I)
+
+/**
+ * returns if we have something equipped - the slot if it is, null if not
+ *
+ * SLOT_ID_HANDS if in hands
+ */
+/mob/proc/is_in_inventory(obj/item/I)
+ return (I?.worn_mob() == src) && I.worn_slot
+ // we use entirely cached vars for speed.
+ // if this returns bad data well fuck you, don't break equipped()/unequipped().
+
+/**
+ * returns if an item is in inventory (equipped) rather than hands
+ */
+/mob/proc/is_wearing(obj/item/I)
+ var/slot = is_in_inventory(I)
+ return slot && (slot != SLOT_ID_HANDS)
+
+/**
+ * get all equipped items
+ *
+ * @params
+ * include_inhands - include held items too?
+ * include_restraints - include restraints too?
+ */
+/mob/proc/get_equipped_items(include_inhands, include_restraints)
+ return get_held_items() + _get_all_slots(include_restraints)
+
+// todo: below procs needs optimization for when we need the datum anyways, to avoid two lookups
+
+/mob/proc/has_slot(id)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return _item_by_slot(id) != INVENTORY_SLOT_DOES_NOT_EXIST
+
+/mob/proc/semantically_has_slot(id)
+ return has_slot(id) && _semantic_slot_id_check(id)
+
+/mob/proc/get_inventory_slot_ids(semantic, sorted)
+ // get all
+ if(sorted)
+ . = list()
+ for(var/id as anything in GLOB.inventory_slot_meta)
+ if(!semantically_has_slot(id))
+ continue
+ . += id
+ return
+ else
+ . = _get_inventory_slot_ids()
+ // check if we should filter
+ if(!semantic)
+ return
+ . = _get_inventory_slot_ids()
+ for(var/id in .)
+ if(!_semantic_slot_id_check(id))
+ . -= id
+
+//* Equipping *//
+
+/**
+ * checks if we can equip an item to a slot
+ *
+ * Preconditions: The item will either be equipped on us already, or not yet equipped.
+ *
+ * @return TRUE/FALSE
+ *
+ * @params
+ * - I - item
+ * - slot - slot ID
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - user trying to equip that thing to us there - can be null
+ * - denest_to - the old slot we're leaving if called from handle_item_reequip. **extremely** snowflakey
+ *
+ * todo: refactor nesting to not require this shit
+ */
+/mob/proc/can_equip(obj/item/I, slot, flags, mob/user, denest_to)
+ // let's NOT.
+ if(I && QDELETED(I))
+ to_chat(user, SPAN_DANGER("A deleted [I] was checked in can_equip(). Report this entire line to coders immediately. Debug data: [I] ([REF(I)]) slot [slot] flags [flags] user [user]"))
+ to_chat(user, SPAN_DANGER("can_equip will now attempt to prevent the deleted item from being equipped. There should be no glitches."))
+ return FALSE
+
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
+ var/self_equip = user == src
+ if(!slot_meta)
+ . = FALSE
+ CRASH("Failed to resolve to slot datm.")
+
+ if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
+ // special handling: make educated guess, defaulting to yes
+ switch(slot_meta.type)
+ if(/datum/inventory_slot/abstract/hand/left)
+ return (flags & INV_OP_FORCE) || !get_left_held_item()
+ if(/datum/inventory_slot/abstract/hand/right)
+ return (flags & INV_OP_FORCE) || !get_right_held_item()
+ if(/datum/inventory_slot/abstract/put_in_backpack)
+ var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
+ return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
+ if(/datum/inventory_slot/abstract/put_in_belt)
+ var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
+ return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
+ if(/datum/inventory_slot/abstract/put_in_hands)
+ return (flags & INV_OP_FORCE) || !are_usable_hands_full()
+ return TRUE
+
+ if(!inventory_slot_bodypart_check(I, slot, user, flags) && !(flags & INV_OP_FORCE))
+ return FALSE
+
+ var/conflict_result = inventory_slot_conflict_check(I, slot, flags)
+ var/obj/item/to_wear_over
+
+ if((flags & INV_OP_IS_FINAL_CHECK) && conflict_result && (slot != SLOT_ID_HANDS))
+ // try to fit over
+ var/obj/item/conflicting = item_by_slot_id(slot)
+ if(conflicting)
+ // there's something there
+ var/can_fit_over = I.equip_worn_over_check(src, slot, user, conflicting, flags)
+ if(can_fit_over)
+ conflict_result = CAN_EQUIP_SLOT_CONFLICT_NONE
+ to_wear_over = conflicting
+ // ! DANGER: snowflake time
+ // take it out of the slot
+ _unequip_slot(slot, flags | INV_OP_NO_LOGIC | INV_OP_NO_UPDATE_ICONS)
+ // recheck
+ conflict_result = inventory_slot_conflict_check(I, slot)
+ // put it back in incase something else breaks
+ _equip_slot(conflicting, slot, flags | INV_OP_NO_LOGIC | INV_OP_NO_UPDATE_ICONS)
+
+ switch(conflict_result)
+ if(CAN_EQUIP_SLOT_CONFLICT_HARD)
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, SPAN_WARNING("[self_equip? "You" : "They"] are already [slot_meta.display_plural? "holding too many things" : "wearing something"] [slot_meta.display_preposition] [self_equip? "your" : "their"] [slot_meta.display_name]."))
+ return FALSE
+ if(CAN_EQUIP_SLOT_CONFLICT_SOFT)
+ if(!(flags & INV_OP_FORCE))
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, SPAN_WARNING("[self_equip? "You" : "They"] are already [slot_meta.display_plural? "holding too many things" : "wearing something"] [slot_meta.display_preposition] [self_equip? "your" : "their"] [slot_meta.display_name]."))
+ return FALSE
+
+ if(!inventory_slot_semantic_conflict(I, slot, user) && !(flags & INV_OP_FORCE))
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, SPAN_WARNING("[I] doesn't fit there."))
+ return FALSE
+
+ var/blocked_by
+
+ if((blocked_by = inventory_slot_reachability_conflict(I, slot, user)) && !(flags & (INV_OP_FORCE | INV_OP_IGNORE_REACHABILITY)))
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, SPAN_WARNING("\the [blocked_by] is in the way!"))
+ return FALSE
+
+ // lastly, check item's opinion
+ if(!I.can_equip(src, slot, user, flags))
+ return FALSE
+
+ // we're the final check - side effects ARE allowed
+ if((flags & INV_OP_IS_FINAL_CHECK) && to_wear_over)
+ //! Note: this means that can_unequip is NOT called for to wear over.
+ //! This is intentional, but very, very sonwflakey.
+ to_wear_over.worn_inside = I
+ // setting worn inside first disallows equip/unequip from triggering
+ to_wear_over.forceMove(I)
+ // check we don't have something already (wtf)
+ if(I.worn_over)
+ handle_item_denesting(I, denest_to, flags, user)
+ // set the other way around
+ I.worn_over = to_wear_over
+ // tell it we're inserting the old item
+ I.equip_on_worn_over_insert(src, slot, user, to_wear_over, flags)
+ // take the old item off our screen
+ client?.screen -= to_wear_over
+ to_wear_over.screen_loc = null
+ // we don't call slot re-equips here because the equip proc does this for us
+
+ return TRUE
+
+/**
+ * equips an item to a slot if possible
+ *
+ * @params
+ * - I - item
+ * - slot - the slot
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+/mob/proc/equip_to_slot_if_possible(obj/item/I, slot, flags, mob/user)
+ return _equip_item(I, flags, slot, user)
+
+/**
+ * equips an item to a slot if possible
+ * item is deleted on failure
+ *
+ * @params
+ * - I - item
+ * - slot - the slot
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+/mob/proc/equip_to_slot_or_del(obj/item/I, slot, flags, mob/user)
+ . = equip_to_slot_if_possible(I, slot, flags, user)
+ if(!.)
+ qdel(I)
+/**
+ * automatically equips to the best inventory (non storage!) slot we can find for an item, if possible
+ * this proc is silent for the sub-calls by default to prevent spam.
+ *
+ * @params
+ * - I - item
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+/mob/proc/equip_to_appropriate_slot(obj/item/I, flags, mob/user)
+ for(var/slot in GLOB.slot_equipment_priority)
+ if(equip_to_slot_if_possible(I, slot, flags | INV_OP_SUPPRESS_WARNING, user))
+ return TRUE
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, user == src? SPAN_WARNING("You can't find somewhere to equip [I] to!") : SPAN_WARNING("[src] has nowhere to equip [I] to!"))
+ return FALSE
+
+/**
+ * automatically equips to the best inventory (non storage!) slot we can find for an item, if possible
+ *
+ * item is deleted on failure.
+ *
+ * @params
+ * - I - item
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+
+/mob/proc/equip_to_appropriate_slot_or_del(obj/item/I, flags, mob/user)
+ if(!equip_to_appropriate_slot(I, flags, user))
+ qdel(I)
+
+/**
+ * forcefully equips an item to a slot
+ * kicks out conflicting items if possible
+ *
+ * This CAN fail, so listen to return value
+ * Why? YOU MIGHT EQUIP TO A MOB WITHOUT A CERTAIN SLOT!
+ *
+ * @params
+ * - I - item
+ * - slot - slot to equip to
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+/mob/proc/force_equip_to_slot(obj/item/I, slot, flags, mob/user)
+ return _equip_item(I, flags | INV_OP_FORCE | INV_OP_CAN_DISPLACE, slot, user)
+
+/**
+ * forcefully equips an item to a slot
+ * kicks out conflicting items if possible
+ * if still failing, item is deleted
+ *
+ * this can fail, so listen to return values.
+ * @params
+ * - I - item
+ * - slot - slot to equip to
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - the user doing the action, if any. defaults to ourselves.
+ *
+ * @return TRUE/FALSE
+ */
+/mob/proc/force_equip_to_slot_or_del(obj/item/I, slot, flags, mob/user)
+ if(!force_equip_to_slot(I, slot, flags, user))
+ qdel(I)
+ return FALSE
+ return TRUE
+
+//* Dropping *//
+
+/**
+ * checks if we can unequip an item
+ *
+ * Preconditions: The item is either equipped already, or isn't equipped.
+ *
+ * @return TRUE/FALSE
+ *
+ * @params
+ * - I - item
+ * - slot - slot we're unequipping from - can be null
+ * - flags - inventory operation hint bitfield, see defines
+ * - user - stripper - can be null
+ */
+/mob/proc/can_unequip(obj/item/I, slot, flags, mob/user = src)
+ // destroyed IS allowed to call these procs
+ if(I && QDELETED(I) && !QDESTROYING(I))
+ to_chat(user, SPAN_DANGER("A deleted [I] was checked in can_unequip(). Report this entire line to coders immediately. Debug data: [I] ([REF(I)]) slot [slot] flags [flags] user [user]"))
+ to_chat(user, SPAN_DANGER("can_unequip will return TRUE to allow you to drop the item, but expect potential glitches!"))
+ return TRUE
+
+ if(!slot)
+ slot = slot_id_by_item(I)
+
+ if(!(flags & INV_OP_FORCE) && HAS_TRAIT(I, TRAIT_ITEM_NODROP))
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
+ to_chat(user, SPAN_WARNING("[I] is stubbornly stuck [slot_meta.display_preposition] your [slot_meta.display_name]!"))
+ return FALSE
+
+ var/blocked_by
+ if((blocked_by = inventory_slot_reachability_conflict(I, slot, user)) && !(flags & (INV_OP_FORCE | INV_OP_IGNORE_REACHABILITY)))
+ if(!(flags & INV_OP_SUPPRESS_WARNING))
+ to_chat(user, SPAN_WARNING("\the [blocked_by] is in the way!"))
+ return FALSE
+
+ // lastly, check item's opinion
+ if(!I.can_unequip(src, slot, user, flags))
+ return FALSE
+
+ return TRUE
+
+// So why do all of these return true if the item is null?
+// Semantically, transferring/dropping nothing always works
+// This lets us tidy up other pieces of code by not having to typecheck everything.
+// However, if you do pass in an invalid object instead of null, the procs will fail or pass
+// depending on needed behavior.
+// Use the helpers when you can, it's easier for everyone and makes replacing behaviors later easier.
+
+// This logic is actually **stricter** than the previous logic, which was "if item doesn't exist, it always works"
+
+// todo: yeah this is pretty bad huh; this is because new hand crap is properly refactored and we aren't
+// so this is just a wrapper to route stuff around while we slowly refactor inventory.
+/datum/inventory/proc/drop_item_to_ground(obj/item/I, inv_op_flags, datum/event_args/actor/actor)
+ if(!actor)
+ actor = new /datum/event_args/actor(owner)
+ return owner.drop_item_to_ground(I, inv_op_flags, actor?.performer)
+
+/**
+ * drops an item to ground
+ *
+ * semantically returns true if the thing is no longer in our inventory after our call, whether or not we dropped it
+ * if you require better checks, check if something is in inventory first.
+ *
+ * if the item is null, this returns true
+ * if an item is not in us, this returns true
+ */
+/mob/proc/drop_item_to_ground(obj/item/I, flags, mob/user = src)
+ // destroyed IS allowed to call these procs
+ if(I && QDELETED(I) && !QDESTROYING(I))
+ to_chat(user, SPAN_DANGER("A deleted item [I] was used in drop_item_to_ground(). Report the entire line to coders. Debugging information: [I] ([REF(I)]) flags [flags] user [user]"))
+ to_chat(user, SPAN_DANGER("Drop item to ground will now proceed, ignoring the bugged state. Errors may ensue."))
+ else if(!is_in_inventory(I))
+ return TRUE
+ return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, drop_location(), user)
+
+/**
+ * transfers an item somewhere
+ * newloc MUST EXIST, use transfer_item_to_nullspace otherwise
+ *
+ * semantically returns true if we transferred something from our inventory to newloc in the call
+ *
+ * if the item is null, this returns true
+ * if an item is not in us, this crashes
+ */
+/mob/proc/transfer_item_to_loc(obj/item/I, newloc, flags, mob/user)
+ if(!I)
+ return TRUE
+ ASSERT(newloc)
+ if(!is_in_inventory(I))
+ return FALSE
+ return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, newloc, user)
+
+/**
+ * transfers an item into nullspace
+ *
+ * semantically returns true if we transferred something from our inventory to null in the call
+ *
+ * if the item is null, this returns true
+ * if an item is not in us, this crashes
+ */
+/mob/proc/transfer_item_to_nullspace(obj/item/I, flags, mob/user)
+ if(!I)
+ return TRUE
+ if(!is_in_inventory(I))
+ return FALSE
+ return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, null, user)
+
+/**
+ * removes an item from inventory. does NOT move it.
+ * item MUST be qdel'd or moved after this if it returns TRUE!
+ *
+ * semantically returns true if the passed item is no longer in our inventory after the call
+ *
+ * if the item is null, ths returns true
+ * if an item is not in us, this returns true
+ */
+/mob/proc/temporarily_remove_from_inventory(obj/item/I, flags, mob/user)
+ if(!is_in_inventory(I))
+ return TRUE
+ return _unequip_item(I, flags | INV_OP_DIRECTLY_DROPPING, FALSE, user)
+
+//* MasS Operations *//
+
+/**
+ * drops everything in our inventory
+ *
+ * @params
+ * - include_inhands - include held items too?
+ * - include_restraints - include restraints too?
+ * - force - ignore nodrop and all that
+ */
+/mob/proc/drop_inventory(include_inhands = TRUE, include_restraints = TRUE, force = TRUE)
+ for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
+ drop_item_to_ground(I, INV_OP_SILENT | INV_OP_FLUFFLESS | (force? INV_OP_FORCE : NONE))
+
+ // todo: handle what happens if dropping something requires a logic thing
+ // e.g. dropping jumpsuit makes it impossible to transfer a belt since it
+ // de-equipped from the jumpsuit
+
+/mob/proc/transfer_inventory_to_loc(atom/newLoc, include_inhands = TRUE, include_restraints = TRUE, force = TRUE)
+ for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
+ transfer_item_to_loc(I, newLoc, INV_OP_SILENT | INV_OP_FLUFFLESS | (force? INV_OP_FORCE : NONE))
+ // todo: handle what happens if dropping something requires a logic thing
+ // e.g. dropping jumpsuit makes it impossible to transfer a belt since it
+ // de-equipped from the jumpsuit
+
+/**
+ * wipe our inventory
+ *
+ * @params
+ * include_inhands - include held items too?
+ * include_restraints - include restraints too?
+ */
+/mob/proc/delete_inventory(include_inhands = TRUE, include_restraints = TRUE)
+ for(var/obj/item/I as anything in get_equipped_items(include_inhands, include_restraints))
+ qdel(I)
diff --git a/code/modules/mob/login.dm b/code/modules/mob/mob-login.dm
similarity index 93%
rename from code/modules/mob/login.dm
rename to code/modules/mob/mob-login.dm
index d781b7a82aa3..2b8d8a1ea632 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/mob-login.dm
@@ -81,8 +81,12 @@
client.action_drawer.register_holder(actions_innate)
if(inventory)
client.action_drawer.register_holder(inventory.actions)
- // we really hate that this is needed but it is until the screens/images reset isn't there
+ // todo: we really hate that this is needed but it is until the screens/images reset isn't in Login()
client.action_drawer.reassert_screen()
+ // bind actor HUDs
+ // todo: invalidate any potential remote control going on as we will have a state desync otherwise;
+ // remote control systems will overrule actor hud binding.
+ client.actor_huds.bind_all_to_mob(src)
// reset statpanel of any verbs/whatnot
client.tgui_stat?.request_reload()
// update ssd overlay
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/mob-logout.dm
similarity index 100%
rename from code/modules/mob/logout.dm
rename to code/modules/mob/mob-logout.dm
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 359eaaac1a18..2dfb74a08761 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -175,7 +175,7 @@
if(C.statpanel_tab("Status"))
STATPANEL_DATA_ENTRY("Ping", "[round(client.lastping,1)]ms (Avg: [round(client.avgping,1)]ms)")
STATPANEL_DATA_ENTRY("Map", "[(LEGACY_MAP_DATUM)?.name || "Loading..."]")
- if(!isnull(SSmapping.next_station) && (SSmapping.next_station.name != SSmapping.loaded_station.name))
+ if(!isnull(SSmapping.next_station) && !isnull(SSmapping.loaded_station) && (SSmapping.next_station.name != SSmapping.loaded_station.name))
STATPANEL_DATA_ENTRY("Next Map", "[SSmapping.next_station.name]")
/// Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
@@ -1031,9 +1031,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
animate(client, color = null, time = 10)
return
-/mob/proc/swap_hand()
- return
-
/mob/proc/will_show_tooltip()
if(alpha <= EFFECTIVE_INVIS)
return FALSE
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index eb2428be040d..93d40d40da3f 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -56,11 +56,11 @@
/// Atom we're buckl**ing** to. Used to stop stuff like lava from incinerating those who are mid buckle.
var/atom/movable/buckling
- //* HUD (Atom)
+ //* HUD (Atom) *//
/// HUDs to initialize, typepaths
var/list/atom_huds_to_initialize
- //* HUD
+ //* HUD *//
/// active, opened storage
// todo: doesn't clear from clients properly on logout, relies on login clearing screne.
// todo: we'll eventually need a system to handle ckey transfers properly.
@@ -113,9 +113,11 @@
/// our abilities - set to list of paths to init to intrinsic abilities.
var/list/datum/ability/abilities
- //? Inventory
+ //* Inventory *//
/// our inventory datum, if any.
var/datum/inventory/inventory
+ /// active hand index - null or num. must always be in range of held_items indices!
+ var/active_hand
//* IFF *//
/// our IFF factions
@@ -161,7 +163,6 @@
var/next_move = null // For click delay, despite the misleading name.
- var/list/datum/action/actions = list()
var/atom/movable/screen/hands = null
var/atom/movable/screen/pullin = null
var/atom/movable/screen/purged = null
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 8107d87fc978..889e85b4b709 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -465,11 +465,11 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM)
threatcount += 4
if(auth_weapons && !access_obj.allowed(src))
- if(istype(l_hand, /obj/item/gun) || istype(l_hand, /obj/item/melee))
- threatcount += 4
-
- if(istype(r_hand, /obj/item/gun) || istype(r_hand, /obj/item/melee))
- threatcount += 4
+ for(var/obj/item/held as anything in get_held_items())
+ if(istype(held, /obj/item/melee))
+ threatcount += 4
+ if(istype(held, /obj/item/gun))
+ threatcount += 4
if(istype(belt, /obj/item/gun) || istype(belt, /obj/item/melee))
threatcount += 2
@@ -593,35 +593,6 @@ GLOBAL_VAR_INIT(organ_combined_size, 25 + 70 + 30 + 25 + 25 + 25 + 25 + 10 + 10
var/area/A = get_area(src)
return A.sound_env
-/mob/proc/position_hud_item(obj/item/item, slot)
- var/held = is_holding(item)
-
- if(!slot)
- slot = slot_id_by_item(item)
-
- if(!istype(hud_used) || !slot || !LAZYLEN(hud_used.slot_info))
- return
-
- // They may have hidden their entire hud but the hands.
- if(!hud_used.hud_shown && held)
- item.screen_loc = null
- return
-
- // They may have hidden the icons in the bottom left with the hide button.
- if(!hud_used.inventory_shown && !held && (resolve_inventory_slot(slot)?.inventory_slot_flags & INV_SLOT_HUD_REQUIRES_EXPAND))
- item.screen_loc = null
- return
-
- var/screen_place = held? hud_used.hand_info["[get_held_index(item)]"] : hud_used.slot_info["[slot]"]
- if(!screen_place)
- item.screen_loc = null
- return
-
- if(item.base_pixel_x || item.base_pixel_y)
- screen_place = pixel_shift_screen_loc(screen_place, item.base_pixel_x, item.base_pixel_y)
-
- item.screen_loc = screen_place
-
/mob/proc/can_see_reagents()
// Dead guys and silicons can always see reagents.
return stat == DEAD || issilicon(src)
diff --git a/code/modules/mob/update_icons.dm b/code/modules/mob/update_icons.dm
index a5e110a93bbc..b8eac465c1c1 100644
--- a/code/modules/mob/update_icons.dm
+++ b/code/modules/mob/update_icons.dm
@@ -35,19 +35,7 @@
/mob/proc/update_inv_back()
return
-/mob/proc/update_inv_active_hand()
- return
-
-/mob/living/update_inv_active_hand(var/A)
- if(hand)
- update_inv_l_hand(A)
- else
- update_inv_r_hand(A)
-
-/mob/proc/update_inv_l_hand()
- return
-
-/mob/proc/update_inv_r_hand()
+/mob/proc/update_inv_hand(index)
return
/mob/proc/update_inv_wear_mask()
@@ -92,10 +80,15 @@
/mob/proc/update_targeted()
return
-/mob/proc/update_inv_hands()
- update_inv_l_hand()
- update_inv_r_hand()
-
/mob/proc/update_hair()
/mob/proc/update_eyes()
+
+//* Helpers - These call other update procs. *//
+
+/mob/proc/update_inv_hands()
+ for(var/i in 1 to length(inventory?.held_items))
+ update_inv_hand(i)
+
+/mob/proc/update_inv_active_hand()
+ return update_inv_hand(active_hand)
diff --git a/code/modules/organs/external/external.dm b/code/modules/organs/external/external.dm
index 8021f5df2ef4..e6507bcfb7a3 100644
--- a/code/modules/organs/external/external.dm
+++ b/code/modules/organs/external/external.dm
@@ -547,14 +547,8 @@
return FALSE
if(user == src.owner)
- var/grasp
- if(user.l_hand == tool && (src.body_part_flags & (ARM_LEFT|HAND_LEFT)))
- grasp = BP_L_HAND
- else if(user.r_hand == tool && (src.body_part_flags & (ARM_RIGHT|HAND_RIGHT)))
- grasp = BP_R_HAND
-
- if(grasp)
- to_chat(user, SPAN_WARNING("You can't reach your [src.name] while holding [tool] in your [owner.get_bodypart_name(grasp)]."))
+ if(owner.get_hand_organ(owner.get_held_index(tool)) == src)
+ to_chat(user, SPAN_WARNING("You can't reach your [src] while holding [tool] in the same hand!"))
return FALSE
user.setClickCooldown(user.get_attack_speed(tool))
@@ -1022,13 +1016,6 @@ Note that amputating the affected organ does in fact remove the infection from t
qdel(src)
- if(victim.l_hand)
- if(istype(victim.l_hand,/obj/item/material/twohanded)) //if they're holding a two-handed weapon, drop it now they've lost a hand
- victim.l_hand.update_held_icon()
- if(victim.r_hand)
- if(istype(victim.r_hand,/obj/item/material/twohanded))
- victim.r_hand.update_held_icon()
-
/****************************************************
HELPERS
****************************************************/
@@ -1509,6 +1496,12 @@ Note that amputating the affected organ does in fact remove the infection from t
if(!istype(I,/obj/item/implant) && !istype(I,/obj/item/nif))
return TRUE
+//* Hand Integration *//
+
+// todo: some kind of API for querying what hands this organ provides
+// this will require organs be composition instead of inheritance,
+// as defining this on every left / right hand would be satanic.
+
//* Environmentals *//
// todo: limb specific
diff --git a/code/modules/organs/external/subtypes/standard.dm b/code/modules/organs/external/subtypes/standard.dm
index deab9e72497b..6fcd310eb1af 100644
--- a/code/modules/organs/external/subtypes/standard.dm
+++ b/code/modules/organs/external/subtypes/standard.dm
@@ -104,9 +104,9 @@
if(prob(.))
owner.custom_pain("A jolt of pain surges through your [name]!",1)
if(organ_tag == BP_L_ARM) //Specific level 2 'feature
- owner.drop_held_item_of_index(1)
+ owner.drop_held_index(1)
else if(organ_tag == BP_R_ARM)
- owner.drop_held_item_of_index(2)
+ owner.drop_held_index(2)
/obj/item/organ/external/arm/right
organ_tag = BP_R_ARM
@@ -225,9 +225,17 @@
if(prob(.))
owner.custom_pain("A jolt of pain surges through your [name]!",1)
if(organ_tag == BP_L_HAND) //Specific level 2 'feature
- owner.drop_left_held_item()
+ // sequentially drop the first left-held-item
+ for(var/i in 1 to length(owner.inventory?.held_items) step 2)
+ if(isnull(owner.inventory.held_items[i]))
+ continue
+ owner.drop_held_index(i)
else if(organ_tag == BP_R_HAND)
- owner.drop_right_held_item()
+ // sequentially drop the first left-right-item
+ for(var/i in 2 to length(owner.inventory?.held_items) step 2)
+ if(isnull(owner.inventory.held_items[i]))
+ continue
+ owner.drop_held_index(i)
/obj/item/organ/external/hand/right
organ_tag = BP_R_HAND
diff --git a/code/modules/organs/internal/subtypes/augment.dm b/code/modules/organs/internal/subtypes/augment.dm
index bbe4e3311c7a..8dc71901e8c8 100644
--- a/code/modules/organs/internal/subtypes/augment.dm
+++ b/code/modules/organs/internal/subtypes/augment.dm
@@ -218,7 +218,7 @@
to_chat(M, SPAN_NOTICE("You cannot use your augments when restrained."))
return FALSE
- if((slot == /datum/inventory_slot/abstract/hand/left && l_hand) || (slot == /datum/inventory_slot/abstract/hand/right && r_hand))
+ if((slot == /datum/inventory_slot/abstract/hand/left && get_left_held_item()) || (slot == /datum/inventory_slot/abstract/hand/right && get_right_held_item()))
to_chat(M, SPAN_WARNING("Your hand is full. Drop something first."))
return FALSE
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index b2bcd9a135a1..3f06f9f874ab 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -18,35 +18,16 @@
drop_sound = 'sound/items/drop/cardboardbox.ogg'
pickup_sound = 'sound/items/pickup/cardboardbox.ogg'
-
-
/obj/item/paper_bin/OnMouseDropLegacy(mob/user as mob)
if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
- if(!istype(usr, /mob/living/simple_mob))
- if( !usr.get_active_held_item() ) //if active hand is empty
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
-
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
-
- to_chat(user, "You pick up the [src].")
- user.put_in_hands(src)
-
- return
+ if(!user.put_in_hands(src))
+ return
+ to_chat(user, "You pick up the [src].")
/obj/item/paper_bin/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
- if (H.hand)
- temp = H.organs_by_name["l_hand"]
- if(temp && !temp.is_usable())
- to_chat(user, "You try to move your [temp.name], but cannot!")
- return
+ if(!user.standard_hand_usability_check(src, e_args.hand_index, HAND_MANIPULATION_GENERAL))
+ return
+
var/response = ""
if(!papers.len)
response = alert(user, "Do you take regular paper, or Carbon copy paper?", "Paper type request", "Regular", "Carbon-Copy", "Cancel")
diff --git a/code/modules/photography/camera.dm b/code/modules/photography/camera.dm
index 374b9255ac09..3a2edbb82f37 100644
--- a/code/modules/photography/camera.dm
+++ b/code/modules/photography/camera.dm
@@ -107,19 +107,15 @@
var/mob_detail
for(var/mob/living/carbon/A in the_turf)
if(A.invisibility) continue
- var/holding = null
- if(A.l_hand || A.r_hand)
- if(A.l_hand) holding = "They are holding \a [A.l_hand]"
- if(A.r_hand)
- if(holding)
- holding += " and \a [A.r_hand]"
- else
- holding = "They are holding \a [A.r_hand]"
+ var/holding = list()
+
+ for(var/obj/item/held as anything in A.get_held_items())
+ holding += "\a [held]"
if(!mob_detail)
- mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]. "
+ mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[length(holding)? " They are holding [english_list(holding)]":"."]. "
else
- mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]."
+ mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[length(holding)? " They are holding [english_list(holding)]":"."]."
return mob_detail
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index 6fdfc767bb8b..32f478273c62 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -47,7 +47,6 @@
AddComponent(/datum/component/radiation_listener)
rad_insulation = active? rad_insulation_active : rad_insulation_inactive
-
/obj/machinery/power/rad_collector/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(anchored)
if(!src.locked)
diff --git a/code/modules/power/smes/smes_construction.dm b/code/modules/power/smes/smes_construction.dm
index 2dde0f3ad5b6..2b1109adfc37 100644
--- a/code/modules/power/smes/smes_construction.dm
+++ b/code/modules/power/smes/smes_construction.dm
@@ -165,7 +165,7 @@
log_game("SMES FAILURE: [src.x]X [src.y]Y [src.z]Z User: [usr.ckey], Intensity: [intensity]/100")
message_admins("SMES FAILURE: [src.x]X [src.y]Y [src.z]Z User: [usr.ckey], Intensity: [intensity]/100 - JMP")
- var/used_hand = h_user.hand? BP_L_HAND : BP_R_HAND
+ var/used_hand = h_user.get_active_hand_organ()?.organ_tag
switch (intensity)
if (0 to 15)
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 91a323abad26..7e23875486c7 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -308,7 +308,7 @@
update_icon() // In case item_state is set somewhere else.
..()
-/obj/item/gun/update_held_icon()
+/obj/item/gun/update_worn_icon()
if(wielded_item_state)
var/mob/living/M = loc
if(istype(M))
@@ -532,7 +532,7 @@
// We do this down here, so we don't get the message if we fire an empty gun.
- if(user.is_holding(src) && user.hands_full())
+ if(user.is_holding(src) && user.are_usable_hands_full())
if(one_handed_penalty >= 20)
to_chat(user, "You struggle to keep \the [src] pointed at the correct position with just one hand!")
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index 89db582917b5..1fbe3f438125 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -131,7 +131,7 @@
user.visible_message("[user] inserts [P] into [src].", "You insert [P] into [src].")
playsound(src, 'sound/weapons/flipblade.ogg', 50, 1)
update_icon()
- update_held_icon()
+ update_worn_icon()
else
to_chat(user, "This cell is not fitted for [src].")
return
@@ -147,7 +147,7 @@
power_supply = null
playsound(src, 'sound/weapons/empty.ogg', 50, 1)
update_icon()
- update_held_icon()
+ update_worn_icon()
else
to_chat(user, "[src] does not have a power cell.")
@@ -218,7 +218,7 @@
icon_state = "[initial(icon_state)]"
if(!ignore_inhands)
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/energy/proc/start_recharge()
if(power_supply == null)
diff --git a/code/modules/projectiles/guns/energy/frontier.dm b/code/modules/projectiles/guns/energy/frontier.dm
index ded27a270018..3796836a2209 100644
--- a/code/modules/projectiles/guns/energy/frontier.dm
+++ b/code/modules/projectiles/guns/energy/frontier.dm
@@ -39,7 +39,6 @@
/obj/item/gun/energy/frontier/update_icon()
if(recharging)
icon_state = "[initial(icon_state)]_pump"
- update_held_icon()
return
..()
@@ -68,12 +67,6 @@
)
-/obj/item/gun/energy/frontier/locked/carbine/update_icon_state()
- . = ..()
- if(recharging)
- icon_state = "[modifystate]_pump"
- update_held_icon()
-
//Expeditionary Holdout Phaser Pistol
/obj/item/gun/energy/frontier/locked/holdout
name = "Holdout Phaser Pistol"
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 14aa3f933823..4dca17d1d3a4 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -491,7 +491,7 @@
. = ..()
if(overheating)
icon_state = "prifle_overheat"
- update_held_icon()
+ update_worn_icon()
else
return
@@ -528,7 +528,7 @@
. = ..()
if(overheating)
icon_state = "ppistol_overheat"
- update_held_icon()
+ update_worn_icon()
else
return
diff --git a/code/modules/projectiles/guns/launcher/pneumatic.dm b/code/modules/projectiles/guns/launcher/pneumatic.dm
index f990a8d6935c..ee5e5db521d6 100644
--- a/code/modules/projectiles/guns/launcher/pneumatic.dm
+++ b/code/modules/projectiles/guns/launcher/pneumatic.dm
@@ -132,10 +132,7 @@
/obj/item/gun/launcher/pneumatic/update_icon()
. = ..()
- if (ismob(src.loc))
- var/mob/M = src.loc
- M.update_inv_r_hand()
- M.update_inv_l_hand()
+ update_worn_icon()
/obj/item/gun/launcher/pneumatic/update_icon_state()
. = ..()
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
index 322951fb7dd5..4fbe61ed0bb5 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm
@@ -29,7 +29,7 @@
/obj/item/gun/ballistic/automatic/fluff/crestrose/switch_firemodes(mob/user)
if(..())
update_icon()
- update_held_icon()
+ update_worn_icon()
// todo: uhh we can fix it later maybe idfk
// /obj/item/gun/ballistic/automatic/fluff/crestrose/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm b/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
index 77a05acafa81..ae00b9653639 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/custom_guns.dm
@@ -20,7 +20,7 @@
/obj/item/gun/ballistic/automatic/battlerifle/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/battlerifle/update_icon_state()
. = ..()
@@ -61,7 +61,7 @@
/obj/item/gun/ballistic/automatic/pdw/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/pdw/update_icon_state()
. = ..()
@@ -102,7 +102,7 @@
/obj/item/gun/ballistic/automatic/stg/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/stg/update_icon_state()
. = ..()
diff --git a/code/modules/projectiles/guns/legacy_vr_guns/gunsword.dm b/code/modules/projectiles/guns/legacy_vr_guns/gunsword.dm
index b83837bc0de2..22544b989f60 100644
--- a/code/modules/projectiles/guns/legacy_vr_guns/gunsword.dm
+++ b/code/modules/projectiles/guns/legacy_vr_guns/gunsword.dm
@@ -102,16 +102,10 @@
H?.take_random_targeted_damage(brute = 5, burn = 5)
deactivate(user)
update_icon()
- update_held_icon()
else
activate(user)
update_icon()
- update_held_icon()
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
+ update_worn_icon()
add_fingerprint(user)
return
diff --git a/code/modules/projectiles/guns/modular_guns.dm b/code/modules/projectiles/guns/modular_guns.dm
index 7e226f09e813..4a7a0a128ab5 100644
--- a/code/modules/projectiles/guns/modular_guns.dm
+++ b/code/modules/projectiles/guns/modular_guns.dm
@@ -144,7 +144,7 @@
user.visible_message("[user] inserts [P] into [src].", "You insert [P] into [src].")
playsound(src.loc, 'sound/weapons/flipblade.ogg', 50, 1)
update_icon()
- update_held_icon()
+ update_worn_icon()
return
/obj/item/gun/energy/modular/pistol
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index 4cdf03148e99..18bce0997603 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -95,7 +95,7 @@
/obj/item/gun/ballistic/automatic/sts35/update_icon(ignore_inhands)
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/wt550
name = "machine pistol"
@@ -189,7 +189,7 @@
/obj/item/gun/ballistic/automatic/z8/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/z8/examine(mob/user, dist)
. = ..()
@@ -247,7 +247,7 @@
cover_open = !cover_open
to_chat(user, "You [cover_open ? "open" : "close"] [src]'s cover.")
update_icon()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/lmg/attack_self(mob/user, datum/event_args/actor/actor)
if(cover_open)
@@ -263,7 +263,7 @@
/obj/item/gun/ballistic/automatic/lmg/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/lmg/update_icon_state()
. = ..()
@@ -467,7 +467,7 @@
/obj/item/gun/ballistic/automatic/tommygun/update_icon_state()
. = ..()
icon_state = (ammo_magazine)? "tommygun" : "tommygun-empty"
-// update_held_icon()
+// update_worn_icon()
/obj/item/gun/ballistic/automatic/bullpup // Admin abuse assault rifle. ToDo: Make this less shit. Maybe remove its autofire, and make it spawn with only 10 rounds at start.
name = "bullpup rifle"
@@ -498,7 +498,7 @@
/obj/item/gun/ballistic/automatic/bullpup/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
/obj/item/gun/ballistic/automatic/fal
name = "FN-FAL"
@@ -786,4 +786,4 @@
/obj/item/gun/ballistic/automatic/lmg/foam/update_icon()
. = ..()
- update_held_icon()
+ update_worn_icon()
diff --git a/code/modules/projectiles/projectile/subtypes/energy/hook.dm b/code/modules/projectiles/projectile/subtypes/energy/hook.dm
index 65eeec07a86e..dec0bb763e3e 100644
--- a/code/modules/projectiles/projectile/subtypes/energy/hook.dm
+++ b/code/modules/projectiles/projectile/subtypes/energy/hook.dm
@@ -93,7 +93,7 @@
playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
return
- H.drop_all_held_items()
+ H.drop_held_items()
visible_message("\The [src] has disarmed [H]!")
playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
return
diff --git a/code/modules/reagents/chemistry/reagents/other/cleaner.dm b/code/modules/reagents/chemistry/reagents/other/cleaner.dm
index 7e3295f31390..37009458c0fc 100644
--- a/code/modules/reagents/chemistry/reagents/other/cleaner.dm
+++ b/code/modules/reagents/chemistry/reagents/other/cleaner.dm
@@ -25,10 +25,8 @@
M.adjustToxLoss(rand(5, 10))
/datum/reagent/space_cleaner/affect_touch(mob/living/carbon/M, alien, removed)
- if(M.r_hand)
- M.r_hand.clean_blood()
- if(M.l_hand)
- M.l_hand.clean_blood()
+ for(var/obj/item/held as anything in M.get_held_items())
+ held.clean_blood()
if(M.wear_mask)
if(M.wear_mask.clean_blood())
M.update_inv_wear_mask(0)
diff --git a/code/modules/resleeving/mirror.dm b/code/modules/resleeving/mirror.dm
index 1ae42e1f719f..05cdcd7fb53b 100644
--- a/code/modules/resleeving/mirror.dm
+++ b/code/modules/resleeving/mirror.dm
@@ -210,5 +210,5 @@
imp = I
user.visible_message("[user] inserts the [I] into the [src].", "You insert the [I] into the [src].")
update_icon()
- update_held_icon()
+ update_worn_icon()
return
diff --git a/code/modules/species/outsider/vox.dm b/code/modules/species/outsider/vox.dm
index 4d9fd5581afd..847423b80fa6 100644
--- a/code/modules/species/outsider/vox.dm
+++ b/code/modules/species/outsider/vox.dm
@@ -125,10 +125,8 @@
H.equip_to_slot_or_del(new /obj/item/clothing/mask/breath(H), SLOT_ID_MASK, INV_OP_SILENT | INV_OP_FLUFFLESS)
if(H.backbag == 1)
H.equip_to_slot_or_del(new /obj/item/tank/vox(H), SLOT_ID_BACK, INV_OP_SILENT | INV_OP_FLUFFLESS)
- H.internal = H.back
else
H.equip_to_slot_or_del(new /obj/item/tank/vox(H), /datum/inventory_slot/abstract/hand/right, INV_OP_SILENT | INV_OP_FLUFFLESS)
- H.internal = H.r_hand
H.internal = locate(/obj/item/tank) in H.contents
if(istype(H.internal,/obj/item/tank) && H.internals)
H.internals.icon_state = "internal1"
diff --git a/code/modules/species/promethean/promethean_blob.dm b/code/modules/species/promethean/promethean_blob.dm
index 59f53f770869..40df906f7534 100644
--- a/code/modules/species/promethean/promethean_blob.dm
+++ b/code/modules/species/promethean/promethean_blob.dm
@@ -21,10 +21,7 @@
//glow_intensity = 0
var/mob/living/carbon/human/humanform
- var/datum/modifier/healing
-
- var/datum/weakref/prev_left_hand
- var/datum/weakref/prev_right_hand
+ var/list/datum/weakref/previously_held
var/human_brute = 0
var/human_burn = 0
@@ -357,15 +354,7 @@
//Size update
blob.transform = matrix()*size_multiplier
blob.size_multiplier = size_multiplier
-
- if(l_hand)
- blob.prev_left_hand = WEAKREF(l_hand) //Won't save them if dropped above, but necessary if handdrop is disabled.
- else
- blob.prev_left_hand = null //make it so prommies can't just "recall" items magically if they had nothing in their hand.
- if(r_hand)
- blob.prev_right_hand = WEAKREF(r_hand)
- else
- blob.prev_right_hand = null //make it so prommies can't just "recall" items magically if they had nothing in their hand.
+ blob.previously_held = inventory?.get_held_items_as_weakrefs()
//Put our owner in it (don't transfer var/mind)
blob.transforming = TRUE
@@ -468,10 +457,12 @@
//vore_organs.Cut()
- if(blob.prev_left_hand)
- put_in_left_hand(blob.prev_left_hand.resolve()) //The restore for when reforming.
- if(blob.prev_right_hand)
- put_in_right_hand(blob.prev_right_hand.resolve())
+ for(var/i in 1 to length(blob.previously_held))
+ var/datum/weakref/ref = blob.previously_held[i]
+ var/obj/item/resolved = ref?.resolve()
+ if(isnull(resolved))
+ continue
+ put_in_hands_or_drop(resolved, specific_index = i)
if(!isnull(blob.mob_radio))
if(!equip_to_slots_if_possible(blob.mob_radio, list(
diff --git a/code/modules/species/protean/protean_blob.dm b/code/modules/species/protean/protean_blob.dm
index 7b8438c15b91..2a6a13850edb 100644
--- a/code/modules/species/protean/protean_blob.dm
+++ b/code/modules/species/protean/protean_blob.dm
@@ -52,8 +52,7 @@
var/obj/item/organ/internal/nano/refactory/refactory
var/datum/modifier/healing
- var/datum/weakref/prev_left_hand
- var/datum/weakref/prev_right_hand
+ var/list/datum/weakref/previously_held
player_msg = "In this form, you can move a little faster and your health will regenerate as long as you have metal in you!"
holder_type = /obj/item/holder/protoblob
@@ -320,12 +319,7 @@
//Size update
blob.transform = matrix()*size_multiplier
blob.size_multiplier = size_multiplier
-
- if(l_hand)
- blob.prev_left_hand = WEAKREF(l_hand) //Won't save them if dropped above, but necessary if handdrop is disabled.
- if(r_hand)
- blob.prev_right_hand = WEAKREF(r_hand)
-
+ blob.previously_held = inventory?.get_held_items_as_weakrefs()
//languages!!
for(var/datum/prototype/language/L in languages)
blob.add_language(L.name)
@@ -485,10 +479,12 @@
B.forceMove(src)
B.owner = src
- if(blob.prev_left_hand)
- put_in_left_hand(blob.prev_left_hand.resolve()) //The restore for when reforming.
- if(blob.prev_right_hand)
- put_in_right_hand(blob.prev_right_hand.resolve())
+ for(var/i in 1 to length(blob.previously_held))
+ var/datum/weakref/ref = blob.previously_held[i]
+ var/obj/item/resolved = ref?.resolve()
+ if(isnull(resolved))
+ continue
+ put_in_hands_or_drop(resolved, specific_index = i)
if(!isnull(blob.mob_radio))
if(!equip_to_slots_if_possible(blob.mob_radio, list(
diff --git a/code/modules/species/species.dm b/code/modules/species/species.dm
index 49f83c734ec3..f15edddfeb38 100644
--- a/code/modules/species/species.dm
+++ b/code/modules/species/species.dm
@@ -111,6 +111,29 @@
/// Used for offsetting large icons.
var/pixel_offset_y = 0
+ //* Inventory *//
+
+ /// Available inventory slots IDs
+ ///
+ /// * associate to list for remapping; use INVENTORY_SLOT_REMAP_* keys
+ var/list/inventory_slots = list(
+ /datum/inventory_slot/inventory/back::id,
+ /datum/inventory_slot/inventory/suit::id,
+ /datum/inventory_slot/inventory/suit_storage::id,
+ /datum/inventory_slot/inventory/uniform::id,
+ /datum/inventory_slot/inventory/ears/left::id,
+ /datum/inventory_slot/inventory/ears/right::id,
+ /datum/inventory_slot/inventory/glasses::id,
+ /datum/inventory_slot/inventory/gloves::id,
+ /datum/inventory_slot/inventory/mask::id,
+ /datum/inventory_slot/inventory/shoes::id,
+ /datum/inventory_slot/inventory/pocket/left::id,
+ /datum/inventory_slot/inventory/pocket/right::id,
+ /datum/inventory_slot/inventory/belt::id,
+ /datum/inventory_slot/inventory/id::id,
+ /datum/inventory_slot/inventory/head::id,
+ )
+
//? Overlays
/// Used by changelings. Should also be used for icon previews.
var/base_color
diff --git a/code/modules/species/species_hud.dm b/code/modules/species/species_hud.dm
index dbbdcdff25fc..dd64ece05679 100644
--- a/code/modules/species/species_hud.dm
+++ b/code/modules/species/species_hud.dm
@@ -1,7 +1,5 @@
/datum/hud_data
var/icon // If set, overrides ui_style.
- var/has_a_intent = 1 // Set to draw intent box.
- var/has_m_intent = 1 // Set to draw move intent box.
var/has_warnings = 1 // Set to draw environment warnings.
var/has_pressure = 1 // Draw the pressure indicator.
var/has_nutrition = 1 // Draw the nutrition indicator.
@@ -11,56 +9,3 @@
var/has_throw = 1 // Set to draw throw button.
var/has_resist = 1 // Set to draw resist button.
var/has_internals = 1 // Set to draw the internals toggle button.
- var/list/equip_slots = list() // Checked by mob_can_equip().
-
- // Contains information on the position and tag for all inventory slots
- // to be drawn for the mob. This is fairly delicate, try to avoid messing with it
- // unless you know exactly what it does.
- // keyed by slot ID.
- var/list/gear = list(
- SLOT_ID_UNIFORM = list("loc" = ui_iclothing, "name" = "Uniform", "slot" = SLOT_ID_UNIFORM, "state" = "center", "toggle" = 1),
- SLOT_ID_SUIT = list("loc" = ui_oclothing, "name" = "Suit", "slot" = SLOT_ID_SUIT, "state" = "suit", "toggle" = 1),
- SLOT_ID_MASK = list("loc" = ui_mask, "name" = "Mask", "slot" = SLOT_ID_MASK, "state" = "mask", "toggle" = 1),
- SLOT_ID_GLOVES = list("loc" = ui_gloves, "name" = "Gloves", "slot" = SLOT_ID_GLOVES, "state" = "gloves", "toggle" = 1),
- SLOT_ID_GLASSES = list("loc" = ui_glasses, "name" = "Glasses", "slot" = SLOT_ID_GLASSES, "state" = "glasses","toggle" = 1),
- SLOT_ID_LEFT_EAR = list("loc" = ui_l_ear, "name" = "Left Ear", "slot" = SLOT_ID_LEFT_EAR, "state" = "ears", "toggle" = 1),
- SLOT_ID_RIGHT_EAR = list("loc" = ui_r_ear, "name" = "Right Ear", "slot" = SLOT_ID_RIGHT_EAR, "state" = "ears", "toggle" = 1),
- SLOT_ID_HEAD = list("loc" = ui_head, "name" = "Hat", "slot" = SLOT_ID_HEAD, "state" = "hair", "toggle" = 1),
- SLOT_ID_SHOES = list("loc" = ui_shoes, "name" = "Shoes", "slot" = SLOT_ID_SHOES, "state" = "shoes", "toggle" = 1),
- SLOT_ID_SUIT_STORAGE = list("loc" = ui_sstore1, "name" = "Suit Storage", "slot" = SLOT_ID_SUIT_STORAGE, "state" = "suitstore"),
- SLOT_ID_BACK = list("loc" = ui_back, "name" = "Back", "slot" = SLOT_ID_BACK, "state" = "back"),
- SLOT_ID_WORN_ID = list("loc" = ui_id, "name" = "ID", "slot" = SLOT_ID_WORN_ID, "state" = "id"),
- SLOT_ID_LEFT_POCKET = list("loc" = ui_storage1, "name" = "Left Pocket", "slot" = SLOT_ID_LEFT_POCKET, "state" = "pocket"),
- SLOT_ID_RIGHT_POCKET = list("loc" = ui_storage2, "name" = "Right Pocket", "slot" = SLOT_ID_RIGHT_POCKET, "state" = "pocket"),
- SLOT_ID_BELT = list("loc" = ui_belt, "name" = "Belt", "slot" = SLOT_ID_BELT, "state" = "belt")
- )
-
-/datum/hud_data/New()
- ..()
- for(var/slot in gear)
- equip_slots |= gear[slot]["slot"]
-
- if(has_hands)
- equip_slots |= SLOT_ID_HANDCUFFED
-
- equip_slots |= SLOT_ID_LEGCUFFED
-
-/datum/hud_data/diona
- has_internals = 0
- gear = list(
- SLOT_ID_UNIFORM = list("loc" = ui_iclothing, "name" = "Uniform", "slot" = SLOT_ID_UNIFORM, "state" = "center", "toggle" = 1),
- SLOT_ID_SUIT = list("loc" = ui_shoes, "name" = "Suit", "slot" = SLOT_ID_SUIT, "state" = "suit", "toggle" = 1),
- SLOT_ID_LEFT_EAR = list("loc" = ui_gloves, "name" = "Left Ear", "slot" = SLOT_ID_LEFT_EAR, "state" = "ears", "toggle" = 1),
- SLOT_ID_HEAD = list("loc" = ui_oclothing, "name" = "Hat", "slot" = SLOT_ID_HEAD, "state" = "hair", "toggle" = 1),
- SLOT_ID_SUIT_STORAGE = list("loc" = ui_sstore1, "name" = "Suit Storage", "slot" = SLOT_ID_SUIT_STORAGE, "state" = "suitstore"),
- SLOT_ID_BACK = list("loc" = ui_back, "name" = "Back", "slot" = SLOT_ID_BACK, "state" = "back"),
- SLOT_ID_WORN_ID = list("loc" = ui_id, "name" = "ID", "slot" = SLOT_ID_WORN_ID, "state" = "id"),
- SLOT_ID_LEFT_POCKET = list("loc" = ui_storage1, "name" = "Left Pocket", "slot" = SLOT_ID_LEFT_POCKET, "state" = "pocket"),
- SLOT_ID_RIGHT_POCKET = list("loc" = ui_storage2, "name" = "Right Pocket", "slot" = SLOT_ID_RIGHT_POCKET, "state" = "pocket"),
- )
-
-/datum/hud_data/monkey
- gear = list(
- SLOT_ID_MASK = list("loc" = ui_shoes, "name" = "Mask", "slot" = SLOT_ID_MASK, "state" = "mask", "toggle" = 1),
- SLOT_ID_BACK = list("loc" = ui_sstore1, "name" = "Back", "slot" = SLOT_ID_BACK, "state" = "back"),
- )
diff --git a/code/modules/species/station/adherent.dm b/code/modules/species/station/adherent.dm
index e6b9e0d1a3cc..f55fd5a8f4ae 100644
--- a/code/modules/species/station/adherent.dm
+++ b/code/modules/species/station/adherent.dm
@@ -75,7 +75,6 @@
vision_innate = /datum/vision/baseline/species_tier_2
- hud_type = /datum/hud_data/adherent
/*
available_cultural_info = list(
TAG_CULTURE = list(
@@ -134,6 +133,24 @@
wikilink = "N/A"
+ //* Inventory *//
+
+ inventory_slots = list(
+ /datum/inventory_slot/inventory/back::id,
+ /datum/inventory_slot/inventory/ears/left::id = list(
+ INVENTORY_SLOT_REMAP_NAME = "Aux Port (1)",
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 1,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 0,
+ ),
+ /datum/inventory_slot/inventory/ears/right::id = list(
+ INVENTORY_SLOT_REMAP_NAME = "Aux Port (2)",
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 2,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 0,
+ ),
+ /datum/inventory_slot/inventory/belt::id,
+ /datum/inventory_slot/inventory/id::id,
+ )
+
/datum/species/adherent/equip_survival_gear(mob/living/carbon/human/H, extendedtank = FALSE, comprehensive = FALSE)
H.equip_to_slot_or_del(new /obj/item/storage/belt/utility/crystal, /datum/inventory_slot/abstract/put_in_backpack)
@@ -198,15 +215,5 @@
if(can_overcome_gravity(H))
return "They are floating on a cloud of shimmering distortion."
-/datum/hud_data/adherent
- has_internals = FALSE
- gear = list(
- SLOT_ID_LEFT_EAR = list("loc" = ui_iclothing, "name" = "Aux Port", "slot" = SLOT_ID_LEFT_EAR, "state" = "ears", "toggle" = 1),
- SLOT_ID_HEAD = list("loc" = ui_glasses, "name" = "Hat", "slot" = SLOT_ID_HEAD, "state" = "hair", "toggle" = 1),
- SLOT_ID_BACK = list("loc" = ui_back, "name" = "Back", "slot" = SLOT_ID_BACK, "state" = "back"),
- SLOT_ID_WORN_ID = list("loc" = ui_id, "name" = "ID", "slot" = SLOT_ID_WORN_ID, "state" = "id"),
- SLOT_ID_BELT = list("loc" = ui_belt, "name" = "Belt", "slot" = SLOT_ID_BELT, "state" = "belt"),
- )
-
/datum/species/adherent/post_organ_rejuvenate(obj/item/organ/org, mob/living/carbon/human/H)
org.robotic = ORGAN_CRYSTAL
diff --git a/code/modules/species/station/diona.dm b/code/modules/species/station/diona.dm
index 5505755348c6..18c736fc3852 100644
--- a/code/modules/species/station/diona.dm
+++ b/code/modules/species/station/diona.dm
@@ -32,7 +32,6 @@
dark_slowdown = 3
snow_movement = -2 // Ignore light snow
water_movement = -4 // Ignore shallow water
- hud_type = /datum/hud_data/diona
siemens_coefficient = 0.3
show_ssd = "completely quiescent"
health_hud_intensity = 2.5
@@ -119,6 +118,29 @@
genders = list(PLURAL)
+ //* Inventory *//
+
+ inventory_slots = list(
+ /datum/inventory_slot/inventory/back::id,
+ /datum/inventory_slot/inventory/suit::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 0,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 1,
+ ),
+ /datum/inventory_slot/inventory/suit_storage::id,
+ /datum/inventory_slot/inventory/uniform::id,
+ /datum/inventory_slot/inventory/ears/left::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 2,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 2,
+ ),
+ /datum/inventory_slot/inventory/ears/right::id,
+ /datum/inventory_slot/inventory/pocket/left::id,
+ /datum/inventory_slot/inventory/pocket/right::id,
+ /datum/inventory_slot/inventory/id::id,
+ /datum/inventory_slot/inventory/head::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 1,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 1,
+ ),
+ )
/datum/species/diona/can_understand(mob/other)
var/mob/living/carbon/alien/diona/D = other
diff --git a/code/modules/species/station/monkey.dm b/code/modules/species/station/monkey.dm
index 7719bf4df6ec..6367ef4d319f 100644
--- a/code/modules/species/station/monkey.dm
+++ b/code/modules/species/station/monkey.dm
@@ -32,7 +32,6 @@
unarmed_types = list(/datum/unarmed_attack/bite, /datum/unarmed_attack/claws)
inherent_verbs = list(/mob/living/proc/ventcrawl)
- hud_type = /datum/hud_data/monkey
meat_type = /obj/item/reagent_containers/food/snacks/meat/monkey
//rarity_value = 0.1
@@ -64,6 +63,22 @@
BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right),
)
+ //* Inventory *//
+
+ inventory_slots = list(
+ /datum/inventory_slot/inventory/back::id,
+ /datum/inventory_slot/inventory/id::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 0,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 2,
+ INVENTORY_SLOT_REMAP_CLASS = INVENTORY_HUD_CLASS_ALWAYS,
+ ),
+ /datum/inventory_slot/inventory/mask::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 0,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 1,
+ INVENTORY_SLOT_REMAP_CLASS = INVENTORY_HUD_CLASS_ALWAYS,
+ ),
+ )
+
/datum/species/monkey/handle_npc(mob/living/carbon/human/H)
if(H.stat != CONSCIOUS)
return
diff --git a/code/modules/species/station/station_special_abilities.dm b/code/modules/species/station/station_special_abilities.dm
index 191540472b40..b7debb074b5d 100644
--- a/code/modules/species/station/station_special_abilities.dm
+++ b/code/modules/species/station/station_special_abilities.dm
@@ -159,51 +159,51 @@
if(16 to 25) //10% chance
//Strange items
//to_chat(src, "Traitor Items")
- if(!halitem)
- halitem = new
- var/list/slots_free = list(ui_lhand,ui_rhand)
- if(l_hand) slots_free -= ui_lhand
- if(r_hand) slots_free -= ui_rhand
- if(istype(src,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = src
- if(!H.belt) slots_free += ui_belt
- if(!H.l_store) slots_free += ui_storage1
- if(!H.r_store) slots_free += ui_storage2
- if(slots_free.len)
- halitem.screen_loc = pick(slots_free)
- halitem.layer = 50
- switch(rand(1,6))
- if(1) //revolver
- halitem.icon = 'icons/obj/gun/ballistic.dmi'
- halitem.icon_state = "revolver"
- halitem.name = "Revolver"
- if(2) //c4
- halitem.icon = 'icons/obj/assemblies.dmi'
- halitem.icon_state = "plastic-explosive0"
- halitem.name = "Mysterious Package"
- if(prob(25))
- halitem.icon_state = "c4small_1"
- if(3) //sword
- halitem.icon = 'icons/obj/weapons.dmi'
- halitem.icon_state = "sword1"
- halitem.name = "Sword"
- if(4) //stun baton
- halitem.icon = 'icons/obj/weapons.dmi'
- halitem.icon_state = "stunbaton"
- halitem.name = "Stun Baton"
- if(5) //emag
- halitem.icon = 'icons/obj/card.dmi'
- halitem.icon_state = "emag"
- halitem.name = "Cryptographic Sequencer"
- if(6) //flashbang
- halitem.icon = 'icons/obj/grenade.dmi'
- halitem.icon_state = "flashbang1"
- halitem.name = "Flashbang"
- if(client) client.screen += halitem
- spawn(rand(100,250))
- if(client)
- client.screen -= halitem
- halitem = null
+ // if(!halitem)
+ // halitem = new
+ // var/list/slots_free = list()
+ // for(var/i in get_empty_hand_indices())
+ // slots_free += SCREEN_LOC_INV_HAND(i)
+ // if(istype(src,/mob/living/carbon/human))
+ // var/mob/living/carbon/human/H = src
+ // if(!H.belt) slots_free += ui_belt
+ // if(!H.l_store) slots_free += ui_storage1
+ // if(!H.r_store) slots_free += ui_storage2
+ // if(slots_free.len)
+ // halitem.screen_loc = pick(slots_free)
+ // halitem.layer = 50
+ // switch(rand(1,6))
+ // if(1) //revolver
+ // halitem.icon = 'icons/obj/gun/ballistic.dmi'
+ // halitem.icon_state = "revolver"
+ // halitem.name = "Revolver"
+ // if(2) //c4
+ // halitem.icon = 'icons/obj/assemblies.dmi'
+ // halitem.icon_state = "plastic-explosive0"
+ // halitem.name = "Mysterious Package"
+ // if(prob(25))
+ // halitem.icon_state = "c4small_1"
+ // if(3) //sword
+ // halitem.icon = 'icons/obj/weapons.dmi'
+ // halitem.icon_state = "sword1"
+ // halitem.name = "Sword"
+ // if(4) //stun baton
+ // halitem.icon = 'icons/obj/weapons.dmi'
+ // halitem.icon_state = "stunbaton"
+ // halitem.name = "Stun Baton"
+ // if(5) //emag
+ // halitem.icon = 'icons/obj/card.dmi'
+ // halitem.icon_state = "emag"
+ // halitem.name = "Cryptographic Sequencer"
+ // if(6) //flashbang
+ // halitem.icon = 'icons/obj/grenade.dmi'
+ // halitem.icon_state = "flashbang1"
+ // halitem.name = "Flashbang"
+ // if(client) client.screen += halitem
+ // spawn(rand(100,250))
+ // if(client)
+ // client.screen -= halitem
+ // halitem = null
if(26 to 35) //10% chance
//Flashes of danger
//to_chat(src, "Danger Flash")
diff --git a/code/modules/species/xenomorphs/alien_powers.dm b/code/modules/species/xenomorphs/alien_powers.dm
index 442ce88c3359..f8701abf4501 100644
--- a/code/modules/species/xenomorphs/alien_powers.dm
+++ b/code/modules/species/xenomorphs/alien_powers.dm
@@ -307,25 +307,18 @@
T.afflict_paralyze(20 * 3)
- var/use_hand = "left"
- if(l_hand)
- if(r_hand)
- to_chat(src, "You need to have one hand free to grab someone.")
- return
- else
- use_hand = "right"
+ if(are_usable_hands_full())
+ to_chat(src, "You need to have one hand free to grab someone.")
+ return
src.visible_message("\The [src] seizes [T] aggressively!")
- var/obj/item/grab/G = new(src,T)
- if(use_hand == "left")
- l_hand = G
- else
- r_hand = G
+ var/obj/item/grab/G = new(src, T)
+ if(!put_in_hands_or_del(G))
+ return
G.state = GRAB_PASSIVE
G.icon_state = "grabbed1"
- G.synch()
/mob/living/carbon/human/proc/gut()
set category = "Abilities"
diff --git a/code/modules/species/xenomorphs/alien_species.dm b/code/modules/species/xenomorphs/alien_species.dm
index e8eaf1b9b056..6016d95308a2 100644
--- a/code/modules/species/xenomorphs/alien_species.dm
+++ b/code/modules/species/xenomorphs/alien_species.dm
@@ -325,6 +325,21 @@
/mob/living/carbon/human/proc/acidspit,
)
+ //* Inventory *//
+
+ inventory_slots = list(
+ /datum/inventory_slot/inventory/suit::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 0,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 4,
+ ),
+ /datum/inventory_slot/inventory/pocket/left::id,
+ /datum/inventory_slot/inventory/pocket/right::id,
+ /datum/inventory_slot/inventory/head::id = list(
+ INVENTORY_SLOT_REMAP_MAIN_AXIS = 0,
+ INVENTORY_SLOT_REMAP_CROSS_AXIS = 3,
+ ),
+ )
+
/datum/species/xenos/queen/handle_login_special(var/mob/living/carbon/human/H)
..()
// Make sure only one official queen exists at any point.
@@ -338,10 +353,7 @@
/datum/hud_data/alien
icon = 'icons/mob/screen1_alien.dmi'
- has_a_intent = 1
- has_m_intent = 1
has_warnings = 1
- has_hands = 1
has_drop = 1
has_throw = 1
has_resist = 1
@@ -349,10 +361,3 @@
has_nutrition = 0
has_bodytemp = 0
has_internals = 0
-
- gear = list(
- SLOT_ID_SUIT = list("loc" = ui_belt, "name" = "Suit", "slot" = SLOT_ID_SUIT, "state" = "equip", "dir" = SOUTH),
- SLOT_ID_HEAD = list("loc" = ui_id, "name" = "Hat", "slot" = SLOT_ID_HEAD, "state" = "hair"),
- SLOT_ID_LEFT_POCKET = list("loc" = ui_storage1, "name" = "Left Pocket", "slot" = SLOT_ID_LEFT_POCKET, "state" = "pocket"),
- SLOT_ID_RIGHT_POCKET = list("loc" = ui_storage2, "name" = "Right Pocket", "slot" = SLOT_ID_RIGHT_POCKET, "state" = "pocket"),
- )
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index a30d1f7c629d..4281f386c575 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -136,6 +136,8 @@
* * Separate from open() so that open() can be non-blocking.
*/
/datum/tgui/proc/initialize(data, modules)
+ // todo: this is a blocking proc. src_object can be deleted at any time between the blocking procs.
+ // we need sane handling of deletion order, of runtimes happen.
if(!window.is_ready())
window.initialize(
strict_mode = TRUE,
diff --git a/code/modules/vehicles/sealed/mecha/mecha.dm b/code/modules/vehicles/sealed/mecha/mecha.dm
index 30a9c3ae6bd5..adbe091a1dc5 100644
--- a/code/modules/vehicles/sealed/mecha/mecha.dm
+++ b/code/modules/vehicles/sealed/mecha/mecha.dm
@@ -565,32 +565,6 @@
occupant_legacy << browse(src.get_stats_html(), "window=exosuit")
-////////////////////////////
-///// Action processing ////
-////////////////////////////
-/*
-/atom/DblClick(object,location,control,params)
- var/mob/M = src.mob
- if(M && M.in_contents_of(/obj/vehicle/sealed/mecha))
-
- if(mech_click == world.time) return
- mech_click = world.time
-
- if(!istype(object, /atom)) return
- if(istype(object, /atom/movable/screen))
- var/atom/movable/screen/using = object
- if(using.screen_loc == ui_acti || using.screen_loc == ui_iarrowleft || using.screen_loc == ui_iarrowright)//ignore all HUD objects save 'intent' and its arrows
- return ..()
- else
- return
- var/obj/vehicle/sealed/mecha/Mech = M.loc
- spawn() //this helps prevent clickspam fest.
- if (Mech)
- Mech.click_action(object,M)
-// else
-// return ..()
-*/
-
/obj/vehicle/sealed/mecha/proc/click_action(atom/target,mob/user, params)
if(!src.occupant_legacy || src.occupant_legacy != user ) return
if(user.stat) return
diff --git a/code/modules/vore/fluffstuff/custom_items.dm b/code/modules/vore/fluffstuff/custom_items.dm
index 00f1836ba675..43a397dd526f 100644
--- a/code/modules/vore/fluffstuff/custom_items.dm
+++ b/code/modules/vore/fluffstuff/custom_items.dm
@@ -1158,9 +1158,9 @@
update_icon()
return
-/obj/item/melee/baton/fluff/stunstaff/update_held_icon()
+/obj/item/melee/baton/fluff/stunstaff/update_worn_icon()
var/mob/living/M = loc
- if(istype(M) && !issmall(M) && M.is_holding(src) && !M.hands_full())
+ if(istype(M) && !issmall(M) && M.is_holding(src) && !M.are_usable_hands_full())
wielded = 1
damage_force = 15
name = "[base_name] (wielded)"
@@ -1185,7 +1185,7 @@
if(wielded)
wielded = 0
spawn(0)
- update_held_icon()
+ update_worn_icon()
/obj/item/melee/baton/fluff/stunstaff/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
@@ -1201,7 +1201,7 @@
else
status = 0
to_chat(user, "[src] is out of charge.")
- update_held_icon()
+ update_worn_icon()
add_fingerprint(user)
/obj/item/storage/backpack/fluff/stunstaff
@@ -1266,14 +1266,8 @@
deactivate(user)
else
activate(user)
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
+ update_worn_icon()
add_fingerprint(user)
- return
/obj/item/melee/fluffstuff/suicide_act(mob/user)
var/tempgender = "[user.gender == MALE ? "he's" : user.gender == FEMALE ? "she's" : "they are"]"
@@ -1418,8 +1412,4 @@
else
icon_state = "jazzcamcorder"
item_state = "jazzcamcorder"
- var/mob/living/carbon/human/H = loc
- if(istype(H))
- H.update_inv_r_hand()
- H.update_inv_l_hand()
- H.update_inv_belt()
+ update_worn_icon()
diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm
index 9852e887903e..33af9b05e594 100644
--- a/code/modules/vore/resizing/resize_vr.dm
+++ b/code/modules/vore/resizing/resize_vr.dm
@@ -125,12 +125,8 @@ var/const/RESIZE_A_SMALLTINY = (RESIZE_SMALL + RESIZE_TINY) / 2
var/size_diff = M.get_effective_size() - get_effective_size()
if(!holder_default && holder_type)
holder_default = holder_type
- if(!istype(M))
+ if(!istype(M) || !M.has_hands())
return FALSE
- if(isanimal(M))
- var/mob/living/simple_mob/SA = M
- if(!SA.has_hands)
- return FALSE
if(M.get_active_held_item() && !istype(M.get_active_held_item(), /obj/item/grab)) //scooper's hand is holding something that isn't a grab.
to_chat(M, SPAN_WARNING("You can't pick up someone with your occupied hand."))
return TRUE
diff --git a/icons/mob/screen/holo.dmi b/icons/mob/screen/holo.dmi
index 539e07fbf7a9..72a5464604d4 100644
Binary files a/icons/mob/screen/holo.dmi and b/icons/mob/screen/holo.dmi differ
diff --git a/icons/mob/screen/midnight.dmi b/icons/mob/screen/midnight.dmi
index 829e80923208..8077973de881 100644
Binary files a/icons/mob/screen/midnight.dmi and b/icons/mob/screen/midnight.dmi differ
diff --git a/icons/mob/screen/minimalist.dmi b/icons/mob/screen/minimalist.dmi
index 5b4a19844f88..f146d3edfbfe 100644
Binary files a/icons/mob/screen/minimalist.dmi and b/icons/mob/screen/minimalist.dmi differ
diff --git a/icons/mob/screen/old.dmi b/icons/mob/screen/old.dmi
index 2805abc652b5..c23fb6234f65 100644
Binary files a/icons/mob/screen/old.dmi and b/icons/mob/screen/old.dmi differ
diff --git a/icons/mob/screen/orange.dmi b/icons/mob/screen/orange.dmi
index fc98b1cedab8..3778a7fd9c89 100644
Binary files a/icons/mob/screen/orange.dmi and b/icons/mob/screen/orange.dmi differ
diff --git a/icons/mob/screen/white.dmi b/icons/mob/screen/white.dmi
index 42cc4dc9cbbd..25eb215f36b2 100644
Binary files a/icons/mob/screen/white.dmi and b/icons/mob/screen/white.dmi differ
diff --git a/icons/mob/screen1_Midnight.dmi b/icons/mob/screen1_Midnight.dmi
index b7a884dfb230..8aae00e4960f 100644
Binary files a/icons/mob/screen1_Midnight.dmi and b/icons/mob/screen1_Midnight.dmi differ
diff --git a/icons/mob/screen1_Orange.dmi b/icons/mob/screen1_Orange.dmi
index 3a1b3fa2221d..5c3e3afb32d6 100644
Binary files a/icons/mob/screen1_Orange.dmi and b/icons/mob/screen1_Orange.dmi differ
diff --git a/icons/mob/screen1_White.dmi b/icons/mob/screen1_White.dmi
index 2286669e2779..afce3d16f1f2 100644
Binary files a/icons/mob/screen1_White.dmi and b/icons/mob/screen1_White.dmi differ
diff --git a/icons/mob/screen1_old.dmi b/icons/mob/screen1_old.dmi
index e24f995a5c32..4d023bdcf4cc 100644
Binary files a/icons/mob/screen1_old.dmi and b/icons/mob/screen1_old.dmi differ
diff --git a/icons/screen/hud/hologram/32x32.dmi b/icons/screen/hud/hologram/32x32.dmi
new file mode 100644
index 000000000000..be511b041894
Binary files /dev/null and b/icons/screen/hud/hologram/32x32.dmi differ
diff --git a/icons/screen/hud/hologram/inventory-slot.dmi b/icons/screen/hud/hologram/inventory-slot.dmi
new file mode 100644
index 000000000000..a1b29e56ca3b
Binary files /dev/null and b/icons/screen/hud/hologram/inventory-slot.dmi differ
diff --git a/icons/screen/hud/hologram/inventory-wide.dmi b/icons/screen/hud/hologram/inventory-wide.dmi
new file mode 100644
index 000000000000..ad7ccd76842c
Binary files /dev/null and b/icons/screen/hud/hologram/inventory-wide.dmi differ
diff --git a/icons/screen/hud/hologram/inventory.dmi b/icons/screen/hud/hologram/inventory.dmi
new file mode 100644
index 000000000000..50673be6fae8
Binary files /dev/null and b/icons/screen/hud/hologram/inventory.dmi differ
diff --git a/icons/screen/hud/midnight/32x32.dmi b/icons/screen/hud/midnight/32x32.dmi
new file mode 100644
index 000000000000..cf74d73796c8
Binary files /dev/null and b/icons/screen/hud/midnight/32x32.dmi differ
diff --git a/icons/screen/hud/midnight/inventory-slot.dmi b/icons/screen/hud/midnight/inventory-slot.dmi
new file mode 100644
index 000000000000..ec08f647d6b5
Binary files /dev/null and b/icons/screen/hud/midnight/inventory-slot.dmi differ
diff --git a/icons/screen/hud/midnight/inventory-wide.dmi b/icons/screen/hud/midnight/inventory-wide.dmi
new file mode 100644
index 000000000000..ae62f032b871
Binary files /dev/null and b/icons/screen/hud/midnight/inventory-wide.dmi differ
diff --git a/icons/screen/hud/midnight/inventory.dmi b/icons/screen/hud/midnight/inventory.dmi
new file mode 100644
index 000000000000..abef9e084a57
Binary files /dev/null and b/icons/screen/hud/midnight/inventory.dmi differ
diff --git a/icons/screen/hud/minimalist/32x32.dmi b/icons/screen/hud/minimalist/32x32.dmi
new file mode 100644
index 000000000000..509052f6a754
Binary files /dev/null and b/icons/screen/hud/minimalist/32x32.dmi differ
diff --git a/icons/screen/hud/minimalist/inventory-slot.dmi b/icons/screen/hud/minimalist/inventory-slot.dmi
new file mode 100644
index 000000000000..e1008481b6e7
Binary files /dev/null and b/icons/screen/hud/minimalist/inventory-slot.dmi differ
diff --git a/icons/screen/hud/minimalist/inventory-wide.dmi b/icons/screen/hud/minimalist/inventory-wide.dmi
new file mode 100644
index 000000000000..e3d6881c02d8
Binary files /dev/null and b/icons/screen/hud/minimalist/inventory-wide.dmi differ
diff --git a/icons/screen/hud/minimalist/inventory.dmi b/icons/screen/hud/minimalist/inventory.dmi
new file mode 100644
index 000000000000..3ab625d5f72d
Binary files /dev/null and b/icons/screen/hud/minimalist/inventory.dmi differ
diff --git a/icons/screen/hud/old/32x32.dmi b/icons/screen/hud/old/32x32.dmi
new file mode 100644
index 000000000000..22d9801354a8
Binary files /dev/null and b/icons/screen/hud/old/32x32.dmi differ
diff --git a/icons/screen/hud/old/inventory-slot.dmi b/icons/screen/hud/old/inventory-slot.dmi
new file mode 100644
index 000000000000..36eebe0ce960
Binary files /dev/null and b/icons/screen/hud/old/inventory-slot.dmi differ
diff --git a/icons/screen/hud/old/inventory-wide.dmi b/icons/screen/hud/old/inventory-wide.dmi
new file mode 100644
index 000000000000..9b4b1569795f
Binary files /dev/null and b/icons/screen/hud/old/inventory-wide.dmi differ
diff --git a/icons/screen/hud/old/inventory.dmi b/icons/screen/hud/old/inventory.dmi
new file mode 100644
index 000000000000..74d04a8afa66
Binary files /dev/null and b/icons/screen/hud/old/inventory.dmi differ
diff --git a/icons/screen/hud/orange/32x32.dmi b/icons/screen/hud/orange/32x32.dmi
new file mode 100644
index 000000000000..7f55b8e80f60
Binary files /dev/null and b/icons/screen/hud/orange/32x32.dmi differ
diff --git a/icons/screen/hud/orange/inventory-slot.dmi b/icons/screen/hud/orange/inventory-slot.dmi
new file mode 100644
index 000000000000..128813cb43cb
Binary files /dev/null and b/icons/screen/hud/orange/inventory-slot.dmi differ
diff --git a/icons/screen/hud/orange/inventory-wide.dmi b/icons/screen/hud/orange/inventory-wide.dmi
new file mode 100644
index 000000000000..b109874e5e3b
Binary files /dev/null and b/icons/screen/hud/orange/inventory-wide.dmi differ
diff --git a/icons/screen/hud/orange/inventory.dmi b/icons/screen/hud/orange/inventory.dmi
new file mode 100644
index 000000000000..a4f9751e0cdd
Binary files /dev/null and b/icons/screen/hud/orange/inventory.dmi differ
diff --git a/icons/screen/hud/white/32x32.dmi b/icons/screen/hud/white/32x32.dmi
new file mode 100644
index 000000000000..b2c310fb93bf
Binary files /dev/null and b/icons/screen/hud/white/32x32.dmi differ
diff --git a/icons/screen/hud/white/inventory-slot.dmi b/icons/screen/hud/white/inventory-slot.dmi
new file mode 100644
index 000000000000..fd1c2381a0ea
Binary files /dev/null and b/icons/screen/hud/white/inventory-slot.dmi differ
diff --git a/icons/screen/hud/white/inventory-wide.dmi b/icons/screen/hud/white/inventory-wide.dmi
new file mode 100644
index 000000000000..899fff420682
Binary files /dev/null and b/icons/screen/hud/white/inventory-wide.dmi differ
diff --git a/icons/screen/hud/white/inventory.dmi b/icons/screen/hud/white/inventory.dmi
new file mode 100644
index 000000000000..0bfcec299192
Binary files /dev/null and b/icons/screen/hud/white/inventory.dmi differ