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